Skip to content

Instantly share code, notes, and snippets.

@dalcib
Forked from tannerlinsley/Counter.js
Created October 14, 2019 11:02
Show Gist options
  • Save dalcib/0d9f5fe95e8709d0e748ff62b772c759 to your computer and use it in GitHub Desktop.
Save dalcib/0d9f5fe95e8709d0e748ff62b772c759 to your computer and use it in GitHub Desktop.
Global React State with Context and Immer
import { useCount, useIncrement, useDecrement } from './store.Count'
export default function Counter () {
const count = useCount()
const increment = useIncrement()
const decrement = useDecrement()
return (
<div>
<div>Count: {count}</div>
<div>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
import React from 'react'
import { render } from 'react-dom'
// Import the store
import Store from './store'
// Import any initialState
import { initialState as count } from './store.Count'
// Import components
import Counter from './Counter'
// Build initial state
const initialState = {
count
}
render(
// Use the Store Provider
<Store.Provider initialState={initialState}>
<Counter />
</Store.Provider
)
import React, { useState, useMemo, useCallback, useContext } from 'react'
import immer from 'immer' // Immer rocks. Go check it out if you haven't
export default function makeStore() {
// This is basically a factory for a new store instance
// Create a new context for this store instance
const context = React.createContext()
// Make a provider that takes an initial state
const Provider = ({ children, initialState = {} }) => {
// useState will do. Nothing fancy needed
const [state, preSetState] = useState(initialState)
// We make our own setState callback that uses immer under the hood
// (or we could use `useImmer`, but this is fun, too)
const setState = useCallback(
updater => preSetState(old => immer(old, draft => updater(draft))),
preSetState
)
// Memoize the context value so it only updates when the state changes
const contextValue = useMemo(() => [state, setState], [state])
// Pass the context down
return <context.Provider value={contextValue}>{children}</context.Provider>
}
// A hook to consume the context. No need to import `useContext` everywhere. How wasteful...
const useStore = () => useContext(context)
// Export them however you like. I prefer a default.
return {
Provider,
useStore,
}
}
import Store from './'
// Export our initial state
export const initialState = 0
export function useCount () {
const [{ count }] = Store.useStore()
return count
}
export function useIncrement () {
const [_, setState] = Store.useStore()
return () => setState(draft => {
draft.count++
})
}
export function useDecrement () {
const [_, setState] = Store.useStore()
return () => setState(draft => {
draft.count--
})
}
// Import our factory
import makeStore from './makeStore'
// Export a new state instance
export default makeStore()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment