Search code examples
reactjsreact-hooksreactn

Updating a global variable from 2 different components simultaneously using react hooks


I have a global variable that is an array.

In 2 different components they add an element to the array simultaneously on mount. But in order to add an element to the array they need to clone the current one & add to it. Since they're mounting simultaneously, the array is empty when they clone it. So only one element is added to the array.

How can I use useEffect to run the inserted code once on mount & run the return code on dismount reading a global variable that can change.

here's an example

store.js

import { setGlobal } from 'reactn';

const initStore = () => {
  setGlobal({
    globalArray: [],
  });
};

export default initStore;

component-number-one.js

import React,{useEffect, } from 'react';
// works just like useState but makes the variables accessible anywhere
import { useGlobal, } from 'reactn';
// Just a clone util, clones stuff
import { clone } from '../../../utils/clone';

const ComponentNumberOne = () => {

  const [globalArray, setGlobalArray] = useGlobal('globalArray'); 

  useEffect(() => {
    // Only want this to run on mount
    let newGlobalArray = clone(globalArray); // Problem is here, doesn't know globalArray has an updated value
    newGlobalArray.push("ComponentNumberOne");
    setGlobalArray(newGlobalArray);

    // Only want this to run on dismount
    return () => {
      let newGlobalArray = clone(globalArray);  // Possibly problem here too
      let index = newGlobalArray.indexOf("ComponentNumberOne");
      newGlobalArray.splice(index,1);
      setGlobalArray(newGlobalArray);
    }
  },[]);

  return (
    <div className="ComponentNumberOne">
      ComponentNumberOne stuff
    </div>
  );
};

export default ComponentNumberOne;

component-number-two.js

import React,{useEffect, } from 'react';
// works just like useState but makes the variables accessible anywhere
import { useGlobal, } from 'reactn';
// Just a clone util, clones stuff
import { clone } from '../../../utils/clone';

const ComponentNumberTwo = () => {

  const [globalArray, setGlobalArray] = useGlobal('globalArray'); 

  useEffect(() => {
    // Only want this to run on mount
    let newGlobalArray = clone(globalArray); // Problem is here, doesn't know globalArray has an updated value
    newGlobalArray.push("ComponentNumberTwo");
    setGlobalArray(newGlobalArray);

    // Only want this to run on dismount
    return () => {
      let newGlobalArray = clone(globalArray); // Possibly problem here too
      let index = newGlobalArray.indexOf("ComponentNumberTwo");
      newGlobalArray.splice(index,1);
      setGlobalArray(newGlobalArray);
    }
  },[]);

  return (
    <div className="ComponentNumberTwo">
      ComponentNumberOne stuff
    </div>
  );
};

export default ComponentNumberTwo;

Solution

  • The useEffect should have globalArray in its dependencies array.

    Of course, just putting globalArray in the dependencies breaks your code.

    The actual problem is that your code behaves "imperatively", i.e. "if the component is mounted, add an item to the array. That is not the way React is supposed to be used.

    Instead of telling React when to do something, you should tell React what state you expect. I.e. update the array to represent the desired state, e.g.:

      useEffect(() => {
    
        if( !globalArray.includes("ComponentNumberTwo") ){
          setGlobalArray([
            ...globalArray,
            "ComponentNumberTwo"
          ]);
        };
    
      }, [ globalArray ]);