0

I'm trying to access the value of ThemeContext.Consumer in my styled components. I'm using Gatsby to implement dark mode.

This is the ThemeContext.Consumer file:

import React from 'react'

const defaultState = {
  dark: false,
  toggleDark: () => {},
}
const ThemeContext = React.createContext(defaultState)
// Getting dark mode information from OS!
// You need macOS Mojave + Safari Technology Preview Release 68 to test this currently.
const supportsDarkMode = () =>
  window.matchMedia('(prefers-color-scheme: dark)').matches === true
class ThemeProvider extends React.Component {
  state = {
    dark: false,
  }

  componentDidMount() {
    // Getting dark mode value from localStorage!
    const lsDark = JSON.parse(localStorage.getItem('dark'))
    if (lsDark) {
      this.setState({ dark: lsDark })
    } else if (supportsDarkMode()) {
      this.setState({ dark: true })
    }
  }

  // https://stackoverflow.com/questions/59005886/eslint-prevent-using-this-state-within-a-this-setstate-react-no-access-state-i?stw=2
  toggleDark = () => {
    const dark = !this.state.dark
    localStorage.setItem('dark', JSON.stringify(dark))
    this.setState(({ dark }) => ({ dark: !dark }))
  }

  render() {
    const { children } = this.props
    const { dark } = this.state
    return (
      <ThemeContext.Provider
        value={{
          dark,
          toggleDark: this.toggleDark,
        }}
      >
        {children}
      </ThemeContext.Provider>
    )
  }
}
export default ThemeContext
export { ThemeProvider }

This is my Header.js file:

import React from 'react'
import styled from 'styled-components'

import ThemeContext from '../context/ThemeContext'

class Header extends React.Component {
  render() {
    const currentTheme = this.props.theme

    return (
      <ThemeContext.Consumer>
        {theme => (
          <HeaderWrapper>
            <span
              role="presentation"
              className="dark-switcher"
              onClick={theme.toggleDark}
            >
              {theme.dark ? <span>☀</span> : <span>☾</span>}
            </span>
          </HeaderWrapper>
        )}
      </ThemeContext.Consumer>
    )
  }
}

export default Header

const HeaderWrapper = styled.div`
  background: ${() => (theme.dark ? '#C6D0EB' : '#205284')};
  border-bottom: 1px solid var(--accents-2);
`

I added const currentTheme = this.props.theme to be able to use the value globally. With expectation of being able to use it inside my styled component.

Any suggestions on how to approach this problem?

Pa Ye
  • 1,779
  • 12
  • 22
  • What's your actual issue? I guess it's the background color not changing? If so you should write it in your question. Also, your `HeaderWrapper` does not have access to `theme`. Pass it explicitly as a prop if need be. And maybe put all of this in a CodeSandBox.io so we can easily check and help :) – Antoine Nov 25 '19 at 12:04
  • Could you show where exactly `ThemeProvider` is being used? – Pa Ye Nov 25 '19 at 17:33
  • Here's the link to CodeSandbox - https://codesandbox.io/s/github/laurosilvacom/laurosilvacom. I'm trying to access `theme.dark` from inside my styled components. Instead of adding another class (as you can see from the Codesandbox repo). How can I pass the 'theme' explicitly as a prop? cc: @PowellYe @antoine129 –  Nov 25 '19 at 22:06
  • Thanks Lauro, I believe I know the solution, will compose an answer in a bit. – Pa Ye Nov 26 '19 at 08:55

1 Answers1

0

You are probably looking for contextType property. It works as following:

1) Assign a context object to it;
2) Access provided values on this.context.

import ThemeContext from '../context/ThemeContext'

class Header extends React.Component {
  // as a public class field
  static contextType = ThemeContext

  render() {
    const { toggleDark, dark } = this.context

    return (
      <HeaderWrapper>
        <span
          role="presentation"
          className="dark-switcher"
          onClick={toggleDark}
        >
          {dark ? <span>☀</span> : <span>☾</span>}
        </span>
      </HeaderWrapper>
    )
  }
}

// or as a property
Header.contextType = ThemeContext

I noticed you are using a React version which includes support for hooks - if you like, useContext hook provides an alternative way to tap into context values.

Pa Ye
  • 1,779
  • 12
  • 22
  • Yes. This is what I was trying to implement. Thanks for the help Powell. :D –  Nov 28 '19 at 03:42