0

I am trying to implement custom global state hook based on the article here State Management with React Hooks — No Redux or Context API. I keep getting double renders. It seems to be with the following piece of code:

function useCustom() {
  const newListener = useState()[1];
  effect(() => {
    this.listeners.push(newListener);
    return () => {
      this.listeners = this.listeners.filter(
        listener => listener !== newListener
      );
    };
  }, []);
  return [this.state, this.setState, this.actions];
}

If you console log inside this piece of code you can see it running twice at initial render and also twice every time you update the hook.

Any help on how to fix this would be much appreciated.

Here is the full code:

CodeSandbox

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

const effect = typeof window === "undefined" ? useEffect : useLayoutEffect;

function setState(newState) {
  if (newState === this.state) return;
  this.state = newState;
  this.listeners.forEach(listener => {
    listener(this.state);
  });
}

function useCustom() {
  const newListener = useState()[1];
  effect(() => {
    this.listeners.push(newListener);
    return () => {
      this.listeners = this.listeners.filter(
        listener => listener !== newListener
      );
    };
  }, []);
  return [this.state, this.setState, this.actions];
}

function associateActions(store, actions) {
  const associatedActions = {};
  if (actions) {
    Object.keys(actions).forEach(key => {
      if (typeof actions[key] === "function") {
        associatedActions[key] = actions[key].bind(null, store);
      }
      if (typeof actions[key] === "object") {
        associatedActions[key] = associateActions(store, actions[key]);
      }
    });
  }
  return associatedActions;
}

const useGlobalHook = (initialState, actions) => {
  const store = { state: initialState, listeners: [] };
  store.setState = setState.bind(store);
  store.actions = associateActions(store, actions);
  return useCustom.bind(store, React);
};

export default useGlobalHook;

Then set up the store like so:

import useGlobalState from './useGlobalState';

const initialState = false;

const useValue = useGlobalState(initialState);

export default useValue;

And the component

import React from 'react';
import useValue from '../store/useValue';

const Component1 = () => {
    const [value, setValue] = useValue();
    console.log('rendered component');
    return (
        <div>
            <p>Value1: {value ? 'true' : 'false'}</p>
            <button onClick={() => setValue(!value)}>Toggle Me</button>
        </div>
    );
};

export default Component1;
user3331344
  • 728
  • 8
  • 23
  • In your `index.js` `` is wrapped in React.StrictMode which will intentionally double-invoke render methods https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects. If you remove this, I think it works as you expected. – user2340824 Aug 02 '20 at 03:28
  • @user2340824 I didn't even look at that it was definitely the issue. I just used codesandbox's react template and it is obviously set by default in their template. Thanks I've literally been looking at this for hours. – user3331344 Aug 02 '20 at 03:32
  • Does this answer your question? [Why does useState cause the component to render twice on each update?](https://stackoverflow.com/questions/61578158/why-does-usestate-cause-the-component-to-render-twice-on-each-update) – Drew Reese Aug 02 '20 at 03:32

0 Answers0