useFocusEffect runs every time useState updates - react-native

I've been trying to use useState inside useFocusEffect. Following react-navigation I got something like this:
const [counter, setCounter] = useState(0);
useFocusEffect(
useCallback(() => {
console.log(counter);
}, [counter]),
);
Now the problem is that every time counter updates, useFocusEffect fires. What I want is for it to fire only when screen comes into focus. Now I've also tried doing this with navigation focus listener:
useEffect(() => {
const onFocus = navigation.addListener('focus', () => {
console.log(counter);
});
return onFocus;
}, [navigation, counter]);
It works, well partially. While the onFocus function is performed only when screen comes into focus, useEffect fires every time counter updates. Same thing happens when using redux-toolkit slices. How can I prevent this behaviour?
Update
I should add that removing counter from dependency array prevents it from updating in subsequential runs. So I will rephrase the question. Is there a way to either fix useCallback by preventing it from firing every time counter updates or fix useEffect so that it fires only on focus with counter updated?

You included counter in the dependency array of your useEffect. This tells the useEffect to run every time a change is made to counter. See the docs for useEffect here: https://reactjs.org/docs/hooks-reference.html#useeffect

You need to change your useEffect to this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log(counter);
});
return unsubscribe;
}, [navigation]); // remove `counter` from your dependency array
The problem you had was your counter value was in the dependency array of your useEffect dependency array. This means that every time your counter value changes the callback inside your useEffect will run which is obviously undesirable.
You most likely followed CRA's default linting rules where they give you a warning to add items to the dependency array, I'd highly recommend turning that off in your linting rules.

Related

Trigger UseEffect whenever i switch tabs in React Native Tab Navigator

I have implemented react native tab navigator and added 4 screens to it.
I post some record to api in the second screen and i want to have the updated record in the 4th screens where i am getting updated records..
Useeffect only gets targeted only once, and when i put something in it's argument it gives me strange behavior.
I want useeffect to reload and call the api to get latest items in the 4th screen without putting anything in it's arguement(empty argument)
Any help would be highly appreciated.
Try doing this ;
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
makeApiCall();
});
return unsubscribe;
}, [navigation]);
Get navigation in component's arguments(destructuring)
like below;
const My4thTab = ({ navigation }) => {
}
This way useEffect will trigger only once, every time you come on this screen
but make sure to clear the previous state where you store your data, otherwise, there could be a record duplication.
Hope it helps :)

React Native console.log old value useState

I'm having trouble with React Native showing wrong value for me. I wan't to show the value after an useState update. My goal is to pass the value to the parent component but right now it passes the opposite value (true when switch is off). What do I have to do to console.log the right value after a useState update?
Watch image for example here
The useState hook is somewhat asynchronous (although you cannot wait for it).
Try using a useEffect:
useEffect(() => {
console.log(isEnabled)
}, [isEnabled]) // Array of dependencies: when any of these value changes, the function in the useEffect will re-run
More information here:
https://dev.to/shareef/react-usestate-hook-is-asynchronous-1hia
https://javascript.plainenglish.io/why-you-shouldnt-always-use-usestate-658994693018
The Change function will always "see" the state value that existed at the time of running the function. This is not because of asynchronicity per se (state updates are actually sync) but because of how closures work. It does feel like it is async though.
The state value will properly update in the background, but it won't be available in the "already-running" function. You can find more info here.
The way I see your handler implemented though:
const handleChange = () => {
setIsEnabled(!isEnabled) // you do not need updater function, you can directly reference the state
triggerParentMethod(!isEnabled); // then you can also directly call the parent function here
}
I recommend this as this way you will notify the parent immediately on user click instead of waiting for the state to be set and then notifying the parent in the next render cycle (in the effect), which should be unnecessary.
State updates in React are asynchronous, meaning that React does not wait for the state to be updated before executing the next line of code. In your case, the state update setIsEnabled(...) is not finished before console.log(isEnabled) is run, and therefore it returns the old value.
Just put the console.log(isEnabled) outside the function for it to print the update correctly. The component SetupSwitch is re-rendered when the state isEnabled is updated, which means it prints the console.log of the updated variable again.
...
console.log(isEnabled);
const Change = () => {
...
You will have to implement useEffect to view the changes.
useState is an asynchronous function it will go to the callback queue, meanwhile, the value will be consumed, so you need to trigger the action whenever the count changes. (for this example)
const [count, setCount] = useState(0);
useEffect(() => console.log(count), [count]);
setCurrPos(preevCount => prevCount + 1);

Should I use useCallback when I navigate to a screen?

I want to ask you, should I use "useCallback" when I navigate to any screen or is that unnecessary?
...
const navigateToScreen = useCallback(() => {
navigation.navigate('screen')
}, []);
And if I should use "useCallback" then what I have to do in the array ?
No, you should simply navigate to the other page onClick etc... without UseCallback.
UseCallback is useful when you recreate a function/object on every render, which can be very expensive (if it is a complex calculation, or very large object), and slow your app down a lot. You can pass a variable into the empty array, telling UseCallback to run every time this variable changes.

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 Hooks and useEffect – best practices and server issues

I am using React Native with functional components. componentDidMount() etc. are not available in functional components, instead I use Hooks. But Hooks don't act like lifecycle methods. I am wondering what the best practices are.
Assumed that we have a function like this one:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
// some code inside this function which is called on every component update
}, [])
server.asyncCall().then(data => {
setSomeHook(data)
})
return (<View>
{someHook ? (<Text> `someHook` was assigned </Text>) : (<Text> `someHook` was not assigned, display some ActivityIndicator instead</Text>)}
</View>)
}
Where to place server.asyncCall()? Inside or outside of useEffect?
I think you have a misunderstanding here. The convention is that all the fetching data is going to be placed inside the componentDidMount lifecycle method. React useEffect hook can replace this easily by placing an empty array of dependencies, which means you can place that call inside the useEffect you already have.
Unlike you mention in your code comment, this hook won't be triggered on each component update. It will be only be triggered once the component is being mounted. So, you should be able to do it as follows:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
server.asyncCall().then(setSomeHook)
}, [])//only triggered when component is mounted.
In the future, you might want to check the rules of the hooks.