Re-render component when navigating the stack with React Navigation - reactjs

I am currently using react-navigation to do stack- and tab- navigation.
Is it possible to re-render a component every time the user navigates to specific screens? I want to make sure to rerun the componentDidMount() every time a specific screen is reached, so I get the latest data from the server by calling the appropriate action creator.
What strategies should I be looking at? I am pretty sure this is a common design pattern but I failed to see documented examples.

If you are using React Navigation 5.X, just do the following:
import { useIsFocused } from '#react-navigation/native'
export default function App(){
const isFocused = useIsFocused()
useEffect(() => {
if(isFocused){
//Update the state you want to be updated
}
}, [isFocused])
}
The useIsFocused hook checks whether a screen is currently focused or not. It returns a boolean value that is true when the screen is focused and false when it is not.

React Navigation lifecycle events quoted from react-navigation
React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: willFocus, willBlur, didFocus and didBlur. Read more about them in the API reference.
Let's check this out,
With navigation listeners you can add an eventlistener to you page and call a function each time your page will be focused.
const didBlurSubscription = this.props.navigation.addListener(
'willBlur',
payload => {
console.debug('didBlur', payload);
}
);
// Remove the listener when you are done
didBlurSubscription.remove();
Replace the payload function and change it with your "refresh" function.
Hope this will help.

You can also use also useFocusEffect hook, then it will re render every time you navigate to the screen where you use that hook.
useFocusEffect(()=> {
your code
})

At the request of Dimitri in his comment, I will show you how you can force a re-rendering of the component, because the post leaves us with this ambiguity.
If you are looking for how to force a re-rendering on your component, just update some state (any of them), this will force a re-rendering on the component. I advise you to create a controller state, that is, when you want to force the rendering, just update that state with a random value different from the previous one.

Add a useEffect hook with the match params that you want to react to. Make sure to use the parameters that control your component so it rerenders. Example:
export default function Project(props) {
const [id, setId] = useState(props?.match?.params?.id);
const [project, setProject] = useState(props?.match?.params?.project);
useEffect(() => {
if (props.match) {
setId(props.match?.params.id);
setProject(props.match?.params.project);
}
}, [props.match?.params]);
......

To trigger a render when navigating to a screen.
import { useCallback } from "react";
import { useFocusEffect } from "#react-navigation/native";
// Quick little re-render hook
function useForceRender() {
const [value, setValue] = useState(0);
return [() => setValue(value + 1)];
}
export default function Screen3({ navigation }) {
const [forceRender] = useForceRender();
// Trigger re-render hook when screen is focused
// ref: https://reactnavigation.org/docs/use-focus-effect
useFocusEffect(useCallback(() => {
console.log("NAVIGATED TO SCREEN3")
forceRender();
}, []));
}
Note:
"#react-navigation/native": "6.0.13",
"#react-navigation/native-stack": "6.9.0",

Related

How to implement promise when updating a state in functional React (when using useState hooks)

I know similar questions are bouncing around the web for quite some time but I still struggle to find a decision for my case.
Now I use functional React with hooks. What I need in this case is to set a state and AFTER the state was set THEN to start the next block of code, maybe like React with classes works:
this.setState({
someStateFlag: true
}, () => { // then:
this.someMethod(); // start this method AFTER someStateFlag was updated
});
Here I have created a playground sandbox that demonstrates the issue:
https://codesandbox.io/s/alertdialog-demo-material-ui-forked-6zss6q?file=/demo.tsx
Please push the button to get the confirmation dialog opened. Then confirm with "YES!" and notice the lag. This lag occurs because the loading data method starts before the close dialog flag in state was updated.
const fireTask = () => {
setOpen(false); // async
setResult(fetchHugeData()); // starts immediately
};
What I need to achieve is maybe something like using a promise:
const fireTask = () => {
setOpen(false).then(() => {
setResult(fetchHugeData());
});
};
Because the order in my case is important. I need to have dialog closed first (to avoid the lag) and then get the method fired.
And by the way, what would be your approach to implement a loading effect with MUI Backdrop and CircularProgress in this app?
The this.setState callback alternative for React hooks is basically the useEffect hook.
It is a "built-in" React hook which accepts a callback as it's first parameter and then runs it every time the value of any of it's dependencies changes.
The second argument for the hook is the array of dependencies.
Example:
import { useEffect } from 'react';
const fireTask = () => {
setOpen(false);
};
useEffect(() => {
if (open) {
return;
}
setResult(fetchHugeData());
}, [open]);
In other words, setResult would run every time the value of open changes,
and only after it has finished changing and a render has occurred.
We use a simple if statement to allow our code to run only when open is false.
Check the documentation for more info.
Here is how I managed to resolve the problem with additional dependency in state:
https://codesandbox.io/s/alertdialog-demo-material-ui-forked-gevciu?file=/demo.tsx
Thanks to all that helped.

Updating React state after calling document.onpaste?

I am trying to setup a functional component that allows the user to copy/paste their clipboard image into the app. In useEffect, I setup document.onpaste . This was working and I was able to get the image and upload it. But I need to have it update the state after paste, so that I can allow them to paste multiple images and keep them in array in state.
I've simplified the problem below. Basically when I paste I want to have it update the counter value, but it doesn't. Counter always = 0. If I use normal react button onclick event, it works fine. I'm guessing the onpaste event is outside of React, but then,
How can I process the paste event and also update the functional state?
import React, { useEffect, useState } from 'react';
const AddAttachmentsContainer = ({ zoneid, tableid, field }) => {
const [counter,setCounter]=useState(0);
useEffect(() => {
document.onpaste=(e)=>{GetImage(e);e.preventDefault()}
},[])
const GetImage = (e) => {
setCounter(counter+1)
console.log(counter)
}
}
If I am not mistaken, I'll bet that the state is updated. useState will update the state and trigger a re-render. Since it is asynchronous, console.log will be called with the old state value.
useEffect(() => console.log(counter), [counter]);
... don't forget to add counter in the second argument of useEffect. see here...
Also see here
Alternatively, of course, you can also use refs, but be aware of the use cases for them. It is easy to allow refs to unnecessarily spread through your code.
To be honest, it might be wise to just setCounter in the useEffect you already have and call your GetImage function afterwards... from there, you can retrieve counter. There are multiple approaches to this.

Should we change class component to functional component when we put state into Redux?

I was learning React and created two class components having respective states. Then, I learned about Redux and decided to transfer states into redux store. The question is "Is it best practice to change class componenents into functional components since we get state via props from redux store?"
Functional components with react hooks is the new standard of coding on React. For store management(f.e. redux) you may use as classes as functional components, but most of the libs moved to functional components and you may not use all benefits of last versions of them.
Why I prefer functional components and hooks over classes:
Cleaner render tree. No wrapper components
More flexible code. You
can use useEffect on different state changes, in classes you have
only componentDidUpdate for ANY state/props change
You can define your custom hooks to keep your code clean and shiny
IMHO, yes, I suggest that you should switch from class-based component to functional component as soon as possible.You might not want to know how the class-based components have bugged me so hurt before I decided to go with Hooks. The number of components in my large project is now over 400 (including both smart and dumb components) and keep increasing. Hooks keep my life easier to continue developing and maintaining.
Have a look at this useful article: https://blog.bitsrc.io/why-we-switched-to-react-hooks-48798c42c7f
Basically, this is how we manage state with class-based:
It can be simplified to half the lines of code, achieving the same results with functional component and useState, useEffect:
Please also take a look at this super useful site: https://usehooks.com/
There are many useful custom hooks from the community that you can utilize. Below are the ones that I have been using all the time:
useRouter: Make your life easier with react-router. For example:
import { useRouter } from "./myCustomHooks";
const ShowMeTheLocation = () => {
const router = useRouter();
return <div>Show me my param: {router.match.params.myDesiredParam}</div>;
}
useEventListener: simplify your event handler without using componentDidMount and componentWillUnmount to subscribe/unsubscribe. For example, I have a button that needs to bind a keypress event:
import { useEventListener } from "./myCustomHooks";
const FunctionButton = () => {
const keydownHandler = event => { // handle some keydown actions };
const keyupHandler = event => { // handle some keyup actions };
// just simple like this
useEventListener("keydown", keydownHandler);
useEventListener("keyup", keyupHandler);
}
useAuth: authenticate your user.
import { useAuth } from "./use-auth.js";
const Navbar = (props) => {
// Get auth state and re-render anytime it changes
const auth = useAuth();
// if user is authenticated, then show user email, else show Login
return <div>{auth.user? auth.user.email: "Login"}</div>;
}
useRequireAuth: handle redirect your user if they are signed out and trying to view a page that should require them to be authenticated. This is composed by useRouter and useAuth above.
import { useRequireAuth } from "./myCustomHooks";
// Dashboard is a page that need authentication to view
const Dashboard = () => {
const isAuth = useRequireAuth();
// If isAuth is null (still fetching data)
// or false (logged out, above hook will redirect)
// then show loading indicator.
if (isAuth) {
return <div>Fetching data, please wait!</div>
}
// {...{ isAuth }} is similar to:
// isAuth={isAuth}
return <Dashboard {...{ isAuth }} />
}
Hope this helps!
First of All, States can be used only in Class Component. In React's latest version there's a huge update that allows functional components to declare and use state using React-Hooks. So, the best practice I would personally suggest you is to use Class Component when you use the Redux Store. As you're a beginner, Please use a functional component where you don't use any state or props and just render DOM elements (Note: Functional components can accept props). Once you learn the differences properly, go with React-Hooks.
I hope it helps!! Happy Coding!!

Can't perform a React State update on unMounted child component?

Am getting this warning:
Can't perform a React state update on unmounted component. This is a no-op...
It results from a child component and I can't figure out how to make it go away.
Please note that I have read many other posts about why this happens, and understand the basic issue. However, most solutions suggest cancelling subscriptions in a componentWillUnmount style function (I'm using react hooks)
I don't know if this points to some larger fundamental misunderstanding I have of React,but here is essentially what i have:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) Please note:
MyTextarea component has a parent component which is only rendered on a certain route.
Theres also a Menu component that once clicked, changes the route and depending on the situation will either show MyTextarea's parent component or show another component.
This warning happens once I click the Menu to switch off MyTextarea's parent component.
More Context
Other answers on StackOverflow suggest making changes to prevent state updates when a component isn't mounted. In my situation, I cannot do that because I didn't design the Picker component (rendered by MyTextarea). The Warning originates from this <Picker onEmojiClick={onClick}> line but I wouldn't want to modify this off-the-shelf component.
That's explains my attempt to either render the component or not based on the isMountedRef. However this doesn't work either. What happens is the component is either rendered if i set useRef(true), or it's never rendered at all if i set useRef(null) as many have suggested.
I'm not exactly sure what your problem actually is (is it that you can't get rid of the warning or that the <Picker> is either always rendering or never is), but I'll try to address all the problems I see.
Firstly, you shouldn't need to conditionally render the <Picker> depending on whether MyTextArea is mounted or not. Since components only render after mounting, the <Picker> will never render if the component it's in hasn't mounted.
That being said, if you still want to keep track of when the component is mounted, I'd suggest not using hooks, and using componentDidMount and componentWillUnmount with setState() instead. Not only will this make it easier to understand your component's lifecycle, but there are also some problems with the way you're using hooks.
Right now, your useRef(true) will set isMountedRef.current to true when the component is initialized, so it will be true even before its mounted. useRef() is not the same as componentDidMount().
Using 'useEffect()' to switch isMountedRef.current to true when the component is mounted won't work either. While it will fire when the component is mounted, useEffect() is for side effects, not state updates, so it doesn't trigger a re-render, which is why the component never renders when you set useRef(null).
Also, your useEffect() hook will fire every time your component updates, not just when it mounts, and your clean up function (the function being returned) will also fire on every update, not just when it unmounts. So on every update, isMountedRef.current will switch from true to false and back to true. However, none of this matters because the component won't re-render anyways (as explained above).
If you really do need to use useEffect() then you should combine it into one function and use it to update state so that it triggers a re-render:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
Lastly, from the code you shared, your component couldn't be causing the warning because there are no state updates anywhere in your code. You should check the picker's repo for issues.
Edit: Seems the warning is caused by your Picker package and there's already an issue for it https://github.com/ealush/emoji-picker-react/issues/142

useEffect not called in React Native when back to screen

How are you.
This is scenario of this issue.
Let's say there are 2 screens to make it simple.
enter A screen. useEffect of A screen called.
navigate to B screen from A screen
navigate back to A screen from B.
at this time, useEffect is not called.
function CompanyComponent(props) {
const [roleID, setRoleID] = useState(props.user.SELECTED_ROLE.id)
useEffect(()=>{
// this called only once when A screen(this component) loaded,
// but when comeback to this screen, it doesn't called
setRoleID(props.user.SELECTED_ROLE.id)
}, [props.user])
}
So the updated state of Screen A remain same when comeback to A screen again (Not loading from props)
I am not changing props.user in screen B.
But I think const [roleID, setRoleID] = useState(props.user.SELECTED_ROLE.id) this line should be called at least.
I am using redux-persist. I think this is not a problem.
For navigation, I use this
// to go first screen A, screen B
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// when come back to screen A from B
function goBack() {
_navigator.dispatch(
NavigationActions.back()
);
}
Is there any callback I can use when the screen appears?
What is wrong with my code?
Thanks
Below solution worked for me:
import React, { useEffect } from "react";
import { useIsFocused } from "#react-navigation/native";
const ExampleScreen = (props) => {
const isFocused = useIsFocused();
useEffect(() => {
console.log("called");
// Call only when screen open or when back on screen
if(isFocused){
getInitialData();
}
}, [props, isFocused]);
const getInitialData = async () => {}
return (
......
......
)
}
I've used react navigation 5+
#react-navigation/native": "5.6.1"
When you navigate from A to B, component A is not destroyed (it stays in the navigation stack). Therefore, when you navigate back the code does not run again.
Perhaps a better way to acheive what you want to to use the navigation lifecycle events (I am assuming you are using react-navigation) I.e. subscribe to the didFocus event and run whatever code you want whenever the component is focussed E.g
const unsubscribe = props.navigation.addListener('didFocus', () => {
console.log('focussed');
});
Don't forget to unsubscribe when appropriate e.g.
// sometime later perhaps when the component is unmounted call the function returned from addListener. In this case it was called unsubscribe
unsubscribe();
The current version of React Navigation provides the useFocusEffect hook. See here.
React Navigation 5 provide a useFocusEffect hook, is analogous to useEffect, the only difference is that it only runs if the screen is currently focused. Check the documentation https://reactnavigation.org/docs/use-focus-effect
useFocusEffect(
useCallback(() => {
const unsubscribe = setRoleID(props.user.SELECTED_ROLE.id)
return () => unsubscribe()
}, [props.user])
)
The above mentioned solution would work definitely but in any case you need to know why it happens here is the reason,
In react native all the screens are stacked meaning they follow the LAST-IN-FIRST-OUT order, so when you are on a SCREEN A and go.Back(), the component(Screen A) would get un-mounted because it was the last screen that was added in the stack but when we naviagte to SCREEN B, it won't get un-mounted and the next SCREEN B would be added in the stack.
So now, when you go.Back() to SCREEN A, the useEffect will not run because it never got un-mounted.
React-native keeps the navigation this way to make it look more responsive and real time.
if you want to un-mount the Screen everytime you navigate to some other screen you might want to try navigation.replace than navigation.navigate
Hope this helps you.
import { useIsFocused } from "#react-navigation/native";
const focus = useIsFocused(); // useIsFocused as shown
useEffect(() => { // whenever you are in the current screen, it will be true vice versa
if(focus == true){ // if condition required here because it will call the function even when you are not focused in the screen as well, because we passed it as a dependencies to useEffect hook
handleGetProfile();
}
}, [focus]);
more
Solution with useEffect
import { useNavigation } from '#react-navigation/native';
const Component = (props) => {
const navigation = useNavigation()
const isFocused = useMemo(() => navigation.isFocused(), [])
useEffect(() => {
if (isFocused) {
// place your logic
}
}, [isFocused])
}

Resources