React.useState react differently as a prop or as a property?

Viewed 56

and my deepest appologies for the questions spam (I really have troubles understanding React learning it alone). In my actual code, I have created a context, specified to be able to change:

const ThemeContext = React.createContext(["light", () => {}]);

In my app function, I create a hook (I still doesn't clearly understand why, see my very last bonus question) on it so I'll be able to access those data in child components:

const themeHook = React.useState("light");
return (<ThemeContext.Provider value={themeHook}>...</ThemeContext.Provider>);

The component where I use it, I had to encompass it in a function to be able to to pass the hook data though props:

function ThemeTogglerFunction() {
    const [contextType, toggleTheme] = React.useContext(ThemeContext);
    return <ThemeToggler context={contextType} toggler={toggleTheme} />;
}

And finally, my ThemeToggler component can use that theme data and even switch between the two values I specified, I'm happy:

class ThemeToggler extends React.Component {
    constructor(props) {
        super(props);
        this.handleToggle = this.handleToggle.bind(this);
    }
    handleToggle() { this.props.toggler(this.props.context === "light" ? "dark" : "light"); }
    render() { return (<span style={ownStyle} onClick={this.handleToggle}>Toggle actuel {this.props.context} theme</span>); }
}

But then, if I insert the props into local properties (which sounds more proper to me):

class ThemeTogglerLocal extends React.Component {
    constructor(props) {
        super(props);
        this.gloablThemeContext = props.context;
        this.gloablThemeToggler = props.toggler;
        this.handleToggle = this.handleToggle.bind(this);
    }
    handleToggle() { this.gloablThemeToggler(this.gloablThemeContext === "light" ? "dark" : "light"); }
    render() { return (<span style={ownStyle} onClick={this.handleToggle}>Toggle actuel {this.gloablThemeContext} theme</span>); }
}

even if the hook value is correctly changed one ("light" becomes"dark") in the global ThemeContext (I can see it as everything is rerendered with the correct color change), the said local properties is not changed (is stays on "light") and so the app stays in "dark" mode from there. I suppose that is because the property is initialised in the constructor and have no more link with the hook, is it correct ? Does it mean that when you "import" a hook into a component, you shouldn't save it into local properties ?

Furthermore (small bonus question) : I had to create a hook in my app function, and I don't understand why, as React.createContext() already creates something which looks like a hook. Why can't I pass it directly the Theme Context.Provider (<ThemeContext.Provider value={ThemeContext}> in place of <ThemeContext.Provider value={themeHook}>) ?

Thank you very much in advance, and sorry for the length of this post.

1 Answers

Given

class ThemeTogglerLocal extends React.Component {
  constructor(props) {
    super(props);
    this.gloablThemeContext = props.context;
    this.gloablThemeToggler = props.toggler;
    this.handleToggle = this.handleToggle.bind(this);
  }

  handleToggle() {
    this.gloablThemeToggler(
      this.gloablThemeContext === 'light' ? 'dark' : 'light',
    );
  }

  render() {
    return (
      <span onClick={this.handleToggle} style={ownStyle}>
        Toggle actuel {this.gloablThemeContext} theme
      </span>
    );
  }
}

Even if the hook value is correctly changed one ("light" becomes"dark") in the global ThemeContext (I can see it as everything is rerendered with the correct color change), the said local properties is not changed (is stays on "light") and so the app stays in "dark" mode from there. I suppose that is because the property is initialised in the constructor and have no more link with the hook, is it correct ?

Correct, in the class-based component you would need to implement the componentDidUpdate lifecycle method to "react" to the prop values changing in order to update the class properties.

componentDidUpdate(prevProps) {
  if (prevProps.context !== this.props.context( {
    this.gloablThemeContext = props.context;
    this.gloablThemeToggler = props.toggler;
  }
}

Does it mean that when you "import" a hook into a component, you shouldn't save it into local properties?

This is exactly correct. Storing passed props into local component state (or class variables in your case) is an anti-pattern in React. It often leads to stale state/enclosures/etc and buggy code. You should consume passed props directly in the rendered result or any other lifecycle methods.

Furthermore (small bonus question): I had to create a hook in my app function, and I don't understand why, as React.createContext() already creates something which looks like a hook. Why can't I pass it directly the Theme Context.Provider (<ThemeContext.Provider value={ThemeContext}> in place of <ThemeContext.Provider value={themeHook}>)?

Recall that your context value is ["light", () => {}]:

const ThemeContext = React.createContext(["light", () => {}]);

When you create the actual value in the provider const themeHook = React.useState("light"); it basically resolves to const [theme, setTheme] = React.useState("light");

You are passing the current state value and the state updater function as the context value to the provider.

return (
  <ThemeContext.Provider value={[theme, setTheme]}>
    ...
  </ThemeContext.Provider>
);

This is destructured in consumers as

const [theme, setTheme] = React.useContext(ThemeContext);

or in your case

const [contextType, toggleTheme] = React.useContext(ThemeContext);
Related