I've tried a few methods to get setState() to update the value of state. Currently the text in the <TextInput> changes, but the value in this.state doesn't change.
I have the console.log in the right place, I've tried writing external functions, I've messed around with the variable's names but nothing seems to work.
import * as React from 'react';
import { View, Text, TextInput, TouchableHighlight, Dimensions, StyleSheet } from "react-native";
import PropTypes from "prop-types";
class EditNote extends React.Component{
constructor(props){
super(props)
this.state = {
title: '',
text: '',
id: ''
}
}
// TODO: Change textboxes to match the props from the NoteList
static getDerivedStateFromProps(props){
return(
{...props.route.params}
)
}
render(){
return(
<View style={s.container}>
<View style={s.titleContainer}>
<Text style={s.titleText}>Edit Note</Text>
<View style={{flex: 1}}/>
</View>
<View style={s.inputContainer}>
<TextInput
style={{...s.input, ...s.titleInput}}
autoCapitalize='words'
keyboardAppearance='dark'
placeholderTextColor='#DDD'
onChangeText={(title) => { this.setState({title: title}, () => console.log(this.state)) }}
defaultValue={this.state.title}
/>
<TextInput
style={{...s.input, ...s.textInput}}
autoCapitalize='sentences'
keyboardAppearance='dark'
placeholderTextColor='#DDD'
multiline
onChangeText={(text) => { this.setState({text: text}, () => console.log(this.state)) }}
defaultValue={this.state.text}
/>
</View>
<View style={s.buttonContainer}>
<TouchableHighlight
style={s.backButton}
onPress={() => this.props.nav.navigate('NoteListView')}
underlayColor='#300030'
>
<Text style={s.buttonText}>Cancel</Text>
</TouchableHighlight>
<TouchableHighlight
style={s.addButton}
onPress={() => {
console.log(this.state.note)
this.props.nav.navigate('NoteListView', {note: this.state, mode: 'edit'})
}}
underlayColor='#300030'
>
<Text style={s.buttonText}>Edit</Text>
</TouchableHighlight>
</View>
</View>
)
}
}
export default EditNote
I just realized that this is a problem with two parts.
The first problem is that props.route.params is unaffected by subsequent render() calls. This means that even if you re-render the component, the same initial properties are used.
The second is getDerivedStateFromProps(). Every time the render function is called it calls getDerivedStateFromProps() right before it which sets the state to the initial route parameters.
This problem can be fixed by:
Clearing the initial route parameters in the render function after their initial use. Something a little like this at the beginning of the render()function will work. this.props.route.params = undefined
Using an if statement and a variable in state to regulate when the props should update the state.
Refactor the code to make use of the props
Option 3 is how things should be correctly done but the best solution depends on how your code works.
I found that the convention to changing css of TextInput is by changing the component's state using onFocus and onBlur props.
I learned it from this post.
However, though successful I had to do some ugly workaround:
I moved the state management to my form component instead of putting it in my TextInput wrapper
I had to implement a hack componentDidUpdate() to refocus the TextInput, something like this
componentDidUpdate() {
this.props.isActive ? setTimeout(() => this.input.focus(), 100) : null
}
Now everything is working as expected, except that the keyboard would flicker upon moving to the next TextInput by using onSubmitEditing props.
Here's my code snippet related to this case:
in my form component I instantiate the input wrapper component like so within a loop, i.e. fields.map()
<AuthTextInput
ref={ref => inputRefs[key] = ref}
key={key}
label={label}
onFocus={() => this.setState({ currentInput: key })}
isActive={this.state.currentInput === key}
onSubmitEditing={() => (idx + 1) < fields.length && fields[idx + 1].type !== 'selection' ? inputRefs[fields[idx + 1].key].focus() : this.setState({ currentInput: null })}
blurOnSubmit={(idx + 1) === fields.length || fields[idx + 1].type === 'selection'}
returnKeyType={(idx + 1) === fields.length || fields[idx + 1].type === 'selection' ? 'done' : 'next'}
autoCapitalize={autocaps}
secureTextEntry={secureTextEntry}
placeholder={placeholder}
keyboardType={keyboard}
containerStyle={[{ width: orientation() === 'landscape' ? 0.5 * windowWidth() : windowWidth() * 0.7, height: normalize(70), marginVertical: normalize(10) }]}
leftIcon={<Image style={{ width: normalize(50), height: normalize(50), marginTop: 25 }} source={fieldIcon} />}
onChangeText={(text) => this.onChange(key, text)}
value={this.state[key] ? this.state[key]['value'] : ''}
error={this.state.error[key]}
/>
The content of AuthTextInput is like so:
import React, { Component } from 'react';
import { View, StyleSheet, TextInput, Text } from 'react-native';
import { Input } from 'react-native-elements';
import { isEmpty } from '../utils/validate';
import { windowWidth, fontSize, fontFamily, normalize, color } from '../theme/baseTheme';
import IconWrapper from './IconWrapper';
const styles = StyleSheet.create({
container: {
flex: 1
},
inputContainer: {
borderBottomWidth: 0,
},
inputStyle: {
fontSize: fontSize.regular + 2,
fontFamily: fontFamily.bold,
paddingLeft: normalize(15),
borderBottomWidth: 1
},
errorStyle: {
color: color.red,
fontSize: fontSize.small - 4,
fontFamily: fontFamily.bold,
fontWeight: 'bold',
marginLeft: normalize(75)
},
focusedContainer: {
borderWidth: 1,
borderColor: color.light_blue,
borderRadius: 8
}
});
class AuthTextInput extends Component {
constructor(props) {
super(props);
this.state = {
secureText: this.props.secureTextEntry,
}
}
componentDidUpdate() {
this.props.isActive ? setTimeout(() => this.input.focus(), 100) : null
}
focus() {
this.input.focus();
}
render() {
const { secureTextEntry, value, containerStyle, isActive } = this.props;
return (
<Input
{...this.props}
ref={ref => this.input = ref}
disableFullscreenUI={true}
secureTextEntry={this.state.secureText}
containerStyle={[styles.container, containerStyle, isActive ? styles.focusedContainer : null]}
inputContainerStyle={styles.inputContainer}
inputStyle={styles.inputStyle}
rightIcon={
secureTextEntry && value !== '' ?
this.state.secureText ?
<IconWrapper name="visibility" size={20} color={color.light_grey} style={{ justifyContent: 'center' }} onPress={() => this.setState({ secureText: false })} />
:
<IconWrapper name="visibility-off" size={20} color={color.light_grey} style={{ justifyContent: 'center' }} onPress={() => this.setState({ secureText: true })} />
:
null
}
errorMessage={!isEmpty(this.props.error) ? this.props.error : null}
errorStyle={[styles.errorStyle, this.props.errorStyle]}
/>
);
}
}
export default AuthTextInput;
The problem I found mainly lies in the first snippet, where I wrote onFocus={() => this.setState({ currentInput: key })} which re-renders the form component and somehow removing the focus. Hence, the refocusing in AuthTextInput's componentDidUpdate.
I thought when my form component re-renders, all the old AuthTextInput are getting destroyed, so I tried doing autoFocus={this.props.isActive} too in AuthTextInput, but it wasn't successful because componentDidMount itself was never called, which implies they didn't get destroyed, just updated.
This made me wonder, if it didn't get destroyed and remade, what made the focus went away?
I mean, I did the same thing to set the value by doing this onChangeText={(text) => this.onChange(key, text)} and calling setState there. and in that case, the component didn't lose focus.
Anyhow, I would love to know if anyone can either:
show me what made the focus go away OR
using my workaround above to refocus after setting the form state, prevent keyboard from flickering (dismissing and reappearing within a short interval).
Thanks in advance!
UPDATE:
I found that in my TextInput wrapper, when it is updated the FIRST TIME using setState from onFocus callback, it always calls onBlur right away, which is weird and I couldn't find anything that calls onBlur the first time both in mine or the library react-native-element's code.
UPDATE2:
Turns out even when I already disabled onBlur from being called and updating the component once again right after onFocus, the input still loses focus, and I have no idea what's causing it. I checked both the form's and the input's component didupdate and they didn't fire, odd...
So I guess I just have to find out what's stealing the focus when my onFocus updates the input state
FINAL UPDATE:
INTERESTING FIND!!! isActive ? styles.focusedContainer : null this is the culprit, for some reason this is triggering blur() event. None of the answers below can recreate this because none of them modifies the css styling of this component.
I think this happens because containerStyleProps is passed as the parent's View component props to the actual TextInput component by react-native-elements. This causes rerendering at that level causing TextInput to rerender too.
But problem persist, how should I go about solving this issue if simply updating my style triggers the TextInput rerendering? Is there any way to tap into the TextInput's shouldUpdateComponent() hook?
Thanks again for any opinion posted here that helped me gain this insight
I tried to implement the skeleton of the problem. In the below example the input does not loses the focus, works as expected.
class App extends React.Component {
state = {
isFocus: 'no focus'
}
handleFocus = () => {
this.setState({ isFocus: 'got focus' })
}
handleBlur = () => {
this.setState({ isFocus: 'no focus' })
}
render() {
return <input onFocus={this.handleFocus} onBlur={this.handleBlur} value={this.state.isFocus} />;
}
}
ReactDOM.render(<App />, document.getElementById('root'))
input:focus {
box-shadow: 5px 5px 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I had the same problem, specifically on Android - iOS seems to be unaffected at the time of writing this.
onBlur gets called when the TextInput or it's container is rendering differently based on State variables... as you noted, in your case it was the following:
isActive ? styles.focusedContainer : null
To get around this and keep your setState in onFocus and onBlur, and still style your component beautifully based on state, I managed to fix this issue by separating focus styling from the input, and make it styled on a sibling view instead of a parent view or the input view itself:
<View>
<View style={ isActive ? styles.focused : styles.unfocused } />
<Input/> // <-- do not use state to style this.
</View>
This will avoid the onBlur getting called, however, you will obviously need to correctly position the "un/focus styled" View behind the Input view to line them up.
For this, use styles for position='absolute', width='100%', height='100%'
E.g:
const styles = StyleSheet.create({
inputView: {
width='100%',
height='100%'
}
focusView: {
position: 'absolute', // <--- this is the key
width: '100%',
height: '100%'
}
}
I made a button component which I render in my Parent component. Been stuck for a while now on trying to fire a function on the onPress event of this child component that is used in my Parent.
I've been looking through some of the recommended questions and answers but I need some specific advice.
I simplified my code as much as possible, please have a quick look.
Thanks in advance!
// PARENT COMPONENT
export class Home extends Component {
constructor(props) {
super(props);
this.onPress = this.onPress.bind(this);
}
onPress = () => {
console.log("Hey");
};
render() {
return (
<View style={styles.container}>
<PrimaryButton text={"Sign up"} onPress={this.onPress} />
</View>
);
}
}
// CHILD COMPONENT
const PrimaryButton = ({ text }) => {
return (
<TouchableOpacity style={style.container} >
<Text style={style.text}>{text}</Text>
</TouchableOpacity>
);
};
export default PrimaryButton;
You need to pass onPress to TouchableOpacity as a prop. I don't know the props for TouchableOpacity but should be bound as either onPress or onClick. Event handlers always need to be passed to the root component (or as close to it as you can get, ie TouchableOpacity is from a 3rd party). Most, if not all, 3rd party components will have props for the proper events.
// CHILD COMPONENT
const PrimaryButton = ({ text, onPress }) => {
return (
<TouchableOpacity style={style.container} onClick={onPress} >
<Text style={style.text}>{text}</Text>
</TouchableOpacity>
);
};
i just start to learn react native i want to make ui like this,
i want the buttons on the image work like radio button, so user just can choose a button and change the button style choosed by user, and bellow is my code
class ButtonOption extends Component {
constructor(props) {
super(props);
this.state = {
data : [],
active : 0
};
}
loadButton(temp){
let result = [];
for (let i =0;i<temp.length;i++){
if(i==this.state.active){
result.push(
<View key={i} style={buttonOption.main}>
<TouchableOpacity style={buttonOption.tabActive} onPress={()=>{this.setState({active:i});alert(this.state.active)}}>
<Text style={buttonOption.labelActive}>{temp[i]}</Text>
</TouchableOpacity>
</View>)
}else{
result.push(
<View key={i} style={buttonOption.main}>
<TouchableOpacity style={buttonOption.tab} onPress={()=>{this.setState({active:i});alert(this.state.active)}}>
<Text style={buttonOption.label}>{temp[i]}</Text>
</TouchableOpacity>
</View>)
}
}
return result;
}
componentDidMount(){
this.setState({data:this.loadButton(this.props.arrButton)})
}
render() {
return (
<View style={{flexDirection:'row',padding:10}}>
{this.state.data}
</View>
);
}
}
export default ButtonOption;
on code above i was try to make button with looping, in looping function i use active as a state for check active button, and add function onPress so everytime user click button, active state will change, but when i run and choose button the active state won't change, what is wronge with my onPress code, can someone help please?
I think I might know the answer, You have generated the view using this.loadButton and saved it in this.state.data. This is done one time only. when the render method is called. It is expecting a new view based on states but this.state.data is not changing since you create it in componentDidMount. What you need to do is to call this.loadButton(this.props.arrButton) in render method. this will create new view every time render method is called.
render() {
return (
<View style={{flexDirection:'row',padding:10}}>
{this.loadButton(this.props.arrButton)}
</View>
);
}
This might do it, If you still have any error, please report.
I'm trying to implement React Native's CheckBox component but can't seem to get it unchecked after changing its value the first time- it's perpetually checked. I understand that the value prop must be set for it to reflect user actions, so I'm trying to setState onChange... what am I getting wrong? :/
export const ReviewCardCheckBox = (props) => {
this.state = { checked: false };
return (
<View style={styles.sectionStyle}>
<View style={{justifyContent: 'flex-start', flex:1}}>
<Text style={styles.fontStyle}>{props.option}</Text>
</View>
<View style={{flexDirection: 'row', justifyContent: 'flex-end', flex:1}}>
<CheckBox onChange={ () => this.setState( {checked: !this.state.checked} )}/>
</View>
</View>
)
};
Thanks for the help!
What you have is a stateless functional component. The purpose of these components is just to receive props and do something with them.
Learn more about the difference here
This function is a valid React component because it accepts a single “props” (which stands for properties) object argument with data and returns a React element. We call such components “functional” because they are literally JavaScript functions.
For you to have state you must do
class ReviewCardCheckBox extends React.Component {
constructor(props) {
super(props);
this.state = { checked: false };
}
render() {
// do what you want.
}
}
You are declaring a Stateless component (a function) so this Component can't have state, as well as it can't setState.
Declare the Component as class instead.
export class ReviewCardCheckBox extends React.Component{
constructor(props){
super(props);
this.state = {chacked: false};
}
render(){
return (
//do something here
);
}
}