Why use prevState to replace a list value React JS - react-native

I just wanted a clarification on this
state = {
foodList: [],
currentFoodItem: null,
}
onFoodsReceived = (foodList) => {
console.log(foodList);
this.setState(prevState => ({
foodList: prevState.foodList = foodList
}));
}
In the above code it uses a prevState to replace a list value when u can use the code below. Why some uses prevstate while others don't to replace a list value
onFoodsReceived = (foodList) => {
console.log(foodList);
this.setState({
foodList: foodList
}));
}

It is used when we want to override the current state with the last state's parameters.
From React docs :
According to the React docs "React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state."
"To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument"
Sources.

Related

React navigation params passing too slow

I am working on a screen which takes data through route.params.
const {
prompt,
isUpscaling,
imageToBeUpscaled,
useDefaultPrompts = true,
isChallenge = false,
challengeData = {},
isCommunityPage,
} = route.params;
When navigating to this page, the params first being passed are actually the previous values. However, upon saving the file(thereby updating the state) the expected params load in.
I am using the params' data in a focusEffect function:
useFocusEffect(
useCallback(() => {
setIsLoading(true);
setSafeAreaBackgroundColor("#1a1a1a");
generateImage();
}, [])
);
However, the original data being passed to this function are the old values. In short, the values being used are the previous route.params value from an old screen and not the new ones being passed in. The new params are being passed in but (I'm assuming) are not loading before the useFocusEffect is called. This results in the function using old data rather than new data.
How can I have the route.params data be updated when used in the useFocusEffect function?

React-Native - useEffect causes infinite loop

I am trying to show some dynamic content in my component but somehow useEffect causes a infinite loop.
What can be the problem?
useEffect(() => {
retrieveLocalData('following').then((contacts) => {
setLocalData(JSON.parse(contacts));
});
}, [getLocalData]);
async function retrieveLocalData(key) {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
console.log('test'); // infinite
Code: https://codepen.io/eneskul/pen/OJWEgmw
Updated Answer
The infinite loop is a result of the useEffect hook updating the same value that is triggering the hook to run in the first place.
Here's a simple example to illustrate the problem:
const [value, setValue] = useState({ foo: 'bar' });
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
const newValue = JSON.parse(result);
// `newValue` is a new object, even if its content is identical to `value`.
setValue(newValue);
});
}, [value]);
In this example, when value is set, it causes the useEffect hook to execute, which will asynchronously update value with a new object, which will cause the useEffect hook to execute again, and so on. Even though the contents of the objects are identical, the JSON.parse call creates a new object with a new reference.
You can prevent the infinite loop by doing a deep equality check of the two objects before updating the state. Using something like Lodash's isEqual function makes this pretty easy.
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
setValue((prev) => {
const newValue = JSON.parse(result);
// Do a deep comparison and only update state with new object if content is different.
return isEqual(prev, newValue) ? prev : newValue;
});
});
}, [value]);
In this example, the reference to value will only change if the contents of the objects are different.
However, this only explains what the problem is. I'm not sure what the right solution is for your problem, since it's not clear why the component only needs to load data from local storage into state when the state changes, but the state is only updated when it loads from local storage. There seems to be a "chicken or the egg" problem here. It feels like there should be something else that should trigger loading data from local storage into state, other than the data that was just loaded from local storage into state.
Previous Answer
The likely culprit here is getLocalData in the dependency list of the useEffect hook. If that is not a stable reference (i.e. the reference changes on each render), then it will cause the useEffect hook to execute, which will then trigger a state update, which will trigger a render, which will cause useEffect to execute again, which starts the whole thing over again.
In the sample code, it's not clear where getLocalData comes from. Wherever it comes from, you might consider wrapping it with the useCallback hook to create a stable reference. If it's just a typo and meant to be retrieveLocalData, then that is definitely the issue. Because retrieveLocalData is declared inside the component's render function, it will create a new instance of the function (with a new reference) on each render.
I would just move it inside the useEffect hook and eliminate the dependencies.
useEffect(() => {
AsyncStorage.getItem('following')
.then((contacts) => {
setLocalData(JSON.parse(contacts));
})
.catch((error) => {
console.log(error);
});
}, []);

React Native Multiselect

I am new to React Native
I am trying to create a multiselect view where user can select and deselect the items and then the selected items should pass back to the previous container and when user comes back to the next view the selected items should be checked.
I am trying to implement but getting the issue it is not updating data accurately. It shows only 1 selected item when I came back again to the screen.
Can anyone tell me the best way to do that or if there is any tutorial.
Should I do it with Redux or using react native?
Any help would be appreciated!!
Thanks!!
I believe the issue you describe is due to the following:
In componentDidMount you are calling updateItemWithSelected in a loop. This updateItemWithSelected call is both overwriting the checked attributes for all of the arrayHolder values on each call and also not using the updater function version of setState, so the later call of the loop may overwrite the earlier calls since setState is async and batched. If you are not using updateItemWithSelected elsewhere you should simplify componentDidMount to:
componentDidMount() {
const selectedTitles = {};
const { state } = this.props.navigation
const params = state.params || {};
if (params.data.length){
params.data.forEach((element) => {
// create a map of selected titles
selectedTitles[element.title] = true;
})
}
const arrayHolder = this.array.map(item => {
// map over `this.array` and set `checked` if title is in `selectedTitles`
return {...item, checked: !!selectedTitles[item.title]};
});
this.setState({ arrayHolder });
}
and delete updateItemWithSelected.

Why I can't read a state in react native?

I have the following code:
export default class Testing extends Component {
state = ({
data: []
});
componentDidMount() {
this.setState({
data: this.props.values
});
console.log(this.state.posts); //prints empty but if I do
console.log(this.props.values); //prints the array correctly
}
Where is the error since I can print the props not the state?
Thanks
You're not storing anything in this.state.posts. Your initial state only contains data.
Also when you construct your initial state you should do it like this:
state = {
data: []
}
You do not need the ( ) around it.
If you are wanting to print a value from state as soon as you have stored it you must use the callback functionality of state. This is due to the fact that setState is asynchronous and takes time to set the value. Currently you are trying to read the value before it has been set, use the callback functionality like below.
this.setState({
data: this.props.values
}, () => console.log(this.state.data));
Here are some great articles on setState.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
you don't need the ( ) when you set the initial state because it is an object.
export default class Testing extends Component {
state = { //remove (
data: []
}; //remove )
Also worth noting, setState is an async function. You will not be able to getState directly after setState.
In order to get the state right away, you would provide a callback to setState() https://reactjs.org/docs/react-component.html#setstate

GraphQL query using React Native AsyncStorage

I'm trying to build an component, using React Native and Apollo Client that execute the following Query:
query getTable($tableId:String!){
getTable(id:$tableId){
users{
name
imageURL
}
}
So, as you the query above have a variable called tableId and that value is stored using the AsyncStoragemethod. Is there a way that I can get the value from the AsyncStorage and use it as query variable.
I tried to do the following but it didn't work:
graphql(Query, {
options: {
tableId: await AsyncStorage.getItem('table'),
},
})(MyComponent);
In my opinion, the preferred way of doing this would look like this:
Call AsyncStorage.getItem() in a parent component and then pass the result down as a prop to MyComponent. As illustrated in the docs, options can be a function instead of an object, in which case it gets the component's props passed to it as its first argument. So, assuming the prop is called tableId, your HOC would look like this:
graphql(Query, { options: ({ tableId }) => ({ variables: { tableId } }) })
Alternatively, you could set up the query with some default value:
graphql(Query, { options: { variables: { tableId : 'foo' } } })
You would call AsyncStorage.getItem() in your component's componentDidMount() method and assign the result to your component's state. Your component will have a data prop available to it when it renders. You can call this.props.data.refetch({ tableId: this.state.tableId }) from your render function to force the query to update with the newly available id.
I think that's a lot less clean than the first option, and will require additional logic to keep your component from calling refetch or rerendering unnecessarily... but it should still work if for some reason you don't want to change the parent component.