Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: is this a good way to implement a shared state subscription?

Not sure if this is a so-called "pub/sub" pattern or a form of a "pub/sub" pattern. I am trying to create a piece of shared state so that different components can subscribe to it and only gets updated when there is an update with that state.

const useForceUpdate = () => useReducer((state) => !state, false)[1];

const createSharedState = (reducer, initialState) => {
  const subscribers = [];
  let state = initialState;
  const dispatch = (action) => {
    state = reducer(state, action);
    subscribers.forEach((callback) => callback());
  };
  const useSharedState = () => {
    const forceUpdate = useForceUpdate();
    useEffect(() => {
      const callback = () => forceUpdate();
      subscribers.push(callback);
      const cleanup = () => {
        const index = subscribers.indexOf(callback);
        subscribers.splice(index, 1);
      };
      return cleanup;
    }, []);
    return [state, dispatch];
  };
  return useSharedState;
};

const initialState = 0;
const reducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "set":
      return action.count;
    default:
      return state;
  }
};

const useCount1 = createSharedState(reducer, initialState);
const useCount2 = createSharedState(reducer, initialState);

const Counter = ({ count, dispatch }) => (
  <div>
    {count}
    <button onClick={() => dispatch({ type: "increment" })}>+1</button>
    <button onClick={() => dispatch({ type: "decrement" })}>-1</button>
    <button onClick={() => dispatch({ type: "set", count: 0 })}>reset</button>
  </div>
);

const Counter1 = () => {
  const [count, dispatch] = useCount1();
  return <Counter count={count} dispatch={dispatch} />;
};

const Counter2 = () => {
  const [count, dispatch] = useCount2();
  return <Counter count={count} dispatch={dispatch} />;
};

const Example = () => (
  <>
    <Counter1 />
    <Counter1 />
    <Counter2 />
    <Counter2 />
  </>
);

<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="root"></div>

<script type="text/babel">
const { useEffect, useReducer } = React;

const useForceUpdate = () => useReducer((state) => !state, false)[1];

const createSharedState = (reducer, initialState) => {
  const subscribers = [];
  let state = initialState;
  const dispatch = (action) => {
    state = reducer(state, action);
    subscribers.forEach((callback) => callback());
  };
  const useSharedState = () => {
    const forceUpdate = useForceUpdate();
    useEffect(() => {
      const callback = () => forceUpdate();
      subscribers.push(callback);
      const cleanup = () => {
        const index = subscribers.indexOf(callback);
        subscribers.splice(index, 1);
      };
      return cleanup;
    }, []);
    return [state, dispatch];
  };
  return useSharedState;
};

const initialState = 0;
const reducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "set":
      return action.count;
    default:
      return state;
  }
};

const useCount1 = createSharedState(reducer, initialState);
const useCount2 = createSharedState(reducer, initialState);

const Counter = ({ count, dispatch }) => (
  <div>
    {count}
    <button onClick={() => dispatch({ type: "increment" })}>+1</button>
    <button onClick={() => dispatch({ type: "decrement" })}>-1</button>
    <button onClick={() => dispatch({ type: "set", count: 0 })}>reset</button>
  </div>
);

const Counter1 = () => {
  const [count, dispatch] = useCount1();
  return <Counter count={count} dispatch={dispatch} />;
};

const Counter2 = () => {
  const [count, dispatch] = useCount2();
  return <Counter count={count} dispatch={dispatch} />;
};

const Example = () => (
  <>
    <Counter1 />
    <Counter1 />
    <Counter2 />
    <Counter2 />
  </>
);

ReactDOM.render(<Example />, document.querySelector("#root"));
</script>

It seems to be working fine. My questions are:

  1. Is this a valid way to implement shared update subscription?
  2. Is there any drawbacks with using a simple variable to hold the state + forcing React to re-render if that piece of state changes, instead of using useState or useReducer as one normally would do?
  3. any feedback is welcomed.
like image 746
Joji Avatar asked Mar 13 '21 01:03

Joji


People also ask

What is the best way to manage state in React?

Local state is perhaps the easiest kind of state to manage in React, considering there are so many tools built into the core React library for managing it. useState is the first tool you should reach for to manage state in your components. It can take accept any valid data value, including primitive and object values.

Should I use state management in React?

Why do you need React state management? React applications are built using components and they manage their state internally and it works well for applications with few components, but when the application grows bigger, the complexity of managing states shared across components becomes difficult.

How do I share a state in React?

In React, sharing state is accomplished by moving it up to the closest common ancestor of the components that need it. This is called “lifting state up”. We will remove the local state from the TemperatureInput and move it into the Calculator instead.

Is it good to use jQuery in React?

React uses virtual DOM to display the details and Jquery is used to manipulate the DOM. So if you are using Jquery with React then you will be basically manipulating the Virtual DOM which is not recommended. Happy Coding!.

How to manage state with hooks on React components?

If you are new to these Hooks, check out How To Manage State with Hooks on React Components. The useReducer Hook is a good fit since you’ll need to update the most recent state on every action. Create a reducer function that adds a new item to a state array, then use the useReducer Hook to create a salad array and a setSalad function:

What is global state in react?

Global state in React is synonymous with libraries like Redux. If you ever needed to share state like the current route or data from an API with multiple components, then you may have reached for Redux yourself. Newer versions of React (16.3+) include a built-in way to share state, which means not having to pull in an external library.

How do I update state of a React component?

To update data, you’ll need to use other state management tools such as Hooks. If you were collecting data for the same component, you’d use either the useState or useReducer Hooks. If you are new to these Hooks, check out How To Manage State with Hooks on React Components.

What is the react Context API?

Newer versions of React (16.3+) include a built-in way to share state, which means not having to pull in an external library. This is known as the React Context API and it can be a bit tricky to learn. I hope to provide a simplified explanation and tutorial so that you can quickly add global state to any of your React apps.


1 Answers

  1. Your idea is excellent. React team was also thinking on this topic and ended up with the creation of https://recoiljs.org/. You can use it as useState (DEMO) or as useReducer (DEMO).
  2. I don't want to highlight your solution's drawbacks. Instead, I'd like to list the advantages of using Recoil:
  • Internal memory usage optimization.
  • No need to support the code (Facebook does it).
  • No cheating (useForceUpdate).
  • Supports selectors out of the box.
  1. I'd recommend you to learn more about Recoil and start using it because it gives the exact result you want to achieve.
like image 162
teimurjan Avatar answered Oct 03 '22 15:10

teimurjan