Skip to content

Instantly share code, notes, and snippets.

@jahredhope
Created July 6, 2019 23:06
Show Gist options
  • Save jahredhope/c0d490ec2c58aa45efd11d138b72d9ff to your computer and use it in GitHub Desktop.
Save jahredhope/c0d490ec2c58aa45efd11d138b72d9ff to your computer and use it in GitHub Desktop.
A proposed API for react-unistore
/**
* 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