2

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>
     );
}
MBH
  • 109
  • 1
  • 8

1 Answers1

1

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;
  }
};  
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Well I've already passed `dispatch` to `ThemeContextProvider`. Isn't it supposed to be available to all child compoenents of the app? – MBH Feb 09 '22 at 06:17
  • 1
    @MBH Yes, but the default value is for any components that attempt to access the context outside any provider (*usually by mistake*). – Drew Reese Feb 09 '22 at 06:19
  • This removes the erorr, but then I get another error in `BookList` component: `Uncaught TypeError: Cannot read properties of undefined (reading 'ui')` – MBH Feb 09 '22 at 06:22
  • @MBH I see. Does that seem related to your theme context? I do see a `ui` property in the light and dark theme objects. Is `BookList` also trying to access the theme context? – Drew Reese Feb 09 '22 at 06:23
  • 1
    Yes, just added the component to the question. Please see above. – MBH Feb 09 '22 at 06:24
  • 1
    @MBH Ah, yeah, your reducer function isn't shallow copying the previous state into the next state objects it returns. – Drew Reese Feb 09 '22 at 06:28
  • Right, this resolved the problem. Though TBH I still don't get it why I must add `dispatch` to `INITIAL_STATE` too. – MBH Feb 09 '22 at 06:32
  • 1
    @MBH I think it's mostly a by-convention thing. See https://reactjs.org/docs/context.html#updating-context-from-a-nested-component where this is suggested. I think code will still work without it, but it's good to be explicit. – Drew Reese Feb 09 '22 at 06:34