How to manage the risk of race conditions in useEffects when a component can un-mount at any time, with React?

Viewed 860

Using TypeScript 4, React 17 (current versions at the time I'm writing these lines).

Let say I have the following component.

This component is behind a React Navigation conditional stack navigator. This mean that if the user un-connect, the component will be automatically unmounted and the with the whole UI, showing the login screen instead.

This is managed by such code in the app's root navigator:

user ? (
    <RootStack.Screen name="Home" component={HomeScreen} />
) : (
    <RootStack.Screen name="Login" component={LoginScreen} />
)

Here is a component that print on the home screen.

I had some issues in another project and a friend of mine recommended to always put this un-mounting condition when a component un-mount with a risk of race condition.

I have some race conditions issues but adding un-mounting on all the components did not solved it so far.

Is my friend right that I shall continue to put these un-mounting or is it un-needed for such simple and synchronous cases as the following component?

interface Props {
    prop1: number
}

const MyFunctionalComponent: FC<Props> = ({prop1 = 0}: Props) => {

    const [stateVariable, setStateVariable] = useState<string>('initial value')

    useEffect(() => {
        let mounted = true
        if (mounted) {
          // We are synchronous, no await there.
          const newStateVariable: string = stringify(prop1)
          setStateVariable(newStateVariable)
        }
        return (): void => {
          mounted = false
        }
    }, [prop1])

    return <Text>{ stateVariable }</Text>
}

What do you use and know about this ?

Thanks

2 Answers

You don't have to use useEffect hook's clean up to do this.

useEffect hook is run when dependency list is changed. Which is like componentDidUpdate in the React class components.

useEffect(() => {
   // This runs every time the prop1 changes

 return () => {
  // This runs every time the prop1 changes
 };
}, [prop1]);

If you just have empty array for dependency list then it works like componentDidMount in the React class components.

useEffect(() => {
   // This runs when component mounts
   // see there is no dependency passed for this

 return () => {
  // This runs when component un mounts
  // Do your cleanup here as it is run only on unmount
 };
}, []);

useEffect hook runs all the when its own state or props change if neither empty array or dependency list is passed.

useEffect(() => {
   // Runs every time the component renders

});

I guess now it is now clear about your case.

You don't have to use the mounted variable itself. Every time when your prop1 changes the stateVariable will get updated.

const MyFunctionalComponent: FC<Props> = ({prop1 = 0}: Props) => {

    const [stateVariable, setStateVariable] = useState<string>('initial value')

    useEffect(() => {
       // We are synchronous, no await there.
       const newStateVariable: string = stringify(prop1)

       setStateVariable(newStateVariable)
    }, [prop1])

    return <Text>{ stateVariable }</Text>
}

Some of the cases when you need to use the cleanup function

  1. To removeEventListener on document or window or any other subscription that functional component has made.

  2. To dispatch to cleanup store(if needed) if you are using redux.

  3. To clearInterval or clearTimeout if there are any.

Some of the cases where not use the cleanup function

  1. To set the state of the functional component that is un mounting.

  2. In the case like your question (not needed at all).

In the case of MyFunctionalComponent, you should call useMemo() instead of useState() and useEffect():

interface Props {
    prop1: number
}

const MyFunctionalComponent: FC<Props> = ({ prop1 = 0 }: Props) => {
    const computedVariable = useMemo(() => {
        return stringify(prop1)
    }, [prop1])

    return <Text>{ computedVariable }</Text>
}

useState() should only be necessary if you need a stateful value. In the example you provided, stateVariable is purely dependent on prop1, without any delay, so it doesn't actually need to be stateful at all. useMemo() is technically also a stateful hook, but is only there to avoid unnecessary repetition of expensive computations when the inputs are unchanged.

Related