remove AppProvider, replace with hooks

This commit is contained in:
Matthias Kretschmann 2019-11-11 23:46:47 +01:00
parent d9fa36608e
commit 796dfc64fa
Signed by: m
GPG Key ID: 606EEEF3C479A91F
12 changed files with 55 additions and 216 deletions

View File

@ -74,7 +74,7 @@ If a visitor has set the theme manually that selection is remembered in `localSt
If you want to know how, have a look at the respective components:
- [`src/components/molecules/ThemeSwitch.jsx`](src/components/molecules/ThemeSwitch.jsx)
- [`src/store/provider.jsx`](src/store/provider.jsx)
- [`src/hooks/use-dark-mode.jsx`](src/hooks/use-dark-mode.jsx)
### 🏆 SEO component

View File

@ -1,6 +1,3 @@
import React from 'react'
import PropTypes from 'prop-types'
import AppProvider from './src/store/AppProvider'
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
// Global styles
@ -11,15 +8,6 @@ if (typeof window !== 'undefined' && !window.IntersectionObserver) {
import('intersection-observer')
}
// React Context in Browser
export const wrapRootElement = ({ element }) => {
return <AppProvider>{element}</AppProvider>
}
wrapRootElement.propTypes = {
element: PropTypes.any
}
// Layout with Page Transitions
export const wrapPageElement = wrapPageElementWithLayout

View File

@ -1,13 +1,4 @@
import React from 'react'
import { renderToString } from 'react-dom/server'
import AppProvider from './src/store/AppProvider'
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString }) => {
// React Context in SSR/build
const ConnectedBody = () => <AppProvider>{bodyComponent}</AppProvider>
replaceBodyHTMLString(renderToString(<ConnectedBody />))
}
// Layout with Page Transitions
export const wrapPageElement = wrapPageElementWithLayout

View File

@ -4,13 +4,13 @@
margin-left: auto;
margin-right: auto;
display: block;
border-radius: $border-radius;
overflow: hidden;
box-shadow: 0 3px 5px rgba($brand-main, 0.15),
0 5px 16px rgba($brand-main, 0.15);
@media (min-width: $projectImageMaxWidth) {
max-width: $projectImageMaxWidth;
border-radius: $border-radius;
overflow: hidden;
}
:global(.dark) & {

View File

@ -1,8 +1,8 @@
import React, { useContext } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import posed from 'react-pose'
import Context from '../../store/createContext'
import useDarkMode from '../../hooks/use-dark-mode'
import { fadeIn } from '../atoms/Transitions'
import { ReactComponent as Day } from '../../images/day.svg'
@ -40,7 +40,7 @@ ThemeToggleInput.propTypes = {
}
export default function ThemeSwitch() {
const { darkMode, toggleDark } = useContext(Context)
const { darkMode, toggleDark } = useDarkMode()
const themeColor = darkMode ? '#1d2224' : '#e7eef4'
return (

View File

@ -1,53 +1,23 @@
import React from 'react'
import { render, fireEvent, cleanup } from '@testing-library/react'
import Context from '../../store/createContext'
import { render, fireEvent, cleanup, wait } from '@testing-library/react'
import ThemeSwitch from './ThemeSwitch'
describe('ThemeSwitch', () => {
afterEach(cleanup)
const toggleDark = jest.fn()
it('renders correctly', () => {
const { container } = render(
<Context.Provider
value={{ darkMode: false, toggleDark: () => toggleDark }}
>
<ThemeSwitch />
</Context.Provider>
)
const switchContainer = container.querySelector('aside')
expect(switchContainer).toBeInTheDocument()
it('renders correctly', async () => {
const { container } = render(<ThemeSwitch />)
await wait(() => container.querySelector('aside'))
expect(container.querySelector('aside')).toBeInTheDocument()
})
it('switches when it is dark', () => {
const { container } = render(
<Context.Provider
value={{ darkMode: true, toggleDark: () => toggleDark }}
>
<ThemeSwitch />
</Context.Provider>
)
const toggle = container.querySelector('input')
expect(toggle).toHaveAttribute('checked')
})
it('checkbox can be changed', () => {
const { container } = render(
<Context.Provider
value={{ darkMode: false, toggleDark: () => toggleDark }}
>
<ThemeSwitch />
</Context.Provider>
)
it('checkbox can be changed', async () => {
const { container } = render(<ThemeSwitch />)
const toggle = container.querySelector('input')
const label = container.querySelector('label')
expect(toggle.checked).toBeFalsy()
fireEvent.click(label)
fireEvent.change(toggle, { target: { checked: true } })
expect(toggle.checked).toBeTruthy()
})
})

View File

@ -1,34 +1,27 @@
import React from 'react'
import { render } from '@testing-library/react'
import { render, cleanup, wait } from '@testing-library/react'
import { useStaticQuery } from 'gatsby'
import Header from './Header'
import Context from '../../store/createContext'
import data from '../../../jest/__fixtures__/meta.json'
describe('Header', () => {
beforeEach(() => {
useStaticQuery.mockImplementation(() => {
return {
...data
}
return { ...data }
})
})
it('renders correctly', () => {
const { container } = render(
<Context.Provider value={{ darkMode: false, toggleDark: () => null }}>
<Header />
</Context.Provider>
)
afterEach(cleanup)
it('renders correctly', async () => {
const { container } = render(<Header />)
await wait(() => container.firstChild)
expect(container.firstChild).toBeInTheDocument()
})
it('Availability can be hidden', () => {
const { container } = render(
<Context.Provider value={{ darkMode: false, toggleDark: () => null }}>
<Header minimal={true} />
</Context.Provider>
)
it('Availability can be hidden', async () => {
const { container } = render(<Header minimal={true} />)
await wait(() => container.querySelector('.availability'))
expect(container.querySelector('.availability')).not.toBeInTheDocument()
})
})

View File

@ -0,0 +1,32 @@
import { useState, useEffect } from 'react'
import { getLocationTimes } from '../utils/getLocationTimes'
import { getCountry } from '../utils/getCountry'
export default function useDarkMode() {
const store = typeof localStorage === 'undefined' ? null : localStorage
const darkLocalStorage = store && store.getItem('darkMode')
const [darkMode, setDarkMode] = useState(false)
useEffect(() => {
darkLocalStorage
? darkLocalStorage === 'true'
? setDarkMode(true)
: setDarkMode(false)
: checkTimes()
async function checkTimes() {
const geolocation = await getCountry()
const { sunset, sunrise } = getLocationTimes(geolocation)
const now = new Date().getHours()
const weWantItDarkTimes = now >= sunset || now <= sunrise
weWantItDarkTimes && setDarkMode(true)
}
}, [darkLocalStorage])
function toggleDark() {
setDarkMode(!darkMode)
store && store.setItem('darkMode', !darkMode)
}
return { darkMode, toggleDark }
}

View File

@ -1,18 +0,0 @@
import React from 'react'
import { render } from '@testing-library/react'
import { useStaticQuery } from 'gatsby'
import { useMeta } from './use-meta'
import data from '../../jest/__fixtures__/meta.json'
beforeEach(() => {
useStaticQuery.mockImplementationOnce(() => ({ ...data }))
})
describe('useMeta', () => {
it('renders correctly', () => {
const { social } = useMeta()
const { container } = render(<div>{social.Twitter}</div>)
expect(container.textContent).toBe('https://twitter.com/kremalicious')
})
})

View File

@ -1,74 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Context from './createContext'
import { getLocationTimes } from '../utils/getLocationTimes'
import { getCountry } from '../utils/getCountry'
export default class AppProvider extends PureComponent {
static propTypes = {
children: PropTypes.any.isRequired
}
state = {
darkMode: false,
toggleDark: () => this.toggleDark(),
geolocation: null
}
store = typeof localStorage === 'undefined' ? null : localStorage
mounted = false
async componentDidMount() {
this.mounted = true
const geolocation = await getCountry()
this.setState({ geolocation })
this.checkDark()
}
componentWillUnmount() {
this.mounted = false
}
setDark(darkMode) {
this.mounted && this.setState({ darkMode })
}
darkLocalStorageMode(darkLocalStorage) {
darkLocalStorage === 'true' ? this.setDark(true) : this.setDark(false)
}
//
// All the checks to see if we should go dark or light
//
async checkTimes() {
const { geolocation, darkMode } = this.state
const { sunset, sunrise } = getLocationTimes(geolocation)
const now = new Date().getHours()
const weWantItDarkTimes = now >= sunset || now <= sunrise
!darkMode && weWantItDarkTimes ? this.setDark(true) : this.setDark(false)
}
async checkDark() {
const darkLocalStorage = await this.store.getItem('darkMode')
darkLocalStorage
? this.darkLocalStorageMode(darkLocalStorage)
: this.checkTimes()
}
toggleDark = () => {
this.setState({ darkMode: !this.state.darkMode })
this.store && this.store.setItem('darkMode', !this.state.darkMode)
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}

View File

@ -1,38 +0,0 @@
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import { LocalStorageMock } from '@react-mock/localstorage'
import AppProvider from './AppProvider.jsx'
import Context from './createContext.jsx'
describe('AppProvider', () => {
it('renders correctly', () => {
const { container } = render(<AppProvider>Hello</AppProvider>)
expect(container.firstChild.textContent).toBe('Hello')
})
it('renders with dark detected in localStorage', () => {
const { getByTestId } = render(
<LocalStorageMock items={{ dark: 'true' }}>
<AppProvider>
<Context.Consumer>
{state => (
<button data-testid="toggle" onClick={() => state.toggleDark()}>
Toggle
</button>
)}
</Context.Consumer>
</AppProvider>
</LocalStorageMock>
)
fireEvent.click(getByTestId('toggle'))
})
it('renders with light detected in localStorage', () => {
render(
<LocalStorageMock items={{ dark: 'false' }}>
<AppProvider>Hello</AppProvider>
</LocalStorageMock>
)
})
})

View File

@ -1,5 +0,0 @@
import { createContext } from 'react'
const Context = createContext()
export default Context