Search code examples
reactjsstyled-componentsreact-propsuse-statethemeprovider

Changing the theme color value from user input on button click using ThemeProvider


I would like to understand whether it is possible to change the theme value using ThemeProvider by getting an input from the user and managing state.

I have the app that is wrapped in a ThemeProvider to manage the theme.

import styled from 'styled-components'
import { ThemeProvider } from 'styled-components'
import appTheme from './theme'


function App(props, theme) {
  // const themeContext = useContext(ThemeContext)

  const [themePrimary, setThemePrimary] = useState(appTheme.primary)

  function changePrimaryColor(props) {
    setThemePrimary(props)
    appTheme.primary = props
  }

...

return (
    <ThemeProvider theme={theme}>
      <Router>
        <Switch>
          <Route
            path="/"
            exact
            render={({ match }) => {
              return (
                <Frame id="app-frame">
                  <AppHeader match={match} />
                  <Main>
                    <p>Home</p>
                  </Main>
                </Frame>
              )
            }}
          />
...

In the settings view the user must be able to enter a color value.

const ColorTest = styled.input`
  background: ${props => theme.primary};
  border: none;
  margin-left: 50px;
`

...
  const colorInputValue = useRef(null)


  const handleColorSubmit = (e) => {
    e.preventDefault()
  }

  const handleClick = () => {
    const color = colorInputValue.current.value
    props.changePrimaryColor(color)
    console.log(color)
  }

...

<PageSection>
    <Form onSubmit={handleColorSubmit}>
      <h2>Change primary color</h2>
      <ColorInput placeholder="hex or string" ref={colorInputValue} />
      <SubmitFormBtn type="submit" onClick={handleClick}>Change</SubmitFormBtn>
      <ColorTest disabled></ColorTest>
    </Form>
</PageSection>

(The ColorTest component here is for testing purposes. It actually shows me that all the data is being passed correctly and the color changes.)

Here is my "theme"

const theme = {
  primary: '#0068B6',
  secondary: '',
  text_color: '#555',
  text_light: '#fff',
  border: '#e3e3e3',
  drop_shadow: '3px 3px 3px rgba(0, 0, 0, 0.3)',
  background: '#fff',
  background_secondary: '#f5f5f5',
}

export default theme

That new value must change the color value of the primary color and trigger the app component to re-render. How do I make that happen without using local storage or database, but just state?


Solution

  • You can try something like the following. You can now pass down the updateTheme function to other child components.

    I'd potentially take the defaultTheme out of the component and pass it in as a prop (not an imported file). This would mean you could reuse the App component with several different default themes.

    import React, { useState } from 'react';
    import styled from 'styled-components';
    import { ThemeProvider } from 'styled-components';
    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; // Not sure if this is your import
    
    export default function App({
      defaultTheme={
        primary: '#0068B6',
        secondary: '',
        text_color: '#555',
        text_light: '#fff',
        border: '#e3e3e3',
        drop_shadow: '3px 3px 3px rgba(0, 0, 0, 0.3)',
        background: '#fff',
        background_secondary: '#f5f5f5',
      },
    }) {
      const [theme, setTheme] = useState(defaultTheme);
    
      // update here is an object, e.g., { primary: '#333' }
      const updateTheme = (update) => {
        return setTheme({ ...theme, ...update });
      };
    
      const paint = () => {
        // If you're using something to generate the theme, you can put it here instead. For example () => createMuiTheme(theme).
        return React.useMemo(() => ({ ...theme }), [theme]);
      };
    
      return (
        <ThemeProvider theme={paint()}>
          <Router>
           <Switch>
             <Route
               exact
               path='/'
               render={() => <View theme={theme} updateTheme={updateTheme} />}
             />
           </Switch>
          </Router>
        </ThemeProvider>
      );
    };
    
    // Or use your hooks to get the theme
    export default function View({ updateTheme, theme }) {
      return (
        <>
          <div
            style={{
              backgroundColor: theme.primary,
              height: 100,
              width: 100,
             }}
          />
          <button
            onClick={() => updateTheme({ primary: '#333' })}
          >
            Change primary
          </button>
        </>
    
       );
    };