Getting updated state for save in react native [duplicate] - reactjs

I'm quite new to react native and i didn't found any solution to my problem yet so I'm asking here now. I'm trying to update the state of a value on an onPress event of a radio button and save it afterwards. The problem is that the save is getting the non updated value. I know setState is an async call and a forceUpdate is not a recommended solution(and don't work for me for some reason)
here is a sample:
import RadioForm, {
RadioButton,
RadioButtonInput,
RadioButtonLabel
} from 'react-native-simple-radio-button'
class SomeClass extends Component {
constructor(props) {
super(props)
this.state = {
buttonValues: [{label: "someValue1", value: 0}, {label: "someValue2", value: 1}],
someString: "someStringValue_false"
}
this.handleOnPress = this.handleOnPress.bind(this),
this.saveValue = this.saveValue.bind(this)
}
handleOnPress(value) {
if( value === 1 ){
this.setState({
someString: "someStringValue_true"
})
} else {
this.setState({
someString: "someStringValue_false"
})
}
}
saveValue() {
//no problem in this function tested already in other cases
}
render() {
return(
<View>
<RadioForm
radio_props={this.state.buttonValues}
initial={0}
formHorizontal={true}
labelHorizontal={true}
radioStyle={{paddingRight: 20}}
buttonColor={"red"}
selectedButtonColor = {"green"}
animation={true}
onPress={(value) => this.handleOnPress(value)}
/>
<Button
title={"save"}
onPress={()=> this.saveValue()}
/>
</View>
)
}
}
Behavior: the state updates only on the 2nd call

you can try yo use setState callback
setState({ name: "Michael" }, () => console.log(this.state));
// => { name: "Michael" }
to make sure that the state changes.

You are not binding the handlers with this properly in the constructor. It should be
constructor(props) {
/* --- code --- */
this.handleOnPress = this.handleOnPress.bind(this),
this.saveValue = this.saveValue.bind(this)
}

Do the next.
this.setState({
meter_reading_unit: "someStringValue_false"
}, () => {
console.log('meter_reading_unit ==> ', this.state.meter_reading_unit); // should be `someStringValue_false`
});
inside your next render update the value someStringValue_false should be available inside anywhere.

Related

how to avoid re-rendering when passing the object into value of context.provider using React context

In class component when we want to pass the object to the value of context provider using react context, we have a way to avoid re-rendering issue. Below are the codes
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
contextState: {
count: 0,
increment: this.increment
}
};
}
increment = () => {
this.setState({
contextState: {
...this.state.contextState,
count: this.state.contextState.count + 1
}
});
};
onChange = e => {
const { value, name } = e.target;
this.setState({ [name]: value });
};
render() {
return (
<CountContext.Provider value={this.state.contextState}>
<div style={styles}>
<input name="text" value={this.state.text} onChange={this.onChange} />
<div>Count: {this.state.contextState.count}</div>
<Container1 />
<Container2 />
</div>
</CountContext.Provider>
);
}
}
We put this.state.contextState to value of CountContext.Provider. So when user types anything in input element and will not cause <Container1 /> and <Container2 /> re-rendered. Here is the code sandbox: https://codesandbox.io/s/qqx1jqk8mj?file=/src/index.js:260-1105
I am tring to convert it into hooks. Here is the code sandbox https://codesandbox.io/s/affectionate-gauss-duk64?file=/src/index.js but the counter is not working properly. May I please know which part is wrong? thanks
In your hook component, you just need to use the functional setState approach.
setContextState(prevState=>newState)
In your code: https://codesandbox.io/s/admiring-shtern-g6oll?file=/src/index.js
const [contextState, setContextState] = useState({
count: 0,
increment: () => {
setContextState(prev=>({
...prev,
count: prev.count + 1
}));
}
});
The reason you need to do this is because the state value will never update because of the closure around it. contextState.count will always remain at 0 because it was the value when the state was originally set (0), and it won't change.

Passing functions to child components in React - this.props.handleChange is not a function?

Trying to create a simple todo list and I figure out how to pass the function from the parent component down to the child component without it throwing an error
App Component
class App extends React.Component {
state = {
todo: todoData.map(todo => {
return <TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
}),
count: 0,
}
handleChange = (id) => {
console.log(id)
}
render(){
return(
<div className="flex">
<Header />
<div className="todoList" >
{this.state.todo}
</div>
</div>
)
}
}
TodoItem component
class TodoItem extends React.Component {
render(){
console.log(this.props)
return(
<p className="todoItem" onClick={this.props.clickeds}>
<input type="checkbox" checked={this.props.todo.completed} onChange={() => this.props.handleChange(this.props.todo.id)} />
{this.props.todo.text}
</p>
)
}
}
I'm trying to mess with the onChange handler in the TodoItem component, but I keep getting the same error that this.props.handleChange is not a function
Todo just for reference
todoData = {
id: 2,
text: "Grocery Shopping",
completed: false
},
What am I doing wrong?
When I change the handleChange function to NOT an arrow function in the app component, it works. (handleChange(id)). If I change this function to an arrow function (handleChange = (id) => { } ) I run into this error.
I recommend you use ES6's class syntax for now (which I have added below) as this is how most React tutorials —including the official ones— are written.
The data structure you should keep in the state should be an Array of Todo Objects.
You don't need to keep the components inside the state, simply iterate them on render (and don't worry about its performance, React won't recreate the HTML dom by doing this, so the render will be very efficient).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos : todoData,
count : 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
const todos = this.state.todos.map(todo => (
(todo.id === id)
? Object.assign(todo, { completed: !todo.completed }
: todo
));
this.setState({ todos });
}
render() {
// ...
this.state.todos.map(todo => (
<TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
);
}
}
// todoData should probably be an array in case you want to include more by default:
todoData = [
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 2,
text: "Grocery Shopping",
completed: false
}
];
Be careful about mutating the state
The reason for the ugly and confusing map() and Object.assign() inside handleChange is because you cannot do a shallow copy, nor edit the array directly:
this.state.todos[i].completed = true // ! mutates the state, no good
Preferably Lodash's library or even better, an immutable library or memoized selectors would do the trick of setting a todo as completed in a much nicer fashion.
Without deep cloning or immutability, you would need to copy the object, clone it, then create a new array with the new object, and assign that array to the state.

ReactJS, Checkbox doesn't

I Have a ReactJS checkbox component. When onChange is called I can log the new state and see it changing, but it never actually re-renders the checkbox into the new state. So the ADD_ID action is never called. See code below:
class CheckBox extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: true
}
}
changing = (e) => {
this.setState(prevState => ({checked: !prevState.checked}), () => {
console.log(this.state.checked); // false
this.state.checked
? store.dispatch({ type: 'ADD_ID', id: this.props.id })
: store.dispatch({ type: 'REMOVE_ID', id: this.props.id });
});
}
render() {
return (
<label>
Include
<input onChange={this.changing} checked={this.state.checked} type='checkbox'/>
</label>
)
}
}
Is there a lifecycle hook that I have to call? I was under the impression that the component would re-render when either it's props or state changes, in this case, as shown by the console.log(this.state.checked), the state has changed, but the component doesn't re-render.
The event has already a checked property for you. You're doing it in a way that's a bit weird.
Change your function to something like:
handleChange = (e) => {
const isChecked = e.target.checked
if(isChecked){
store.dispatch({type:'ADD_ID', id:this.props.id})
} else {
store.dispatch({type:'REMOVE_ID', id:this.props.id})
}
this.setState(checked: isChecked)
}
It's however still strange that you're using at the same time internal state and Redux. You may want to rethink your approach here.
React docs on forms, which I recommend you to read in 5 min:
https://reactjs.org/docs/forms.html

Rerender not working after states updated

I have a parent component Rides which sends data to child component via props.
Here is the Child component:
class LazyLoader extends React.Component {
constructor(props){
super(props);
this.state = {
loaderState: this.props.loaderState
}
}
componentWillReceiveProps(nextProps){
console.log(`inside componentWillReceiveProps ${nextProps.loaderState}`);
if(this.props != nextProps) {
this.setState({
loaderState: nextProps.loaderState
}, () => {console.log(`Finished setState for ${nextProps.loaderState}`)});
}
}
render(){
console.log(`loaderState: ${this.state.loaderState}`);
return (
this.state.loaderState ? (
<View style={[styles.container]}>
<ActivityIndicator style={styles.spinner} animating={true} size={60} color='#fff'/>
</View>) : (<View/>)
)
}
}
And part of my parent component (here Rides) which send the updated data from its state to child's(here LazyLoader) prop:
export default class Rides extends React.Component {
constructor(props){
super(props);
this.handleRidePress = this.handleRidePress.bind(this);
this.navigateToRideDetails = this.navigateToRideDetails.bind(this);
this.state = {
flash: true
};
}
handleRidePress(rideId){
this.setState({
flash: true
}, () => {
console.log(`Begining handleRidePress for rideId: ${rideId}`);
if(doSomeTimeConsumingOperation){
// Time consuming operation goes here
}
this.navigateToRideDetails({rideId: rideId, pingData:pingData.slice(), rideData: rideDetails});
});
}
navigateToRideDetails(rideObj){
this.setState({
flash: false
}, () => {console.log(`navigateToRideDetails setState cb`); this.props.navigation.navigate('RideLog', rideObj);});
}
render(){
console.log(`flash: ${this.state.flash}`);
return(
<Gradient.FullGradient>
<Reusables.LazyLoader loaderState={this.state.flash}/>
<PRides rides={this.state.rideTiles} onDeletePress={this.deleteRide} navigation={this.props.navigation} onRideDetailPress={this.handleRidePress}/>
</Gradient.FullGradient>
)
}
}
When handleRidePress function is called it updates the flash state using setState() but child component doesn't get rerender. I tried using shouldComponentUpdate() in both component and returned true by default, but It doesn't work.
I think error is in the way you are comparing two objects here:
if(this.props != nextProps) {
....
}
This is not the correct way to check whether two objects are same or not, check the values:
if(this.props.loaderState != nextProps.loaderState) {
....
}
Check this answer How to determine equality for two JavaScript objects?
Check this snippet:
let a = {b: true};
let b = {b: true};
console.log(a == b); //false
I modified navigateToRideDetails to execute setState independently as react batches state updates that occur in event handlers and lifecycle methods. Thus, if we are updating state multiple times in a function handler, React will wait for event handling to finish before re-rendering. In my case, the flash value in state was getting reset after operation gets completed. Hence ActivityIndicator was not getting displayed.
Here is modified navigateToRideDetails function
navigateToRideDetails(rideObj){
setTimeout(() => {
this.setState({
flash: false
}, ()=>{
this.props.navigation.navigate('RideLog', rideObj);
});
}, 0);
}
This solves the issue :)

How to know if all the setState updates have been applied to the state in a React component?

I was reading the documentation about React setState, which says:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
Now I have a component like this:
class NoteScreenComponent extends React.Component {
constructor() {
super();
this.state = { note: Note.newNote() }
}
componentWillMount() {
this.setState({ note: this.props.note });
}
noteComponent_change = (propName, propValue) => {
this.setState((prevState, props) => {
let note = Object.assign({}, prevState.note);
note[propName] = propValue;
return { note: note }
});
}
title_changeText = (text) => {
this.noteComponent_change('title', text);
}
body_changeText = (text) => {
this.noteComponent_change('body', text);
}
saveNoteButton_press = () => {
// Save note to SQL database
Note.save(this.state.note)
}
render() {
return (
<View>
<TextInput value={this.state.note.title} onChangeText={this.title_changeText} />
<TextInput value={this.state.note.body} onChangeText={this.body_changeText} />
<Button title="Save note" onPress={this.saveNoteButton_press} />
</View>
);
}
}
What I'm wondering is, since setState does not update the state immediately, how can I know if the note I'm saving in saveNoteButton_press is the current version of the state? Is there some callback or something that I could poll to know if state has been fully updated?
What they are warning against is trying to do something in the same event loop.
method = () => {
this.setState({ note: 'A' })
saveNote(this.state.note) // <-- this.state.note will not have been updated yet.
}
or to setState using previous state:
method = () => {
let note = this.state.note // possible that `this.state.note` is scheduled to change
this.setState({ note: note + 'B' })
}
Since your user is going to be pushing the button after the setState scheduling, the state will have already been updated.
..but for theory's sake, let's imagine that somehow the input event and button happen in the exact same moment.. what would be the correct solution? If it was a single function call you probably wouldn't be using the new state since you already have the new note and the previous state.
method = (text) => {
let noteToSave = this.state.note + text // old state + new value
saveNote(noteToSave) // maybe this will fail
.then(response => this.setState({ note: noteToSave }))
.catch(err => this.setState({ error: 'something went wrong' }))
// optimistically update the ui
this.setState({ note: noteToSave })
}
but probably the most likely solution is to just pass what you want as an argument where you use it, rather than trying to access state which might be in a race condition, since render will happen after any state transitions.
method = (note) => {
noteToSave(note)
}
render() {
return (
<Button onPress={() => this.method(this.state.note)} /> <-- must be up to date here
)
}

Resources