Skip to content

Instantly share code, notes, and snippets.

@DominicTobias-b1
Created April 3, 2020 01:09
Show Gist options
  • Save DominicTobias-b1/9490b1a76336170021f1f67fcf4c26ca to your computer and use it in GitHub Desktop.
Save DominicTobias-b1/9490b1a76336170021f1f67fcf4c26ca to your computer and use it in GitHub Desktop.
Multi-store Redux with hooks
// limitOrderStore.ts
import { ReduxStore } from 'src/global'
import { LimitOrderActionTypes, LimitOrderActions, LimitOrderState } from './interfaces'
const defaultLimitOrderState: LimitOrderState = {
baseAmount: '',
quotePrice: '',
}
function reducer(state: LimitOrderState, action: LimitOrderActions): LimitOrderState {
switch (action.type) {
case LimitOrderActionTypes.SET_BASE_AMOUNT:
return {
...state,
baseAmount: action.payload,
}
case LimitOrderActionTypes.SET_QUOTE_PRICE:
return {
...state,
quotePrice: action.payload,
}
default:
throw new Error(`Action does not exist: ${JSON.stringify(action)}`)
}
}
export const limitOrderStore = new ReduxStore(reducer, defaultLimitOrderState)
// limitOrderActions.ts
import { LimitOrderActionTypes, LimitOrderActions } from './interfaces'
export function setBaseAmount(value: string): LimitOrderActions {
return {
type: LimitOrderActionTypes.SET_BASE_AMOUNT,
payload: value,
}
}
export function setQuotePrice(value: string): LimitOrderActions {
return {
type: LimitOrderActionTypes.SET_QUOTE_PRICE,
payload: value,
}
}
// interfaces.ts
export interface LimitOrderState {
baseAmount: string
quotePrice: string
}
export enum LimitOrderActionTypes {
SET_BASE_AMOUNT = 'setBaseAmount',
SET_QUOTE_PRICE = 'setQuotePrice',
}
interface SetLimitOrderBaseAmountAction {
type: LimitOrderActionTypes.SET_BASE_AMOUNT
payload: string
}
interface SetLimitOrderQuotePriceAction {
type: LimitOrderActionTypes.SET_QUOTE_PRICE
payload: string
}
export type LimitOrderActions = SetLimitOrderBaseAmountAction | SetLimitOrderQuotePriceAction
interface ActionShape {
type: string
payload: unknown
}
interface DevTools {
connect: () => void
send: (action: string, payload: unknown) => void
}
export class ReduxStore<TState, TActions extends ActionShape> {
public state: TState
private listeners: Listener<TState>[]
private reducer: Reducer<TState, TActions>
private devTools: DevTools
constructor(
reducer: Reducer<TState, TActions>,
initialState: TState = {} as TState
) {
this.reducer = reducer
this.listeners = []
this.state = initialState
this.devTools = typeof window !== 'undefined' && window?.__REDUX_DEVTOOLS_EXTENSION__?.connect()
}
listen(listener: Listener<TState>): void {
this.listeners.push(listener)
}
unlisten(listener: Listener<TState>): void {
this.listeners = this.listeners.filter(l => l !== listener)
}
dispatch = (action: TActions): void => {
const nextState = this.reducer(this.state, action)
if (nextState !== this.state) {
this.state = nextState
this.listeners.forEach(l => l(nextState))
if (this.devTools) {
this.devTools.send(action.type, action.payload)
}
}
}
}
export type Dispatch<TAction> = (action: TAction) => void
export type Listener<TState> = (nextState: TState) => void
export type Reducer<TState, TAction> = (state: TState, action: TAction) => TState
import { useState, useRef, useEffect } from 'react'
import { ReduxStore } from '../ReduxStore'
interface ActionShape {
type: string
payload: unknown
}
export function useStoreSelector<TState, TActions extends ActionShape, ReducedTState>(
globalStore: ReduxStore<TState, TActions>,
stateGetter: (state: TState) => ReducedTState
): ReducedTState {
const [state, setState] = useState(stateGetter(globalStore.state))
// We don't want to re-create the listener as we want to unlisten on unmount
// of the component which uses this hook, so we "tunnel" the state in.
const stateRef = useRef(state)
stateRef.current = state
const listener = useRef((nextState: TState) => {
const stateUpdate = stateGetter(nextState)
if (stateRef.current !== stateUpdate) {
setState(stateUpdate)
}
})
useEffect(() => {
globalStore.listen(listener.current)
const currentListener = listener.current
return () => globalStore.unlisten(currentListener)
}, [globalStore])
return state
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment