1
votes

I am not able to get an updated state from the custom hook. the example is simple. this is a custom hook allowing to switch from dark to light theme inside the custom hook, I use useMemo to return a memoized value, but this does not seem to update each time I update the state with switchOn.

I do not wish to use a context in this example

import { useState, useMemo, useCallback } from "react";

const useTheme = (initial) => {
  const [isOn, turnOn] = useState(false);
  const switchOn = useCallback((e) => turnOn(e.target.checked), [turnOn]);
  return useMemo(() => {
    return {
      theme: isOn ? "light" : "dark",
      buttonTheme: isOn ? "dark" : "light",
      lightsIsOn: isOn ? "On" : "Off",
      isChecked: isOn,
      switchOn,
    };
  }, [switchOn, isOn]);
};
export default useTheme;

import { useState, useRef, useReducer } from "react";
import useTheme from "./useTheme";
import todos from "./reducer";
import "./App.css";

const Item = ({ id, text, done, edit, remove }) => {
  const { buttonTheme } = useTheme();
  const isDone = done ? "done" : "";
  return (
    <div style={{ display: "flex" }}>
      <li className={isDone} key={id} onClick={() => edit(id)}>
        {text}
      </li>
      &nbsp;
      <button type="button" onClick={() => remove(id)} className={buttonTheme}>
        <small>x</small>
      </button>
    </div>
  );
};

const SwitchInput = () => {
  const { switchOn, isChecked, lightsIsOn } = useTheme();
  return (
    <>
      <label class="switch">
        <input type="checkbox" onChange={switchOn} checked={isChecked} />
        <span class="slider round"></span>
      </label>
      &nbsp;
      <span>
        {" "}
        Lights <b>{lightsIsOn}</b>
      </span>
      <br /> <br />
    </>
  );
};

const initialState = {
  items: [],
};
const init = (state) => {
  return {
    ...state,
    items: [
      { id: 1, text: "Learning the hooks", done: false },
      { id: 2, text: "Study JS", done: false },
      { id: 3, text: "Buy conference ticket", done: false },
    ],
  };
};
function App() {
  const inputRef = useRef();
  const [state, dispatch] = useReducer(todos, initialState, init);
  const [inputValue, setInputValue] = useState(null);
  const { theme } = useTheme();

  const handleOnChange = (e) => setInputValue(e.target.value);
  const handleOnSubmit = (e) => {
    e.preventDefault();
    dispatch({ type: "add", payload: inputValue });
    inputRef.current.value = null;
  };

  return (
    <div className={`App ${theme}`}>
      <SwitchInput />
      <form onSubmit={handleOnSubmit}>
        <input ref={inputRef} type="text" onChange={handleOnChange} />
        <ul>
          {state.items.map((item) => (
            <Item
              {...item}
              key={item.id}
              remove={(id) => dispatch({ type: "remove", payload: { id } })}
              edit={(id) => dispatch({ type: "edit", payload: { id } })}
            />
          ))}
        </ul>
      </form>
    </div>
  );
}
export default App;
  1. my custom hook: useTheme.js
  2. below, the component where I want to access logic from custom hook: App.js

I use an App.css to apply theme style : dark and light

below a demo. We can see that the values do not change

enter image description here

How is an effective way to subscribe to state changes share global states between components?

1

1 Answers

1
votes

You need to pass the values of useTheme() from <App> to <SwitchInput> to share it as follows.

function App() {
  const { theme, switchOn, isChecked, lightsIsOn } = useTheme();

  return (
    <div className={`App ${theme}`}>
      <SwitchInput
        switchOn={switchOn}
        isChecked={isChecked}
        lightsIsOn={lightsIsOn}
      />
    </div>
  );
}

Then, <SwitchInput> will receive them in props.

const SwitchInput = ({ switchOn, isChecked, lightsIsOn }) => {
  return (
    <>
      <label class="switch">
        <input type="checkbox" onChange={switchOn} checked={isChecked} />
        <span class="slider round"></span>
      </label>
      &nbsp;
      <span>
        {" "}
        Lights <b>{lightsIsOn}</b>
      </span>
      <br /> <br />
    </>
  );
};

In your example, useTheme() is called in both <App> and <SwitchInput>, but theme and other values are initialized separately and are not shared between each component.