1

I would like to have a global variable that I can edit anywhere using hooks.

In the example I have 2 components both using the same hook. It seems to me that the External toggle is editing its own scoped count and Internal Toggle is changing its own scope also.

Is it possible have both toggles edit the same data?

Code example: https://codesandbox.io/s/520zvyjwlp

index.js

function ChangeCount() {
  const { count, increment } = useCounter();
  return <button onClick={() => increment(!count)}>External Toggle</button>;
}

function App() {
  const { count, increment } = useCounter();
  return (
    <div>
      {`${count}`}
      <br />
      <ChangeCount />
      <br />
      <button onClick={() => increment(!count)}>Internal Toggle</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement); 

useCount.js

import { useState } from "react";
export default function useCounter() {
  const [count, setCount] = useState(false);
  const increment = (changeCount) => setCount(changeCount);
  return { count, increment };
}
Tholle
  • 108,070
  • 19
  • 198
  • 189
Jamie Hutber
  • 26,790
  • 46
  • 179
  • 291

2 Answers2

2

As you've noticed custom hooks is for sharing stateful logic, not actual state.

If you want to share a piece of state you can use the context feature and pass the count variable and the increment function in an object to the value prop of the Provider and consume it further down the tree with useContext.

Example

const { createContext, useContext, useState } = React;
const CounterContext = createContext();

function ChangeCount() {
  const { increment } = useContext(CounterContext);
  return <button onClick={increment}>External increment</button>;
}

function App() {
  const [count, setCount] = useState(0);
  function increment() {
    setCount(count + 1);
  }

  return (
    <CounterContext.Provider value={{ count, increment }}>
      <div>{count}</div>
      <ChangeCount />
      <button onClick={increment}>Internal increment</button>
    </CounterContext.Provider>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • Thanks @tholle. My biggest problem is that I am unable to wrap the `data` that I need with a provider. The particular thing I am trying to achieve is to have my globally accessible `` component display the loading icon whenever data is edited, and then turn it off once the loading as finished. I fear I am going to have to use `mobx` for this now. – Jamie Hutber Mar 22 '19 at 15:08
  • @JamieHutber You could use context with a `Provider` with e.g. `isLoading, setLoading`, and your `` component uses `isLoading` through context to show the loading indicator or not, and other components can use `setLoading` to change its value. – Tholle Mar 22 '19 at 15:12
  • Interesting, I did actually try this I believe: https://stackoverflow.com/questions/55299339/react-context-doesnt-work-using-provider there is an example in there too. – Jamie Hutber Mar 22 '19 at 15:13
  • 1
    @JamieHutber Ah, I see. [You could do something like this](https://codesandbox.io/s/jvpqz3l81v). – Tholle Mar 22 '19 at 15:24
  • Great work around Tholle. It feels like a slight anti pattern though I must say. I assume most of the time the context and hook api's are really designed to be used this way. Which strikes me as odd, I always saw this as a redux replacement no? – Jamie Hutber Mar 22 '19 at 23:16
1

To complete this task, you should share your state via the context API,

consider the following:

const CounterContext = React.createContext({
  count: 0,
  increment: () => null,
});

const changeCount = () => {
  const counter = useContext(CounterContext);

  return <button onClick={() => counter.increment(!counter.count)}>External Toggle</button>;
}

const App = () => {
  const { count, increment } = useCounter();
  return (
    <CounterContext.Provider value={{ count, increment }}>
      {`${count}`}
      <br />
      <ChangeCount />
      <br />
      <button onClick={() => increment(!count)}>Internal Toggle</button>
    </CounterContext.Provider>
  );
}

for more information please visit: https://reactjs.org/docs/context.html

Jose Munoz
  • 558
  • 2
  • 12