Next.js example: Custom Hook for shared global state

reasonreact

#1

Hi all,

I’m working on expanding the https://nextjs.org with-reasonml example to show both a persisted state (shared component?) and an internal component state. A couple things:

  1. I’m a react newby, so is my usage of React.useReducer correct?
  2. Does anyone know why the initial call to dispatch is duplicated?

Here is a link to the PR: https://github.com/zeit/next.js/pull/7312


#2

I’ve updated the implementation to use a custom hook. I muddled around with using the Context API but found that it was very confusing to me and required me to mix in a lot of JavaScript.

Benefits of the custom hook implementation

  1. All concerns are separated nicely
  2. No new raw JavaScript code was required.
  3. Duplicate increment issue on first click after load was solved.

Important code bits.

First I used the reducer hook because of the following note in the reason-react code:

Custom Hook Implementation

/*
    ## Global Count
    This captures the count globally so that it will be persisted across
    the `Index` and `About` pages.  This replicates the functionality
    of the shared-modules example.
 */
type t = ref(int);

type action =
  | Increment;

let current = ref(0);

let increment = () => {
  current := current^ + 1;
  current;
};

let reducer = (_state, action) => {
  switch(action) {
  | Increment =>
    let updated = increment();
    updated^
  }
};

let useGlobalCount = () => React.useReducer(reducer, current^);

This is just a simple global reference wrapped up in a reducer hook interface. This allows the calling component to use this like it would any other reducer hook.

Custom Hook Usage

/*
    This is the set of action messages which are produced by this component.
    * Add updates the components internal state.
 */
type action =
  | Add

/*
   This is the components internal state representation. This state object
   is unique to each instance of the component.
 */
type state = {
  count: int,
};

let counterReducer = (state, action) =>
  switch(action) {
  | Add => { count: state.count + 1 }
  };

[@react.component]
let make = () => {
  let (state, dispatch) = React.useReducer(counterReducer, { count: 0 });
  let (globalState, globalDispatch) = GlobalCount.useGlobalCount();

  let countMsg = "Count: " ++ string_of_int(state.count);
  let persistentCountMsg = "Persistent Count " ++ string_of_int(globalState);

  <div>
    <p> {ReasonReact.string(countMsg)} </p>
    <button onClick={_ => dispatch(Add)}> {React.string("Add")} </button>
    <p> {ReasonReact.string(persistentCountMsg)} </p>
    <button onClick={_ => globalDispatch(GlobalCount.Increment)}>
      {React.string("Add")}
    </button>
  </div>;
};

let default = make;

Here the important things to notice are how the usage of the GlobalCount module maps directly to the usage of the React.useReducer module.

Hopefully this is a helpful example usage of custom hooks.