Using React Hooks and Global State with ReactN



I’ve been pretty happy with the addition of Hooks to React, along with functional components. In this post I’ll provide examples on how to use ReactN to cascade global state updates to functional components.

Functional components have reduced the amount of boilerplate code that I am forced to write and make components seem more atomic. I think we can all agree that writing less code and getting the same result is a great idea.

The addition of hooks, such as useState() and useEffect(), give additional flexibility without adding too much extra code.

useState() will force a re-render when the setSomeVariable() method is called by a user event or some custom event, but the state is local only to the one component and its children. The local state can’t normally be propagated to the parent component or other components in the execution tree. Various systems are available to get around this, like Redux, but sometimes the project that you’re working on doesn’t need a huge library that provides the dispatchers, reducers, and features that Redux uses. Sometimes you want something lighter.

This is where ReactN comes in for my project. ReactN is very lightweight and easy to use, in my opinion, compared to Redux. It provides an easy to use hook called useGlobal() in which you can assign a global property and a default value using hooks. The method’s signature is as follows.

export function SettingsModal (props) {
  const [debugModeChecked, setDebugModeChecked] = useState();
  const [globalDebugMode, setGlobalDebugMode] = useGlobal('debugMode');
  const saveConfig = async () => {
    setGlobalDebugMode(Number(debugModeChecked));
  };
  
  return (
    <div>
      <input 
        type="checkbox"
        checked={debugModeChecked} 
        onChange={(event) => setDebugModeChecked(event.currentTarget.checked)}
      />      
      <button onClick={saveConfig}>Click me!&lt;/button>
    </div>
  );
}

In the above code we are using useGlobal with the ‘debugMode’ property. This means that the setGlobalDebugMode function will add or modify a property called ‘debugMode’ in the global store. I am using numbers for boolean values because those are compatible with sessionStorage, whereas false or true gets converted to string ‘false’ and string ‘true’, which both resolve to Boolean true.

This settings modal modifies some sessionStorage values and then the other components in the website will use those global or session values.

The Consumers

Consumers of the global settings should be intelligent enough to re-render when the global setting changes. This is where some confusion began with functional components and hooks.

I had assumed that just using a hook, such as useGlobal(‘debugMode’), that every functional component using that hook would automatically update the debugMode value and re-render with the new value.

The easiest way to force a re-render is to call a useState setter function and use the state’s property in the component. The question is how do we make useGlobal call a useState setter? This is where useEffect comes in.

The useEffect hook has several method invocations. The uses are listed below.

useEffect(() =&gt; {
  // fires whenever the component re-renders
  ...  
});

useEffect(() =&gt; {
  // fires only once 
  // because the watch properties are empty and unchanging
  ...  
}, []);


useEffect(() =&gt; {
  // fires whenever objectToWatch changes
  ...  
}, [objectToWatch]);

The third use case is useful to us because we want to fire some re-render event whenever the global state changes. Now, we could do this with reducers and dispatchers, but in this case I’m not performing complex operations on the state, so the following method seems easiest.

Remember that the functional component only re-renders if the props or state changes, so we need the useEffect code to initiate a local state change. We’ll also use ReactN’s getGlobal to get the global object every time it changes and potentially update the state whenever the debugMode property is updated.

// set the default value to false
const [debugMode, setDebugMode] = useState(false);
   
useEffect(() => {
  const global = getGlobal();
  setDebugMode(global.debugMode);
}, [getGlobal()]);

return (
  <div id="camera-container">
    <ChildComponent debugMode={debugMode} />;
  </div>
);

In the code above, the [getGlobal()] function call inside the useEffect parameter is re-executed every time any component causes a render update. Since the global object changed in the settings modal, the getGlobal function will return a new object, and the useEffect callback function will be executed.

Inside the callback function for useEffect we re-read the global state again using getGlobal(). The reason we don’t use useGlobal(‘debugMode’) anywhere is because the value returned by this function is unchanging since it’s not run in the useEffect function, but instead only when the component re-renders.

Once we have the up-to-date global value, we pass the debugMode property value to the setDebugMode() local state setter function. This function will force a re-render and the ChildComponent will get the new value, cascading the render event to all children that need the new value.

It’s entirely possible to add useEffect and useState to those child components that need this global state, but in this case I preferred to add it to the parent to minimize the amount of code needed.

Again, it’s also possible to add reducers and dispatchers, but I found this method to be easy. Perhaps as I add more code to the project I’ll run into the limitations and decide on another route, but for now it performs its intended duty.

Leave a Reply

Your email address will not be published. Required fields are marked *