Home > Enterprise >  How to observe change in a global Set variable in useEffect inside react component function?
How to observe change in a global Set variable in useEffect inside react component function?

Time:11-11

I have a page wherein there are Listings. A user can check items from this list.

Whenever the user checks something it gets added to a globally declared Set(each item's unique ID is added into this set). The ID's in this set need to be accessed by a seperate Component(lets call it PROCESS_COMPONENT) which processes the particular Listings whose ID's are present in the set.

My Listings code roughly looks like:

import React from "react";
import { CheckBox, PROCESS_COMPONENT } from "./Process.jsx";

const ListItem = ({lItem}) => {
    return (
            <>
               //name,image,info,etc.
               <CheckBox lId={lItem.id}/>
            </>
)
};

function Listings() {
 // some declarations blah blah..
 return (
       <>
          <PROCESS_COMPONENT /> // Its a sticky window that shows up on top of the Listings.
          //..some divs and headings
          dataArray.map(item => { return <ListItem lItem={item} /> }) // Generates the list also containing the checkboxes
       </>
)
}

And the Checkbox and the PROCESS_COMPONENT functionality is present in a seperate file(Process.jsx).

It looks roughly like:

import React, { useEffect, useState } from "react";

let ProcessSet = new Set(); // The globally declared set.

const CheckBox = ({lID}) => {
   const [isTicked, setTicked] = useState(false);
   const onTick = () => setTicked(!isTicked);
   useEffect( () => {
     if(isTicked) {
          ProcessSet.add(lID);
     }
     else { 
          ProcessSet.delete(lID); 
     }
     console.log(ProcessSet); // Checking for changes in set.
   }, [isTicked]);
   return (
      <div onClick={onTick}>
       //some content
      </div>
   )
}

const PROCESS_COMPONENT = () => {
   const [len, setLen] = useState(ProcessSet.size);
   useEffect( () => {
      setLen(ProcessSet.size);
   }, [ProcessSet]); // This change is never being picked up.
   return (
        <div>
        <h6> {len} items checked </h6>
        </div>
   )
}

export { CheckBox, PROCESS_COMPONENT };

The Set itself does get the correct ID values from the Checkbox. But the PROCESS_COMPONENT does not seem to be picking up the changes in the Set and len shows 0(initial size of the set).

I am pretty new to react. However any help is appreciated.

Edit:

Based on @jdkramhoft 's answer I made the set into a state variable in Listings function.

const ListItem = ({lItem,set,setPSet}) => {
   //...
        <CheckBox lID={lItem.id} pset={set} setPSet={setPSet} />
   )
}

function Listings() {
  const [processSet, setPSet] = useState(new Set());
  //....
      <PROCESS_COMPONENT set={processSet} />
      dataArray.map(item => { 
          return <ListItem lItem={item} set={processSet} setPSet={setPSet} /> 
      })
}

And corresponding changes in Process.jsx

const CheckBox = ({lID,pset,setPSet}) => {
  //...
  if (isTicked) {
     setPSet(pset.add(lID)); 
  }
  else {
     setPSet(pset.delete(lID));
  }
  //...
}

const PROCESS_COMPONENT = ({set}) => {
   //...
   setLen(set.size);
   //...
}

Now whenever I click the check box I get an error:

TypeError: pset.add is not a function. (In 'pset.add(lID)', 'pset.add' is undefined)

Similar error occurs for the delete function as well.

CodePudding user response:

First of all, the set should be a react state const [mySet, setMySet] = useState(new Set()); if you want react to properly re-render with detected changes. If you need the set to be available to multiple components you can pass it to them with props or use a context.

Secondly, React checks if dependencies like [ProcessSet] has been changed with something like ===. Even though the items in the set are different, no change is detected because the object is the same and there is no re-render.

Update:

The setState portion of [state, setState] = useState([]); is not intended to mutate the previous state - only to provide the next state. So to update your set you would do something like:

  const [set, setSet] = useState(new Set())
  const itemToAdd = ' ', itemToRemove = ' ';
  setSet(prev => new Set([...prev, itemToAdd]));
  setSet(prev => new Set([...prev].filter(item => item !== itemToRemove)));

As you might notice, this makes adding and removing from a set as slow as a list. So unless you need to make a lot of checks with set.has() I'd recommend using a list:

  const [items, setItems] = useState([])
  const itemToAdd = ' ', itemToRemove = ' ';
  setItems(prev => [...prev, itemToAdd]);
  setItems(prev => prev.filter(item => item !== itemToRemove));
  • Related