Is it possible to share states between components using the useState() hook in React?

JavascriptReactjsReact Hooks

Javascript Problem Overview


I was experimenting with the new Hook feature in React. Considering I have the following two components (using React Hooks) -

const HookComponent = () => {
  const [username, setUsername] = useState('Abrar');
  const [count, setState] = useState();
  const handleChange = (e) => {
    setUsername(e.target.value);
  }

  return (
    <div>
      <input name="userName" value={username} onChange={handleChange}/>
      <p>{username}</p>
      <p>From HookComponent: {count}</p>
    </div>
  )
}


const HookComponent2 = () => {
  const [count, setCount] = useState(999);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Hooks claim to solve the problem of sharing stateful logic between components but I found that the states between HookComponent and HookComponent2 are not sharable. For example the change of count in HookComponent2 does not render a change in the HookComponent.

Is it possible to share states between components using the useState() hook?

Javascript Solutions


Solution 1 - Javascript

If you are referring to component state, then hooks will not help you share it between components. Component state is local to the component. If your state lives in context, then useContext hook would be helpful.

Fundamentally, I think you misunderstood the line "sharing stateful logic between components". Stateful logic is different from state. Stateful logic is stuff that you do that modifies state. For e.g., a component subscribing to a store in componentDidMount() and unsubscribing in componentWillUnmount(). This subscribing/unsubscribing behavior can be implemented in a hook and components which need this behavior can just use the hook.

If you want to share state between components, there are various ways to do so, each with its own merits:

1. Lift State Up

Lift state up to a common ancestor component of the two components.

function Ancestor() {
    const [count, setCount] = useState(999);
    return <>
      <DescendantA count={count} onCountChange={setCount} />
      <DescendantB count={count} onCountChange={setCount} />
    </>;
  }

This state sharing approach is not fundamentally different from the traditional way of using state, hooks just give us a different way to declare component state.

2. Context

If the descendants are too deep down in the component hierarchy and you don't want to pass the state down too many layers, you could use the Context API.

There's a useContext hook which you can leverage on within the child components.

3. External State Management Solution

State management libraries like Redux or Mobx. Your state will then live in a store outside of React and components can connect/subscribe to the store to receive updates.

Solution 2 - Javascript

It is possible without any external state management library. Just use a simple observable implementation:

function makeObservable(target) {
  let listeners = []; // initial listeners can be passed an an argument aswell
  let value = target;

  function get() {
    return value;
  }

  function set(newValue) {
    if (value === newValue) return;
    value = newValue;
    listeners.forEach((l) => l(value));
  }

  function subscribe(listenerFunc) {
    listeners.push(listenerFunc);
    return () => unsubscribe(listenerFunc); // will be used inside React.useEffect
  }

  function unsubscribe(listenerFunc) {
    listeners = listeners.filter((l) => l !== listenerFunc);
  }

  return {
    get,
    set,
    subscribe,
  };
}

And then create a store and hook it to react by using subscribe in useEffect:

const userStore = makeObservable({ name: "user", count: 0 });

const useUser = () => {
  const [user, setUser] = React.useState(userStore.get());

  React.useEffect(() => {
    return userStore.subscribe(setUser);
  }, []);

  const actions = React.useMemo(() => {
    return {
      setName: (name) => userStore.set({ ...user, name }),
      incrementCount: () => userStore.set({ ...user, count: user.count + 1 }),
      decrementCount: () => userStore.set({ ...user, count: user.count - 1 }),
    }
  }, [user])

  return {
    state: user,
    actions
  }
}

And that should work. No need for React.Context or lifting state up

Solution 3 - Javascript

This is possible using the useBetween hook.

See in codesandbox

import React, { useState } from 'react';
import { useBetween } from 'use-between';

const useShareableState = () => {
  const [username, setUsername] = useState('Abrar');
  const [count, setCount] = useState(0);
  return {
    username,
    setUsername,
    count,
    setCount
  }
}


const HookComponent = () => {
  const { username, setUsername, count } = useBetween(useShareableState);

  const handleChange = (e) => {
    setUsername(e.target.value);
  }

  return (
    <div>
      <input name="userName" value={username} onChange={handleChange}/>
      <p>{username}</p>
      <p>From HookComponent: {count}</p>
    </div>
  )
}


const HookComponent2 = () => {
  const { count, setCount } = useBetween(useShareableState);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

We move React hooks stateful logic from HookComponent to useShareableState. We call useShareableState using useBetween in each component.

useBetween is a way to call any hook. But so that the state will not be stored in the React component. For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.

Disclaimer: I'm the author of the use-between package.

Solution 4 - Javascript

the doc states:

> We import the useState Hook from React. It lets us keep local state in a function component.

it is not mentioned that the state could be shared across components, useState hook just give you a quicker way to declare a state field and its correspondent setter in one single instruction.

Solution 5 - Javascript

I've created hooksy that allows you to do exactly this - https://github.com/pie6k/hooksy

import { createStore } from 'hooksy';

interface UserData {
  username: string;
}

const defaultUser: UserData = { username: 'Foo' };

export const [useUserStore] = createStore(defaultUser); // we've created store with initial value.
// useUserStore has the same signature like react useState hook, but the state will be shared across all components using it

And later in any component

import React from 'react';

import { useUserStore } from './userStore';

export function UserInfo() {
  const [user, setUser] = useUserStore(); // use it the same way like useState, but have state shared across any component using it (eg. if any of them will call setUser - all other components using it will get re-rendered with new state)
  
  function login() {
    setUser({ username: 'Foo' })
  }

  return (
    <div>
      {!user && <strong>You're logged out<button onPress={login}>Login</button></strong>}
      {user && <strong>Logged as <strong>{user.username}</strong></strong>}
    </div>
  );
}

Solution 6 - Javascript

With hooks its not directly possible. I recommend you to take a look at react-easy-state. https://github.com/solkimicreb/react-easy-state

I use it in big Apps and it works like a charm.

Solution 7 - Javascript

You will still need to lift your state up to an ancestor component of HookComponent1 and HookComponent2. That's how you share state before and the latest hook api doesnt change anything about it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAbrarView Question on Stackoverflow
Solution 1 - JavascriptYangshun TayView Answer on Stackoverflow
Solution 2 - JavascriptstreletssView Answer on Stackoverflow
Solution 3 - JavascriptSlava BirchView Answer on Stackoverflow
Solution 4 - JavascriptKarimView Answer on Stackoverflow
Solution 5 - JavascriptAdam PietrasiakView Answer on Stackoverflow
Solution 6 - JavascriptMarcel EnnixView Answer on Stackoverflow
Solution 7 - JavascriptYongzhiView Answer on Stackoverflow