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

1.6k views Asked by At

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?

1

There are 1 answers

3
Ross Sheppard On BEST ANSWER

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>
    </>

   );
};