Change GlobalStyles on page load only

Viewed 128

I am trying to change the transition on the body depending on whether the page has loaded or the ThemeToggle button is clicked. But I don't see a way to send a prop to GlobalStyles

App.tsx

import React, { ReactElement } from 'react'
import { createGlobalStyle } from 'styled-components'
import { Theme } from '@components/Theme/'

interface IPalette {
  body: string
  text: string
}

const GlobalStyles = createGlobalStyle<{ theme: IPalette }>`
  body {
    background: ${({ theme }) => theme.body};
    color: ${({ theme }) => theme.text};
    /* I want to modify the transition on load */
    transition: background-color 0.3s ease;
  }
`

export const App = (): ReactElement => {
  return (
    <Theme>
      <GlobalStyles />
      <h1>Test</h1>
    </Theme>
  )
}

Theme.tsx

import React, { FC, useState, useEffect } from 'react'
import { ThemeProvider } from 'styled-components'
import { darkTheme, lightTheme, ThemeToggle } from './theme-style'
import { createCookie, eraseCookie, readCookie } from './cookie'

export const Theme: FC<{children: React.ReactNode}> = ({children}) => {

  const [themeState, setTheme] = useState<boolean>(false)
  const [firstLoad, setFirstLoad] = useState<boolean>(true)

  const changeTheme = (): void => {
    setFirstLoad(false)
    if (themeState) {
      setTheme(false)
      eraseCookie('Theme')
    } else {
      setTheme(true)
      createCookie('Theme', true, 30)
    }
  }
  
  useEffect(() => {
    if (readCookie('Theme')) setTheme(true)
  }, [])

  return (
    <ThemeProvider theme={themeState ? darkTheme : lightTheme}> 
      {children}
      <ThemeToggle theme={themeState} onClick={changeTheme} />
    </ThemeProvider>
  )
}

I am trying to send the firstLoad boolean from the ThemeProvider to the GlobalStyles but I get an error from TypeScript if I try.

Type '{ children: (ReactNode | Element)[]; firstLoad: true; theme: IPalette; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<ThemeProviderProps<any, any>, any, any>> & Readonly<...> & Readonly<...>'.
1 Answers

Since the goal here was to be able to change the body transition only when the user clicks change theme and not on load, I did this, it may not be the best solution but after trying context provider, I think this is a less convoluted approach, please feel free to suggest an alternative.

What I did was convert the styled themes to be functions that return an object instead of an object literal, so I can pass the parameter transition to toggle the body transition.

app.tsx

import React, { ReactElement } from 'react'
import { createGlobalStyle } from 'styled-components'
import { Theme } from '@components/Theme/'

interface IPalette {
  body: string
  text: string
  transition: string
}

const GlobalStyles = createGlobalStyle<{ theme: IPalette}>`
  body {
    background: ${({ theme }) => theme.body};
    color: ${({ theme }) => theme.text};
    transition: ${({ theme }) => theme.transition};
  }
`

export const App = (): ReactElement => {

  return (
    <Theme>
      <GlobalStyles />
      <h1>Test</h1>
    </Theme>
  )
}

theme.tsx

import React, { FC, useState, useEffect } from 'react'
import { ThemeProvider, IPalette } from 'styled-components'
import { ThemeToggle, colors } from './theme-style'
import { createCookie, eraseCookie, readCookie } from './cookie'

const lightTheme = (transition: boolean): IPalette => ({
  body: colors.bgLight,
  text: colors.textLight,
  transition: transition ? 'background-color 0.3s ease' : ''
})

const darkTheme = (transition: boolean): IPalette => ({
  body: colors.bgDark,
  text: colors.textDark,
  transition: transition ? 'background-color 0.3s ease' : ''
})

export const Theme: FC<{children: React.ReactNode}> = ({children}) => {

  const [themeState, setTheme] = useState<boolean>(false)
  const [transition, setTransition] = useState<boolean>(false)

  const changeTheme = (): void => {
    setTransition(true)
    if (themeState) {
      setTheme(false)
      eraseCookie('Theme')
    } else {
      setTheme(true)
      createCookie('Theme', true, 30)
    }
  }
  
  useEffect((): void => {
    if (readCookie('Theme')) setTheme(true)
  }, [])

  return (
    <ThemeProvider theme={themeState ? darkTheme(transition) : lightTheme(transition)}> 
      {children}
      <ThemeToggle theme={themeState} onClick={changeTheme}>
        <span></span>
      </ThemeToggle>
    </ThemeProvider>
  )
}
Related