Why this navigation.goBack is not working as I intended ? How can I get the Last Active State? - react-native

I got 3 pages
homepage, productList and productDetails
When going from homepage to productList I pass a route param,
navigation.navigate('productList', { showCategory: 'productListA'} )
InitialProcess when component mounted
Inside the productList page when the component is mounted. I am declaring use state like this.
const {showCateory} = route.params;
const [activeTab, setActiveTab] = useState(showCateory);
and calling api using that activeTab
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
User Interaction
But I also add the button in the productList so that user can change the current active tab
<TouchableOpacity onPress={() => changeTab()}></TouchableOpacity>
const changeTab = async () => {
await setActiveTab('productListB'),
await dispatch(fetchProductList(activeTab)
}
Take note that right now active tab and data coming from api is different from when the component is start mounted.
Navigation Change again
When use goes from productList to productDetails. All thing is fine.
But inside the product details I am going back to productList with this.
navigation.goBack().
When I am back in productList page The activeTab is change back to productListA and the data is change back to when component is mounted
Can I pass or change the route params when calling navigation.goBack()?

add activeTab in useEffect depedineces.
as docs say
The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
//this value will always updated when activeTab change
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, [activeTab]); //<<<<< here
also you need to know setState() does not always immediately update the component. see here
so change this
const changeTab = async () => {
//await setActiveTab('productListB'),
//await dispatch(fetchProductList(activeTab)
setActiveTab('productListB')
dispatch(fetchProductList('productListB'))
}

This might be happening because route.params is still set to { showCategory: 'productListA'} when you are coming back to the screen.
If this is the case, you can fix it by Changing params object in changeTab() like
navigation.setParams({
showCategory: 'productListB',
});
I hope this will fix your problem.

This happens because the callback function inside the focus listener uses the initial value of the state when the function was defined (at initial page render) . Throughout the lifespan of listener the callback function uses this stale state value.You can read more about this behaviour in this answer
Although the answer by Ahmed Gaber works in this case as the listener is cleared and redefined after each state change.Another common work-around is to use an useRef instead of useEffect.A ref is basically a recipe that provides a mutable object that can be passed by reference.
In your case you can initialise activeTab with navigation param value using useRef hook as :
const activeTab = useRef(showCateory);
and the focus listener callback function should be changed to use the Reference current value as
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab.current, //<<<<<<---------here
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
and the changeTab function can directly update reference current value
const changeTab = async () => {
setActiveTab.current = 'productListB';
dispatch(fetchProductList('productListB'))
}

Related

Async custom hook from within useEffect

When kept in the component body, the following code works fine. Inside useEffect, it checks the asyncstorage and dispatches an action (the function is longer but other checks/dispatches in the function are of the same kind - check asyncstorage and if value exists, dispatch an action)
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
I'm trying to move it to a custom hook but am having problems. The custom hook is:
const useGetUserSettings = () => {
const dispatch = useDispatch();
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
};
export default useGetUserSettings;
Then in the component where I want to call the above, I do:
import useGetUserSettings from './hooks/useGetUserSettings';
...
const getUserSettings = useGetUserSettings();
...
useEffect(() => {
getUserSettings();
}, [getUserSettings])
It returns an error:
getUserSettings is not a function. (In 'getUserSettings()', 'getUserSettings' is undefined
I've been reading rules of hooks and browsing examples on the internet but I can get it working. I've got ESlint set up so it'd show if there were an invalid path to the hook.
Try the following.
useEffect(() => {
if (!getUserSettings) return;
getUserSettings();
}, [getUserSettings]);
The hook doesn't return anything, so it's not surprising that the return value is undefined ;)

React Native useEffect() re-renders too much

i have a useEffect function where a redux action is called and data is written to prop. My Problem is that useEffect loop many times and flooded the server with requests.
const { loescherData, navigation } = props;
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
setData(props.loescherData);
}
});
}, [loescherData]);
if i leave it blank the rendering is finished before receiving data and the content would not updated.
is there another way to work with this function?
loescherData won't be available right after calling your redux-action fetchLoescherDetails ... and changing component by setData will cause an infinite rendering cause your current useEffect has a dependency on loescherData
So I'd suggest you exec your redux-action onComponentDidMount by passing an empty-deps [] to your effect ... and then consume the output of you action in a different effect
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
// setData(props.loescherData);
}
});
}, []);
useEffect(() => {
if (loescherData) {
// do some with loescherData like setState
}
}, [loescherData]);

React Natie, When I use useState data on componentWillUnmount, just getting first data

When I use useState data inside useEffect return function (componentWillUnmount), I am just getting first data,
I am changing note values on textview and when i go back another screen, useEffect return function working well but note values is "". Why this happining and how can i solve it?
const [note, setNote] = useState("");
useEffect(() => {
getContent();
return () =>{
saveToStorage({note:note}, "componentWillUnmount");
};
}, []);
const saveToStorage = (data) =>{
console.log(data); // note:""
}
useEffect will get the note reference only once since you have added [] as dependencies instead give notes as dependencies so it will be called evertime so it can get updated note reference
code:
const [note, setNote] = useState("");
React.useEffect(() => {
if (condition to check wether its first time) {
// getNotes()
}
return () => {
saveToStorage({ note: note }, 'componentWillUnmount');
}
}, [note]);
const saveToStorage = (data) =>{
console.log(data); // note:""
}

React Native hooks - correct use of useEffect()?

I'm new to hooks and ran across this setup on SO and wanted to confirm that this is the correct pattern. I was getting the RN "unmounted component" leak warning message before and this seemed to solve it. I'm trying to mimic in some way compnentDidMount. This is part of a phone number verify sign up flow and onMount I want to just check for navigation and then fire off a side effect, set mounted true and then unmount correctly.
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const [didMount, setDidMount] = useState(false)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
setDidMount(true)
}
return () => setDidMount(false)
}, [])
if (!didMount) { return null }
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}
RN 0.62.2 with react-nav 5 - thanks!
Since signInWithPhoneNumber is a async function and will setState you will see warning it the component is unmounted before the response is available
In order to handle such scenarios you can keep a variable to keep track whether its mounted or not and then only set state is the mounted variable is true
However you do not need to return null if component has unmounted since that doesn't accomplish anything. The component is removed from view and will anyways not render anything.
Also you do not need to maintain this value in state, instead use a ref
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const isMounted = useRef(true)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
}
return () => {isMounted.current = false;}
}, [])
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}

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>;
}