-
-
Save jahredhope/c0d490ec2c58aa45efd11d138b72d9ff to your computer and use it in GitHub Desktop.
A proposed API for react-unistore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A proposed API for | |
* react-unistore | |
* Binds react components to unistore state and actions | |
* Uses useSelector and useAction | |
*/ | |
import { | |
createContext, | |
createElement, | |
useContext, | |
useEffect, | |
useReducer, | |
useRef, | |
useState, | |
} from "react"; | |
import { Listener, Store } from "unistore"; | |
/** | |
* FROM util.js | |
*/ | |
// Bind an object/factory of actions to the store and wrap them. | |
export function mapActions(actions, store) { | |
if (typeof actions === "function") { | |
actions = actions(store); | |
} | |
const mapped = {}; | |
for (const i in actions) { | |
mapped[i] = store.action(actions[i]); | |
} | |
return mapped; | |
} | |
// select('foo,bar') creates a function of the form: ({ foo, bar }) => ({ foo, bar }) | |
export function select(properties) { | |
if (typeof properties === "string") { | |
properties = properties.split(/\s*,\s*/); | |
} | |
return state => { | |
const selected = {}; | |
for (let i = 0; i < properties.length; i++) { | |
selected[properties[i]] = state[properties[i]]; | |
} | |
return selected; | |
}; | |
} | |
// Lighter Object.assign stand-in | |
export function assign(obj, props) { | |
for (const i in props) { | |
obj[i] = props[i]; | |
} | |
return obj; | |
} | |
/** | |
* END util.js | |
*/ | |
const context = createContext<Store<any>>(null); | |
export const Provider = context.Provider; | |
export const useStore = () => { | |
const store = useContext(context); | |
if (!store) { | |
throw new Error("Missing context. Ensure you've rendered a Provider."); | |
} | |
return store; | |
}; | |
export type UseAction = <State, Args extends any[]>( | |
action: ( | |
state: State, | |
...args: Args | |
) => Promise<Partial<State>> | Partial<State> | void | |
) => (...args: Args) => void; | |
/** | |
* Used to add state definition to useAction | |
* e.g. | |
* export const useAction: TypedUseAction<IState> = _useAction; | |
*/ | |
export type TypedUseAction<State> = <Args extends any[]>( | |
action: ( | |
state: State, | |
...args: Args | |
) => Promise<Partial<State>> | Partial<State> | void | |
) => (...args: Args) => void; | |
export const useAction = <State, Args extends any[]>( | |
action: ( | |
state: State, | |
...args: Args | |
) => Promise<Partial<State>> | Partial<State> | void | |
): ((...args: Args) => void) => useStore().action(action); | |
export type UseSelector<State, Selected> = ( | |
selector: (state: State) => Selected | |
) => Selected; | |
/** | |
* Used to add state definition to useSelector | |
* e.g. | |
* export const useSelector: TypedUseSelector<IState> = _useSelector; | |
*/ | |
export type TypedUseSelector<State> = <Selected>( | |
selector: (state: State) => Selected | |
) => Selected; | |
export const useSelector = <State, Selected>( | |
selector: (state: State) => Selected | |
): Selected => { | |
const store = useStore(); | |
// Allow store subscriptions to force render updates | |
// https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate | |
const [, forceUpdate] = useReducer(x => x + 1, 0); | |
const resultRef = useRef(null); | |
resultRef.current = selector(store.getState()); | |
useEffect(() => { | |
const listener: Listener<any> = state => { | |
const result = selector(state); | |
if (resultRef.current !== result) { | |
forceUpdate({}); | |
} | |
}; | |
store.subscribe(listener); | |
return () => { | |
store.unsubscribe(listener); | |
}; | |
}, []); | |
return resultRef.current; | |
}; | |
/** | |
* connect | |
* Work-in-progress | |
* Below implementation causes unnessary re-renders | |
*/ | |
export function connect(mapStateToProps, actions) { | |
return Child => | |
function Wrapper(props) { | |
const store = useStore(); | |
const [state, setState] = useState({}); | |
function update() { | |
const mapped = mapStateToProps(store ? store.getState() : {}, props); | |
for (const i in mapped) { | |
if (mapped[i] !== state[i]) { | |
return setState(mapped); | |
} | |
} | |
for (const i in state) { | |
if (!(i in mapped)) { | |
return setState(mapped); | |
} | |
} | |
} | |
useEffect(() => { | |
store.subscribe(update); | |
return () => store.unsubscribe(update); | |
}, [state]); | |
useEffect(() => { | |
update(); | |
}, [Object.values(props)]); | |
const boundActions = actions ? mapActions(actions, store) : { store }; | |
return createElement( | |
Child, | |
assign(assign(assign({}, boundActions), props), state) | |
); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment