RVRJ
RVRJ

Reputation: 135

React does not re-render all componenets on state update in a custom hook

I've created 2 components C1 and C2 and one custom hook useCounter.

C1 displays "count" property of useCounter hook.

C2 displays "count" property and also increment or decrement it on button click.

Current behavior: When the "count" is changed, the updated value is displayed only in C2 and not in C1.

Expected behavior: Both components should re-render on the "count" update.

Please let me know if I'm missing something.

PS: I've already done this using Context API and redux. Just want to know if the same behavior could be achieved using custom hooks :)

Codesandbox link: Custom hooks demo

import { useState } from "react";

function useCounter() {
  const [count, setCount] = useState(0);
  const updateCounter = newVal => {
    setCount(newVal);
  };

  //return [count, useCallback( (newVal) => setCount(newVal))];
  return [count, updateCounter];
}
export default useCounter;

Upvotes: 1

Views: 182

Answers (3)

RVRJ
RVRJ

Reputation: 135

Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.

Refer point #3 in Using a Custom Hook


They’re not a way to share state — but a way to share stateful logic. We don’t want to break the top-down data flow!

Reference: Making Sense of React Hooks

Upvotes: 0

Vinay Pandya
Vinay Pandya

Reputation: 3160

Hey I am totally agree with @RVRJ's explanations. So what happens when you import any hooks it will create new object of that hook. Let's suppose if you import same hook in two different files that means you are creating two difference object of that hook.

Here I have tried to solve your problem using hooks only, but I am importing hook only once and passed its object to child component <C1 /> and <C2 />.

Here is example how I created single object of useCounter hook

import React from "react";
import C1 from "./components/C1";
import C2 from "./components/C2";
import useCounter from "./hooks/useCounter";

function App() {
  const [count, updateCount] = useCounter(); // <<-- created one object

  return (
    <div className="App">
      <h1>App Component</h1>
      <hr />
      <C1 count={count} updateCount={updateCount} /> {/* passing values ad props */}
      <hr />
      <C2 count={count} updateCount={updateCount} /> {/* passing values ad props */}
    </div>
  );
}

export default App;

and now you can access count and updateCount as props in every child.

Here is C1 component after change

// C1 component

 import React from "react";

 function C1({ count }) { {/* <-- access count as props */}
   return (
     <div>
       Component 1 <br />
       Count: {count}
     </div>
   );
 }
 export default C1;

And here is your C2 component

// C2 component
import React from "react";

function C3({ count, updateCount }) { {/* <-- access count and as updateCount props */}
  const handleIncr = () => {
    updateCount(count + 1);
  };
  const handleDecr = () => {
    updateCount(count - 1);
  };
  return (
    <div>
      Component 2 <br />
      <button onClick={handleIncr}>Increment</button>
      <button onClick={handleDecr}>Decrement</button>
      <br />
      <br />
      <br />
      Count: {count}
    </div>
  );
}
export default C3;

Here is updated and working solutions of your problem https://codesandbox.io/s/romantic-fire-b3exw

Note: I don't know what is use case of using hooks for same state values, so I still recommend you should use redux for sharing states between components.

Upvotes: 1

Dennis Vash
Dennis Vash

Reputation: 53994

You might want to use Context API to share the same instance, as the custom hook useCounter will assign a new instance of count on mount:

export const CounterContext = React.createContext();

function App() {
  const counterController = useCounter();
  return (
    <CounterContext.Provider value={counterController}>
      <div className="App">
        <h1>App Component</h1>
        <hr />
        <C1 />
        <hr />
        <C2 />
      </div>
    </CounterContext.Provider>
  );
}

// Use context
function C1() {
  const [count] = useContext(CounterContext);
  return (
    <div>
      Component 1 <br />
      Count: {count}
    </div>
  );
}

Edit nostalgic-hypatia-by6s4


Additionally, you can use a library like reusable:

const useCounter = createStore(() => {
  const [counter, setCounter] = useState(0);

  return {
    counter,
    increment: () => setCounter(prev => prev + 1)
  }
});

const Comp1 = () => {
  const something = useCounter();
}

const Comp2 = () => {
  const something = useCounter(); // same something
}

Upvotes: 2

Related Questions