React Native to limit lines in TextInput - react-native

If set multiline as true, React Native will accept unlimited lines in TextInput, there has maxLength but it just limits the maximum number of characters, so how to set maximum lines on TextInput?

You can use maxHeight and minHeight to accept what you want. For standard text fontSize, giving maxHeight={60} will make TextInput scrollable after 3 lines. This is good for IOS - for Android you can use numberOfLines prop.

I created a simple version to extend the TextInput to give the maxLines feature.
import React, { Component } from "react";
import { TextInput } from "react-native";
import PropTypes from "prop-types";
export default class MultiLine extends Component {
static propTypes = {
maxLines: PropTypes.number
};
constructor(props) {
super(props);
this.state = {
value: props.value || ""
};
}
onChangeText = text => {
const { maxLines, onChangeText } = this.props;
const lines = text.split("\n");
if (lines.length <= (maxLines || 1)) {
onChangeText(text);
this.setState({ value: text });
}
};
render() {
const { onChangeText, multiline, value, ...other } = this.props;
return (
<TextInput
{...other}
multiline={true}
value={this.state.value}
onChangeText={this.onChangeText}
/>
);
}
}
A example:
<Multiline
value={this.state.testeText}
maxLines={10}
onChangeText={text => {
this.setState({ testeText: text });
}}
/>

I have implemented the following component for an app I'm the owner. It is tested only on a restricted environment, so it probably has to be adapted to your need. I am sharing here with the hope it helps the community.
react-native: 0.46.3
import React, { Component } from 'react'
import { TextInput, Platform } from 'react-native'
const iosTextHeight = 20.5
const androidTextHeight = 20.5
const textHeight = Platform.OS === 'ios' ? iosTextHeight : androidTextHeight
class TextInputLines extends Component {
state = { height: textHeight * 2, lines: 1 }
componentWillReceiveProps (nextProps) {
if (nextProps.value === '') {
this.setState({ height: textHeight * 2, lines: 1 })
}
}
render () {
const {style, value, placeholder, numberOfLines = 8, autoFocus = true, onChangeText} = this.props
return (
<TextInput
style={[style, { height: this.state.height }]}
multiline
autoFocus={autoFocus}
value={value}
placeholder={placeholder}
onChangeText={(text) => onChangeText(text)}
onContentSizeChange={(event) => {
const height = Platform.OS === 'ios'
? event.nativeEvent.contentSize.height
: event.nativeEvent.contentSize.height - androidTextHeight
const lines = Math.round(height / textHeight)
const visibleLines = lines < numberOfLines ? lines : numberOfLines
const visibleHeight = textHeight * (visibleLines + 1)
this.setState({ height: visibleHeight, lines: visibleLines })
}}
underlineColorAndroid='transparent'
/>
)
}
}
export default TextInputLines
How to use:
import TextInputLines from './TextInputLines'
Within your render() method:
<TextInputLines
style={textStyle}
autoFocus={true}
value={this.state.value}
placeholder={textPlaceholder}
onChangeText={onChangeText}
numberOfLines={10}
/>

I don't think you can on iOS. As mentioned, Android has the prop numberOfLines. The closest I can think of would be to try to estimate the width of your input and limit the characters based on that dimension but it's my opinion that it may be a dark path to go down.
Should you choose to do so, someone was trying to get the number of lines here. A combination of the onLayout function to get the width of your TextInput and onChangeText to limit it might work.

It seems no props or packages to quickly implement checking number of lines, so I use javascript function match to get the number from TextInput, then if the number reach the max value, do something to limit it.

numberOfLines doesn't nothing in android too
You can do something like this
In Component state
this.state = {height: 35};
In JSX TextInput declaration
style={[styles.default, {height: Math.min(Math.max(35, this.state.height),120)}]}

I solved the limit issue using maxHeight,numberOfLine and multiline prop
<TextInput placeholder="description here" numberOfLines={3} maxHeight={60} onChangeText={t => setDescription(t)} multiline={true} style={{ margin: 10, borderRadius: 20, padding: 5, borderWidth: 1 }} />

I was solving this same issue. Here's what I came up with.
You can either remove the last trailing newline, or all of them. Both work differently, so use accordingly to how you want yours to function. I personally chose the remove last trailing newline alternative:
function onChangeText(text) {
if (props.multiline) {
const newlines = (text.match(/\n/g) || '').length + 1;
if (newlines > MAX_LINES) {
props.setState(text.replace(/\n$/, "")); // Remove last trailing newline
// props.setState(text.replace(/\n+$/, "")); // Remove all trailing newlines
return;
}
}
props.setState(text);
return;
};

Related

In React Native, creating a canva and calling getcontext create error

I am trying to use a function called readpixels from this github page and in this function one need to get the context of a canva, but since I am using React Native, I cannot use expressions like new Image() or document.createElement('canvas') so I am trying to do an equivalent using React Native functions.
Here is a minimal version of the code:
import React, { useState, useEffect, useRef } from 'react';
import { Button, Image, View } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import Canvas from 'react-native-canvas';
export function Canva() {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const ctx = ref.current.getContext('2d');
if (ctx) {
Alert.alert('Canvas is ready');
}
}
}, [ref]);
return (
<Canvas ref={ref} />
);
}
function readpixels(url, limit = 0) {
const img = React.createElement(
"img",
{
src: url,
},
)
const canvas = Canva()
const ctx = canvas.getContext('2d')
return 1
}
export default function ImagePickerExample() {
const [image, setImage] = useState(null);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
quality: 1,
});
readpixels(result.uri)
if (!result.cancelled) {
setImage({uri: result.uri, fileSize: result.fileSize});
}
};
return (
<View style={{ flex: 1, backgroundColor: "white", marginTop: 50 }} >
<Button title="Pick image from camera roll" onPress={pickImage} />
{image && <Image source={{ uri: image.uri }} style={{ width: 200, height: 200 }} />}
</View>
);
}
And here is the error that I get:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I have checked the three suggestions to solve the issue, but it did not work.
Thank you
p.s: in order to reproduce the code, you would need to install the react-native-canvas package

Change one button color when onPress in list

So i want to make a list with a heart button
when i tap on one heart button.
i want only one button changed color.
and save the current button color state.
My Problem is
When i tap on one heart button, all button changed color.
Screenshot:
this is my code
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
} from 'react-native';
import { ListItem } from 'react-native-elements'
import { Icon } from 'react-native-elements'
import Data from './src/data/sampledata.json';
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
buttonColor: '#979797', // default button color goes here, grey is default
};
}
onButtonPress = () => {
if(this.state.buttonColor=='#ff002b')
{
this.setState({ buttonColor: '#979797' }) // grey
}
else {
this.setState({ buttonColor: '#ff002b' }) // red
}
}
render() {
return (
<View style={styles.container}>
{
Data.map((item, i) => (
<ListItem
key={item.index}
title={item.dataItem}
rightIcon={
<Icon
raised
name='heart'
type='font-awesome'
color={this.state.buttonColor}
onPress={this.onButtonPress}
/>
}
/>
))
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
});
One way to achieve this is as follows:
What you can do is keep track of the selected items in an array. Using getUpdatedSelectedItemsArray function you can get an array that contains all the selected item like so
const getUpdatedSelectedItemsArray = (selectedItems, id) => {
const forDeletion = [id]; //can also be used for multiple ids
if (selectedItems.includes(id)) {
//if id already exists delete it
return selectedItems.filter(item => !forDeletion.includes(item));
}
//otherwise push it
selectedItems.push(id);
return selectedItems;
};
you can call the above function from this.onButtonPress(item) which takes item as an argument.
//Creating state
this.state = {
buttonColor: '#979797',
selectedItems: []
};
Calling getUpdatedSelectedItemsArray() will give you updated array which you can set in your selectedItems state.
Lastly you will have check while giving the color to icon(item), that if item.id exists in this.state.selectedItems then show red otherwise grey, like so
Data.map((item, i) => (
<ListItem
key={item.index}
title={item.dataItem}
rightIcon={
<Icon
raised
name='heart'
type='font-awesome'
color={this.checkIfIDExists(item.id) ? 'red' : 'grey'}
onPress={this.onButtonPress}
/>
}
/>
))
P.S this.checkIfIDExists(item.id) is a function the returns true if id exists in selectedItems array.

why are colors being updated multiple times

Why are my random colors being updated multiple times? Is the right way to control this behavior through a lifecycle event?
import React from 'react';
import { StyleSheet, Text, ScrollView, FlatList, SectionList, View, Button, SegmentedControlIOS } from 'react-native';
import contacts, {compareNames} from './contacts';
import {Constants} from 'expo';
import PropTypes from 'prop-types'
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
const Row=(props)=>(
<View style={styles.row}>
<Text style={{color:props.color}} >{props.name}</Text>
<Text >{props.phone}</Text>
</View>
)
const renderItem=(obj)=> {
return(<Row {...(obj.item)} color={getRandomColor()} />)
}
const ContactsList = props => {
const renderSectionHeader=(obj) =><Text>{obj.section.title}</Text>
const contactsByLetter = props.contacts.reduce((obj, contact) =>{
const firstLetter = contact.name[0].toUpperCase()
return{
...obj,
[firstLetter]: [...(obj[firstLetter] || []),contact],
}
},{})
const sections = Object.keys(contactsByLetter).sort().map(letter=>({
title: letter,
data: contactsByLetter[letter],
}))
return(
<SectionList
keyExtractor = { (item, key) => key.toString() }
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
sections={sections}
/>
)}
ContactsList.propTypes ={
renderItem: PropTypes.func,
renderSectionHeader: PropTypes.func,
contacts: PropTypes.array,
sections: PropTypes.func
}
export default class App extends React.Component {
state={show: false, selectedIndex: 0, contacts: contacts}
toggleContacts=()=>{
this.setState({show:!this.state.show})
}
sort=()=>{
this.setState({contacts: [...this.state.contacts].sort(compareNames)})
}
render() {
return (
<View style={styles.container}>
<Button title="toggle names" onPress={this.toggleContacts} />
<Button title="sort" onPress={this.sort} />
<SegmentedControlIOS
values={['ScrollView', 'FlatList','SectionList']}
selectedIndex={this.state.selectedIndex}
onChange={(event) => {
this.setState({selectedIndex: event.nativeEvent.selectedSegmentIndex});
}} />
{this.state.show && this.state.selectedIndex === 0 &&
<ScrollView >
{this.state.contacts.map(contact=>(
<Row {...contact}/> ))}
</ScrollView>}
{this.state.show && this.state.selectedIndex === 1 &&
<FlatList
data={this.state.contacts}
keyExtractor = { (item, index) => index.toString() }
renderItem={renderItem}>
</FlatList>}
{this.state.show && this.state.selectedIndex === 2 &&
<ContactsList
contacts={this.state.contacts}>
</ContactsList>}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
// alignItems: 'flex-start',
paddingTop: Constants.statusBarHeight + 25,
},
row: {
padding:20,
},
});
const NUM_CONTACTS = 10
const firstNames = ['Emma','Noah','Olivia','Liam','Ava','William','Sophia','Mason','Isabella','James','Mia','Benjamin','Charlotte','Jacob','Abigail','Michael','Emily','Elijah','Harper','Ethan','Amelia','Alexander','Evelyn','Oliver','Elizabeth','Daniel','Sofia','Lucas','Madison','Matthew','Avery','Aiden','Ella','Jackson','Scarlett','Logan','Grace','David','Chloe','Joseph','Victoria','Samuel','Riley','Henry','Aria','Owen','Lily','Sebastian','Aubrey','Gabriel','Zoey','Carter','Penelope','Jayden','Lillian','John','Addison','Luke','Layla','Anthony','Natalie','Isaac','Camila','Dylan','Hannah','Wyatt','Brooklyn','Andrew','Zoe','Joshua','Nora','Christopher','Leah','Grayson','Savannah','Jack','Audrey','Julian','Claire','Ryan','Eleanor','Jaxon','Skylar','Levi','Ellie','Nathan','Samantha','Caleb','Stella','Hunter','Paisley','Christian','Violet','Isaiah','Mila','Thomas','Allison','Aaron','Alexa','Lincoln']
const lastNames = ['Smith','Jones','Brown','Johnson','Williams','Miller','Taylor','Wilson','Davis','White','Clark','Hall','Thomas','Thompson','Moore','Hill','Walker','Anderson','Wright','Martin','Wood','Allen','Robinson','Lewis','Scott','Young','Jackson','Adams','Tryniski','Green','Evans','King','Baker','John','Harris','Roberts','Campbell','James','Stewart','Lee','County','Turner','Parker','Cook','Mc','Edwards','Morris','Mitchell','Bell','Ward','Watson','Morgan','Davies','Cooper','Phillips','Rogers','Gray','Hughes','Harrison','Carter','Murphy']
// generate a random number between min and max
const rand = (max, min = 0) => Math.floor(Math.random() * (max - min + 1)) + min
// generate a name
const generateName = () => `${firstNames[rand(firstNames.length - 1)]} ${lastNames[rand(lastNames.length - 1)]}`
// generate a phone number
const generatePhoneNumber = () => `${rand(999, 100)}-${rand(999, 100)}-${rand(9999, 1000)}`
// create a person
const createContact = () => ({name: generateName(), phone: generatePhoneNumber()})
// compare two contacts for alphabetizing
export const compareNames = (contact1, contact2) => contact1.name > contact2.name
// add keys to based on index
const addKeys = (val, key) => ({key, ...val})
// create an array of length NUM_CONTACTS and alphabetize by name
export default Array.from({length: NUM_CONTACTS}, createContact).map(addKeys)
It looks like your component is triggering the render method multiple times. This happens mostly when you use setState method, since everytime your state changes the render method is triggered.
You may have 2 options to handle this:
1) Identify where your component is being rerendered and treat it if this behavior is unnecessary. You can use console.log to debug it.
2) You can avoid the random method to be called multiple times if you call it only in your componentDidMount method. Since componentDidMount is called only once in the component lifecycle, all functions inside of it will not be triggered when the component rerenders, only when it mount again. (I think this is the solution)
Let me know if it helps.

change style and property onTextChange - React Native

I just began playing around with React Native and I was trying to figure out how to disable and enable a touchableOpacity when the lengths of two inputTexts are > 0.
On my touchableOpacity I have it set to disabled={true} and it has a children Text with opacity set. When a user types into the textInput's I want to set disabled={false} and change the Text opacity to 1.
What's the best way to do this in react native?
Should i put ref's on them?
Should I use a className?
setNativeProps maybe?
If your constructor's state contains a flag for "isDisabled" and "textOpacity", then you can call setState in the onChangeText function that will change the state of isDisabled and textOpacity. The text component can use the opacity from the state's textOpacity and the touchableOpacity can use the state's isDisabled.
Ex:
`class foo extends Component {
constructor(props) {
super(props);
this.state = {
isDisabled: true,
textOpacity: 0.1,
text1: '',
text2: '',
};
onTextChange1(text) {
if (text.length > 0 && this.state.text2.length > 0) {
this.setState({isDisabled: false, textOpacity: 1, text1: text});
}
else {
this.setState({isDisabled: true, textOpacity: 0.1, text1: text});
}
}
onTextChange2(text) {
if (text.length > 0 && this.state.text1.length > 0) {
this.setState({isDisabled: false, textOpacity: 1, text2: text});
}
else {
this.setState({isDisabled: true, textOpacity: 0.1, text2: text});
}
}
render() {
return (
<View>
<TextInput onChangeText={(text) => this.onTextChange1(text)} value={this.state.text1}/>
<TextInput onChangeText={(text) => this.onTextChange2(text)} value={this.state.text2}/>
<TouchableOpacity disabled={this.state.isDisabled}>
<Text style={{opacity:this.state.textOpacity}}>Hello World!</Text>
</TouchableOpacity>
</View>
);
}
}
I have not tested the above, but I believe this is basically what you are looking for. We perform a check in the on text change to see if a certain condition is met, and depending on if it is, we change the parameters of the child components as you stated. Calling setState and modifying props are the only ways to trigger a rerender, so this is how in react-native we work with rerendering components.
if you want your opacity to be able to change depending on user moves, you need to set it(opacity) in state of your parent component.You can do this for example:
export class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { opacity: 0 }
}
render() {
Some components ....
<TextInput style={{opacity: this.state.opacity}} onChangeText={ () => this.setState({opacity: 1}) } ...other props here .../>
Some components ....
}
}
you can also apply other styles to your TextInput component.

Is there any React Native text editor component?

I am looking for a text editor component for React Native without WebView. I found only react-native-zss-rich-text-editor, but it uses WebView, which I think is terrible.
I hope to find something, that works with NSAttributedString and SpannableString for IOS and Android in a native way, like in Evernote, for example.
Evernote Android app text editor
Here is a TextArea component, that wraps TextInput and resizes it automatically, when you press "new line" button
import React, { PropTypes } from 'react'
import { TextInput } from 'react-native'
export default class TextArea extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
onChangeText: PropTypes.func.isRequired,
initialHeight: PropTypes.number,
isEditing: PropTypes.bool,
scrollViewRef: PropTypes.object,
}
static defaultProps = {
initialHeight: 40,
isEditing: true,
scrollViewRef: null,
}
state = {
height: this.props.initialHeight,
}
componentWillUnmount(){
this._isUnmounted = false
}
focus(){
this.refs.textInput.focus()
}
blur(){
this.refs.textInput.blur()
}
_contentSizeChanged = e => {
this.setState({
height: e.nativeEvent.contentSize.height,
}, () => {
if (this.props.scrollViewRef) {
setTimeout(() => {
if (!this._isUnmounted) this.props.scrollViewRef.scrollToEnd()
}, 0)
}
})
}
_onChangeText = text => {
this.props.onChangeText(text)
}
render(){
return (
<TextInput
ref = "textInput"
multiline
value = {this.props.text}
style = {{ height: this.state.height, color: 'black', flex: 1 }}
onChangeText = {this._onChangeText}
onContentSizeChange = {this._contentSizeChanged}
editable = {this.props.isEditing}
blurOnSubmit = {false}
/>
)
}
}