useFocusEffect doesn't update setstate - react-native

I'm trying to use useFocusEffect to setState.
but I can't update state...
How can i fix this?
const [isFocuse, setIsFocuse] = useState(true);
useFocusEffect(
React.useCallback(() => {
setIsFocuse('sample1');
console.log('mount', isFocuse); // mount true
return () => {
setIsFocuse('sample2');
console.log('unmount', isFocuse); // unmount true
};
}, []),
);

I know its a old question but for anyone looking for solution.
According to React navigation docs on useFocusEffect.
You need to add state you need to change as a dependency to your useCallback hook.
So correct code will be.
`
const [isFocuse, setIsFocuse] = useState(true);
useFocusEffect(
React.useCallback(() => {
setIsFocuse('sample1');
console.log('mount', isFocuse);
return () => {
setIsFocuse('sample2');
console.log('unmount', isFocuse);
};
}, [isFocuse]));`
And that will work.
PS- setState or its hooks equivalent is async task so console.log() may run before state is updated will result is incorrect values of log in this case. You can use useEffect hook as with isFocuse as dependency to log the change in state

Related

useState in React Native get data of previous state

I have one state
const [data, setData] = useState("");
And 2 useEffects that call in parallel when component renders
useEffect(() => {
socket.on("message",()=>{
console.log(data)
})
}, [socket])
useEffect(() => {
const res = getDataFromServer()
setData(res.data)
}, [isLoading])
2nd useEffect get data from server and set state but when socket arrive in first useEffect data is on initial state that is empty. How can I get updated state data in first useEffect when socket arrives. If I set data as dependency to first useEffect then socket event is reinitialized and callback is calling multiple times.
You can return a function in useEffect to clean unnecessary handlers / event listeners.
Effects with Cleanup - React Docs
In this function you can use the offAny method of socket.io client to remove previous listener.
useEffect(() => {
const currentListener = socket.on("message",()=>{
console.log(data)
});
return () => {
socket.offAny(currentListener);
};
}, [socket, data]);
This might help
React.useEffect(() => {
// This effect only executes on the initial render so that we aren't setting up the socket repeatedly.
socket.on("message",()=>{
console.log(data);
})
return () => {
socket.off('message',() => {});
}
}, []);

How to use addListener in useEffect

I want to run method when focus screen, i use this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log(
'test'
);
});
return unsubscribe;
}, [navigation]);
but it doesnt work. it gives an error like this :
*
An effect function must not return anything besides a function, which
is used for clean-up. You returned: [object Object]
also even i dont return anything, console.log(
'test'
) doest work
I am using navigation V4
Is this working?
import { useFocusEffect } from '#react-navigation/native';
......
useFocusEffect(useCallback(() => {
......
console.log(something)
}, [something]));
//-------
If not check if react navigation is configured correctly.
UPDATE
In React navigation 4.x you will have to follow one of the methods in this guide https://reactnavigation.org/docs/4.x/function-after-focusing-screen/
For useEffect to work properly, the flow is following:
in the square brackets in the end you add a variable which triggers the action. In your case it only triggers on the firs run, and on navigation variable change
you should run your function within useEffect. You have only defined a constant in the body of useEffect, but you never run it.
optionally you may return a function in the end of a run. This function is triggered only when the component unmounts, and used to avoid memory leaks.
Based on this: I'm not sure what are you trying to achieve (unclear from your original post), but this may be what you want:
useEffect(() => {
navigation.addListener('focus', () => {
console.log(
'test'
);
});
const unsubscribe = () => navigation.removeListener('focus'); // !!! I'm not sure about this one, check the docs how to unsubscribe !!!
return unsubscribe;
}, [navigation]); // << triggers useEffect
Assuming you are using the latest version of react-navigation you must the use-focus-effect.
https://reactnavigation.org/docs/use-focus-effect/
Your code should be updated as mentioned below
useFocusEffect(
useCallback(() => {
const unsubscribe = () => {
console.log("test");
}
return () => unsubscribe();
}, [userId])
);

Eslint hook refactor useEffect

I create useEffect like that. I want useEffect listen useToken and when I login and set token it will fetch some data
useEffect(() => {
if (userToken) {
setTimeout(() => {
fetchDataWishlistShow();
fetchCart();
}, 500);
}
fetchCategory();
}, [userToken]);
But Eslint of hook automatic add some function to Effect
useEffect(() => {
if (userToken) {
setTimeout(() => {
fetchDataWishlistShow();
fetchCart();
}, 500);
}
fetchCategory();
}, [fetchCart, fetchCategory, fetchDataWishlistShow, userToken]);
Why it do that. I think it wrong but anyone can explain for me?
I guess you installed "eslint-plugin-react-hooks" and enable it.
useEffectis designed the way that which ever you use in you useEffect you are recommended to add it it dependencies array list EXCEPT some things that React guarantee that they are not changed every each re-render such as: dispatch of useReducer, setState form const [state, setState] = useState() or functions or variables imported from other files.
You can turn off the rule but you SHOULD NOT do that because you did not solve the source of the problem. E.g
const [cartStatus, setCartState] = useState("all") // assume it can be "all", "pending"
const fetchCart = () =>{
fetch(`endpoint/cart/${cardStatus}`)
}
useEffect(() => {
if (userToken) {
setTimeout(() => {
fetchDataWishlistShow();
fetchCart();
}, 500);
}
fetchCategory();
// This case you alway fetch all item in cart
}, [userToken]);
To fix issue above you saying you saying, yeah we can just add fetchCart into dependency list, but it'll cause infinite re-render, you need to wrap fetchCart by useCallback or move fetchCart into useEffect. Because you call the function inside setTimeout, you might want to clean the useEffect
const [cartStatus, setCartState] = useState("all") // assume it can be "all", "pending"
useEffect(() => {
const fetchCart = () =>{
fetch(`endpoint/cart/${cardStatus}`)
}
let id = null
if (userToken) {
id = setTimeout(() => {
fetchDataWishlistShow();
fetchCart();
}, 500);
}
fetchCategory();
return () => clearTimeout(id);
}, [userToken]);
This article is written by Dan Abramov is a good resource to look at and deep dive into how useEffect works and why you should follow the recommended way.
You might saying that "No no, I am only have api call when the component mounted, That's it". But when your project grown, and you components become complicated, it's hard to remember that, who's know that your requirements might be changed at some point in the future, why don't do it in proper way to give you more confident when refactor or update your components?

Calling function defined using useCallback after setting the state on which it depends

I defined a function using useCallback, depending on a state. I want to call this function from another, which change the state before calling it and defined using useCallback as well. How can I do?
Example:
const refreshPage = useCallback(
() => {
if (state == '1')
// action
},
[state]
);
const onChangeText = useCallback(
(state) => {
setState(state);
refreshPage();
},
[refreshPage]
);
Here, when onChangeText will be called, it will update the state and the definition of refreshPage, but it will not call refreshPage with the new state I defined.
I precise that it's only an example and and I use useEffect to load data.
Thanks!
You want to do some action when state changed and state equal to one, you better handle it in useEffect rather than useCallback and you do not need to call anything like refreshPage at all
const refreshPage = useCallback(() => {
// load data here
},[]);
useEffect(() => {
if (state == '1'){
refreshPage()
}
},[state]);
const onChangeText = useCallback((state) => {
setState(state);
},[]);
The reason why it won't be called with the updated state is that useCallback will update the value of the function when the component rerenders and when you call setState(state) and refreshPage(), the component haven't rerendered and it's still using the refreshPage that haven't updated yet.
One way to make this work is to pass the state to refreshPage and use it there, instead of relaying on the state that will be changed on the useCallback.
const refreshPage = useCallback(
// added state here
(state) => {
if (state == '1')
// action
},
[] // removed state from here
);
const onChangeText = useCallback(
(state) => {
setState(state);
refreshPage(state);
},
[refreshPage]
);

How to use useFocusEffect hook

As the docs https://reactnavigation.org/docs/en/next/use-focus-effect.html,
"Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc."
I'm trying to use useFocusEffect to fetch data everytime that the user go to that page.
on my component I have a function which dispatch an action with redux to fetch the data:
const fetchData = ()=>{
dispatch(companyJobsFetch(userDetails.companyId));
};
Actually I'm using useEffect hook to call fetchData(), but I'd like to fetch data everytime that the user go to that page and not only when rendered the first time.
It's not clear from the documentation how to use useFocusEffect and I'm not having success on how to do it.
Any help?
The docs show you how to do it. You need to replace API.subscribe with your own thing:
useFocusEffect(
React.useCallback(() => {
dispatch(companyJobsFetch(userDetails.companyId));
}, [dispatch, companyJobsFetch, userDetails.companyId])
);
For version react navigation 4.x, you can use addEvent listener
useEffect(() => {
if (navigation.isFocused()) {
resetReviews(); // replace with your function
}
}, [navigation.isFocused()]);
OR
useEffect(() => {
const focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action
_getBusiness({id: business?.id}); // replace with your function
});
return () => {
// clean up event listener
focusListener.remove();
};
}, []);
For later version 5.x, you can use hooks to achieve this
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}