Global Recoil state map updating in current file, but not globally

20 Views Asked by At

I want to update a user's global recoil state variable based on specific operations. I am using recoil and atoms, and the user's state variable is a JavaScript Map. I pass a function setDynamicUserMap() from my root file (App.js) to my components, and I call that function to update the user's map. The issue I am running into is that App.js shows a change to the map, and any useEffect hooks that rely on it run inside of App.js, but everywhere else it shows no change to the UserMap recoil state variable.

The best way to reproduce is:

// globalState.jsx
import { atom } from "recoil"
export const userMapState = atom({
    key: "userMapState",
    default: new Map(),
})
// App.js
import * as globalState from "./globalState.jsx"
import { RecoilRoot } from 'recoil';
import { useCallback } from 'react';

export default function App(){

  return (<RecoilRoot>
    <AppContent/>
  </RecoilRoot>)
}

export function AppContent() {
  const [UserMap, setUserMap] = useRecoilState(globalState.userMapState)

  const setDynamicUserMap=(data)=>{
   dynamicMapData(data,setUserMap)
  }

  const dynamicMapData = useCallback((data, dataPointSet, callBK) => {//startTransition(()=>{
    if (data) Promise.all(Object.entries(data).flatMap(async([k, v]) => dataPointSet(p => { 
           /* if (p.has(k)) { var { added, deleted, updated } = detailedDiff(p.get(k), v)
                if ((Object.keys(added).length + Object.keys(deleted).length + Object.keys(updated).length) > 0) p.set(k, v)
            } else */
        p.set(k, v);
        return p;
        }))).then((p) => {
            if (callBK) callBK({valid: true, res: p})
            else dataPointSet(p=> new Map(p))
        })
    // })
    }, []);
}

// I also tried a non-memoized version of setting the UserMap:
/*
  const setDynamicUserMap=(data)=>{
    Object.entries(data).map(([k, v]) => {
      setUserMap(p => {
        p.set(k, v); return new Map(p);})})
  }
*/

  useEffect(()=>{
    console.log(UserMap)
  },[UserMap])
// Notification.jsx
const Notification=({navigation, route})=> { // access globalState through route (stackNavigator prop)
const { globalState, setDynamicUserMap, update } = route.params
const [UserMap, setUserMap] = useRecoilState(globalState.userMapState)

    const setModule = (module) => {
      if(!UserMap) return;

      // CRUD operation
      update(...(CRUD operation parameters here)..., (x)=>{
        if(x.success === false) console.log("An error occurred:", x.info)
        else if(x.success === true && x.info?._id) setDynamicUserMap(x.info) // returns from the server with the user's map information
      });
    }

    useEffect(()=>{ // this does not fire!
      console.log("change to UserMap:", UserMap)
    },[UserMap])
}

The general pattern is Update in Notification.jsx -> App.js receives update and performs server API call to get UserMap -> callback fires on Notification.jsx and sets the UserMap using a function declared in App.js -> New Recoil State Variable is rendered -> all currently subscribed useEffects with the UserMap hook run

The issue is the hook inside the useEffect in App.js is firing, which tells me the global recoil variable was changed, however the hook inside of Notificaitons.jsx does not fire, as it does not see a state change.

I have tried using a memoized and non-memoized way of setting the UserMap, ensuring that <RecoilRoot> wraps App.js, and setting p => new Map(p); when setting the UserMap, as for some reason Recoil needs to see a new map created when updating your current map for it to fire any hooks.

It seems that a lot of similar questions want the updated recoil state before running a function, but in this case, I want a useEffect to fire once the state was changed.

Is there something I am missing to get the state to change globally, and having all the relevant useEffect hooks run because of the change?

0

There are 0 best solutions below