0

I'm trying to set up a theme provider for a little project. Each time the user click on a button the app theme (color) is supposed to change. Let's say I've got four themes in a separate file like this:

const themesArray = [
    { primaryColor: '#483639', secondaryColor: '#F7F7F7' },
    { primaryColor: '#fb7051', secondaryColor: '#333' },
    { primaryColor: '#60b494', secondaryColor: '#F7F7F7' },
    { primaryColor: '#afb5c0', secondaryColor: '#333' }
];

export default themesArray;

Each time the user click on a button (which is nested in a specific component) a counter increments until 3 like below:

import React, { useState, useContext } from 'react';
import themesArray from '../../styles/themesArray';

const ThemeContext = React.createContext();
const ThemeUpdateContext = React.createContext();

const useTheme = () => useContext(ThemeContext);
const useThemeUpdate = () => useContext(ThemeUpdateContext);

const ThemeProvider = ({ children }) => {

    const [state, setState] = useState({
        count: 0,
        primaryColor: themesArray[0].primaryColor,
        secondaryColor: themesArray[0].secondaryColor
    });

    const toggleTheme = () => {
        setState(prevState => ({
            count: prevState.count >= 3 ? 0 : ++prevState.count,
            primaryColor: themesArray[prevState.count].primaryColor,
            secondaryColor: themesArray[prevState.count].secondaryColor
        }));
    }

    return (
        <ThemeContext.Provider value={[state.primaryColor, state.secondaryColor]}>
            <ThemeUpdateContext.Provider value={toggleTheme}>
                {children}
            </ThemeUpdateContext.Provider>
        </ThemeContext.Provider>
    );

}

export { ThemeProvider, useTheme, useThemeUpdate };

The counter seems to work fine, but the problem is I never get back to my first object values (I never get back to the first theme colors). I mean when I console.log(state.count, state.primaryColor, state.secondaryColor) I get this:

0 #483639 #F7F7F7 ThemeContext.js:24
1 #fb7051 #333 ThemeContext.js:24
2 #60b494 #F7F7F7 ThemeContext.js:24
3 #afb5c0 #333 ThemeContext.js:24
0 #afb5c0 #333 ThemeContext.js:24
1 #fb7051 #333 ThemeContext.js:24
2 #60b494 #F7F7F7 ThemeContext.js:24
3 #afb5c0 #333 ThemeContext.js:24
0 #afb5c0 #333 ThemeContext.js:24

Do you know why first object values are overwritten by last object values each time the counter value is back to 0?

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
chalatum
  • 67
  • 1
  • 3
  • 9

1 Answers1

2

You are mutating the state ++prevState.count which is a problem.

Treat state as if it were immutable

Moreover you can use modulo instead:

const toggleTheme = () => {
    setState(prevState => {
      const nextCount = prevState.count + 1;
      return {
        count: nextCount % 3,
        primaryColor: themesArray[nextCount].primaryColor,
        secondaryColor: themesArray[nextCount].secondaryColor
      };
    });
  };

Full example:


const themesArray = [
  { primaryColor: "#483639", secondaryColor: "#F7F7F7" },
  { primaryColor: "#fb7051", secondaryColor: "#333" },
  { primaryColor: "#60b494", secondaryColor: "#F7F7F7" },
  { primaryColor: "#afb5c0", secondaryColor: "#333" }
];

const ThemeContext = React.createContext();
const ThemeUpdateContext = React.createContext();

const useTheme = () => useContext(ThemeContext);
const useThemeUpdate = () => useContext(ThemeUpdateContext);

const ThemeProvider = ({ children }) => {
  const [state, setState] = useState({
    count: 0,
    primaryColor: themesArray[0].primaryColor,
    secondaryColor: themesArray[0].secondaryColor
  });

  const toggleTheme = () => {
    setState(prevState => {
      const nextCount = prevState.count + 1;
      return {
        count: nextCount % 3,
        primaryColor: themesArray[nextCount].primaryColor,
        secondaryColor: themesArray[nextCount].secondaryColor
      };
    });
  };

  return (
    <ThemeContext.Provider value={[state.primaryColor, state.secondaryColor]}>
      <ThemeUpdateContext.Provider value={toggleTheme}>
        {children}
      </ThemeUpdateContext.Provider>
    </ThemeContext.Provider>
  );
};

const Button = () => {
  const [primaryColor] = useTheme();
  const toggle = useThemeUpdate();
  return (
    <button
      onClick={toggle}
      style={{ width: 500, height: 500, backgroundColor: primaryColor }}
    >
      Button
    </button>
  );
};

const App = () => {
  return (
    <ThemeProvider>
      <Button />
    </ThemeProvider>
  );
};

Edit brave-sunset-0xu96

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • You are right, directly mutating the state is not a good thing. The use of modulo is smart but I'm not used to it. Moreover with the provided example I never reach my last theme object. So I choose this: ```const nextCount = prevState.count >= 3 ? 0 : ++prevState.count;``` and ```count: nextCount```. Anyway thanks a lot for your help. You unlocked me. – chalatum Jul 25 '20 at 09:25
  • 1
    Feel free upvoting/accepting answer which are helpful – Dennis Vash Jul 25 '20 at 09:26