javascriptreactjsreact-hooksuse-contextuse-reducer

'dispatch' is not defined when using useReducer with useContext in react


I'm trying to figure out how to change global state from a componenet using useContext and useReducer dispatch method.

The component is simply should change the backgournd of the page on a click

Here is how I defined the context ThemeContext.js

import { createContext, useReducer } from "react";
import ThemeReducer from './ThemeReducer'

const INITIAL_STATE = {
    isLightTheme: true,
    light: {syntax: '#555', ui: '#ddd', bg: '#eee'},
    dark: {syntax: '#ddd', ui: '#333', bg: '#555'},
}

export const ThemeContext = createContext(INITIAL_STATE);

const ThemeContextProvider = ({ children }) => {
   const [state, dispatch] = useReducer(ThemeReducer, INITIAL_STATE);
    return ( 
        <ThemeContext.Provider value={{
            isLightTheme: state.isLightTheme,
            light: state.light,
            dark:  state.dark,
            dispatch,
        }}>
            {children}
        </ThemeContext.Provider>
     );
}     
export default ThemeContextProvider;

The ThemeReducer.js is:

const ThemeReducer = (state, action) => {
    switch (action.type) {
      case "SET_DARK":
        return {
            isLightTheme: false,
        };    
      case "SET_LIGHT":
            return {
                isLightTheme: true,
            }; 
      default:
        return state;
    }
  };  
  export default ThemeReducer;

app.js:

function App() {
  return (
    <div className="App">
       <ThemeContextProvider>        
          <Navbar />
          <BookList />
          <ThemeToggle />
       </ThemeContextProvider>
    </div>
  );
}

export default App;

And the ThemeToggle.js compoenent

const ThemeToggle = () => {

    return ( 
        <button onClick={()=>dispatch({type: "SET_DARK"})}>Change theme</button>
     );
}
 
export default ThemeToggle;

However I get this error:

src/components/ThemeToggle.jsx
  Line 6:30:  'dispatch' is not defined 

I don't understand why. Because dispatch is supposed to be in the context. I'm wondering what is wrong here and how can I fix it?

P.S BooKList compoenent.

import  { useContext } from 'react'
import { ThemeContext } from '../context/ThemeContext';


const BookList = () => {
    const {isLightTheme, light, dark} = useContext(ThemeContext)
    const theme = isLightTheme ? light : dark;
    return ( 
        <div  style={{background : theme.ui , color: theme.syntax}}>
            <ul>
                <li stryle={{background: theme.ui}} >The way of kings</li>
                <li stryle={{background: theme.ui}} >The namoe fot the wind</li>
                <li stryle={{background: theme.ui}} >The Final empire</li>
            </ul>
        </div>
     );
}

Solution

  • It appears you are missing accessing the ThemeContext in ThemeToggle. Use the useContext hook to access the ThemeContext Context value and destructure the dispatch function.

    const ThemeToggle = () => {
      const { dispatch } = useContext(ThemeContext);
      return ( 
        <button onClick={() => dispatch({type: "SET_DARK"})}>
          Change theme
        </button>
      );
    }
     
    export default ThemeToggle;
    

    And for completeness' sake, add dispatch to the default context value.

    const INITIAL_STATE = {
      isLightTheme: true,
      light: {syntax: '#555', ui: '#ddd', bg: '#eee'},
      dark: {syntax: '#ddd', ui: '#333', bg: '#555'},
      dispatch: () => {},
    }
    
    export const ThemeContext = createContext(INITIAL_STATE);
    

    The ThemeReducer reducer function is stripping out state in the set light/dark cases. You need to preserve the existing state.

    const ThemeReducer = (state, action) => {
      switch (action.type) {
        case "SET_DARK":
          return {
            ...state,
            isLightTheme: false,
          };
      
        case "SET_LIGHT":
          return {
            ...state,
            isLightTheme: true,
          };
    
        default:
          return state;
      }
    };