0

I'm trying to create a Switch component in react that uses a custom hook. I want other components to use that same custom hook so that they are updated when the Switch is clicked. To do that I've created this custom hook:

function useToggle() {
  const [isToggled, setIsToggled] = useState(false);

  const toggle = React.useCallback(
    () => setIsToggled(state => !state),
    [setIsToggled],
  );

  return [isToggled, toggle];
}

Then in the Switch component I want to subscribe to this custom hook I do:

const Switch = () => {
  const [isToggled, toggle] = useToggle();
  return (
         <Switch
          onChange={toggle as any}
          checked={isToggled as boolean}
          ...
        />

  );
}

Then in the components that change value depending on whether the Switch is toggled, I have:

const PricingHeader = () => {
   // Subscribe to switch's event
   const [isToggled, toggle] = useToggle();

   return (<Price showSpecialPrice={isToggled} />);
}

Problem? The components update independently. I can click the switch and I see it render differently as its value is toggled, but I don't see Price show a different price when switch is toggled. Price is not affected at all whenever I click the Switch.

Not sure what I'm doing wrong? I imagine the isToggled state returned is different every time useToggle is used. Is what I'm trying to do even possible?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Chayemor
  • 3,577
  • 4
  • 31
  • 54

2 Answers2

1

Its possible, you can share your toggle state by Context API:

const SwitchContext = createContext();

function useToggle() {
  return useContext(SwitchContext);
}

const Switch = () => {
  const [isToggled, toggle] = useToggle();
  return <button onClick={toggle}>{isToggled ? "ON" : "OFF"}</button>;
};

const Price = () => {
  const [isToggled] = useToggle();
  return <>The price {isToggled ? "IS TOGGLED" : "IS NOT TOGGLED"}</>;
};

export default function App() {
  const [isToggled, toggle] = useReducer((p) => !p, false);
  return (
    <SwitchContext.Provider value={[isToggled, toggle]}>
      <Switch />
      <Price />
    </SwitchContext.Provider>
  );
}

Edit Singleton Hook Example

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • 1
    I like your use of `useReducer` here! I have a couple remarks 1) you should provide a default value for the context, this provides a value for any component that uses the context, even if not a descendant of a provider. 2) you shouldn't host code offsite when it's possible to host it here. links become outdated and eventually stop working. Cheers :D – Mulan Jul 02 '21 at 16:38
1

I might recommend you use React Context for this. You can create a ToggleContext using createContext and use it with useContext -

const { createContext, useContext, useState } = React

const ToggleContext = createContext(false)

function useToggle (initialState) {
  const [isToggle, setToggle] = useState(initialState)
  const toggle = () => setToggle(value => !value)
  return [isToggle, toggle]
}

function MyApp () {
  const [isToggle, toggle] = useToggle(false)
  return (
    <ToggleContext.Provider value={isToggle}>
      <p>Click any switch</p>
      <Switch onClick={toggle} />
      <Switch onClick={toggle} />
      <Switch onClick={toggle} />
    </ToggleContext.Provider>
  )
}

function Switch ({ onClick }) {
  const isToggle = useContext(ToggleContext)
  return <input type="checkbox" onClick={onClick} checked={isToggle} />
}

ReactDOM.render(<MyApp />, document.querySelector("main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
Mulan
  • 129,518
  • 31
  • 228
  • 259