@easy-peasy/react
Version:
easy-peasy connector for React
207 lines (178 loc) • 6.7 kB
JavaScript
import { setAutoFreeze } from 'immer-peasy';
import React, { createContext, useContext, useRef, useReducer, useState, useEffect, useLayoutEffect, useMemo } from 'react';
import { createStore } from '@easy-peasy/core';
var StoreContext = createContext();
// To get around it, we can conditionally useEffect on the server (no-op) and
// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store
// subscription callback always has the selector from the latest render commit
// available, otherwise a store update may happen between render and the effect,
// which may cause missed updates; we also must ensure the store subscription
// is created synchronously, otherwise a store update may occur before the
// subscription is created and an inconsistent state may be observed
var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
function createStoreStateHook(Context) {
return function useStoreState(mapState) {
var store = useContext(Context);
var mapStateRef = useRef(mapState);
var stateRef = useRef();
var mountedRef = useRef(true);
var subscriptionMapStateError = useRef();
var _useReducer = useReducer(function (s) {
return s + 1;
}, 0),
forceRender = _useReducer[1];
if (subscriptionMapStateError.current || mapStateRef.current !== mapState || stateRef.current === undefined) {
try {
stateRef.current = mapState(store.getState());
} catch (err) {
if (process.env.NODE_ENV === 'development') {
var errorMessage = "An error occurred trying to map state in a useStoreState hook: " + err.message + ".";
if (subscriptionMapStateError.current) {
errorMessage += "\nThis error may be related to the following error:\n" + subscriptionMapStateError.current.stack + "\n\nOriginal stack trace:";
}
throw new Error(errorMessage);
}
throw subscriptionMapStateError.current || err;
}
}
useIsomorphicLayoutEffect(function () {
mapStateRef.current = mapState;
subscriptionMapStateError.current = undefined;
});
useIsomorphicLayoutEffect(function () {
var checkMapState = function checkMapState() {
try {
var newState = mapStateRef.current(store.getState());
if (newState === stateRef.current) {
return;
}
stateRef.current = newState;
} catch (err) {
// see https://github.com/reduxjs/react-redux/issues/1179
// There is a possibility mapState will fail due to stale state or
// props, therefore we will just track the error and force our
// component to update. It should then receive the updated state
subscriptionMapStateError.current = err;
}
if (mountedRef.current) {
forceRender({});
}
};
var unsubscribe = store.subscribe(checkMapState);
checkMapState();
return function () {
mountedRef.current = false;
unsubscribe();
};
}, []);
return stateRef.current;
};
}
var useStoreState = createStoreStateHook(StoreContext);
function createStoreActionsHook(Context) {
return function useStoreActions(mapActions) {
var store = useContext(Context);
return mapActions(store.getActions());
};
}
var useStoreActions = createStoreActionsHook(StoreContext);
function createStoreDispatchHook(Context) {
return function useStoreDispatch() {
var store = useContext(Context);
return store.dispatch;
};
}
var useStoreDispatch = createStoreDispatchHook(StoreContext);
function useStore() {
return useContext(StoreContext);
}
function createStoreRehydratedHook(Context) {
return function useStoreRehydrated() {
var store = useContext(Context);
var _useState = useState(false),
rehydrated = _useState[0],
setRehydrated = _useState[1];
useEffect(function () {
store.persist.resolveRehydration().then(function () {
return setRehydrated(true);
});
}, []);
return rehydrated;
};
}
var useStoreRehydrated = createStoreRehydratedHook(StoreContext);
function createTypedHooks() {
return {
useStoreActions: useStoreActions,
useStoreDispatch: useStoreDispatch,
useStoreState: useStoreState,
useStoreRehydrated: useStoreRehydrated,
useStore: useStore
};
}
/* eslint-disable react/prop-types */
function createContextStore(model, config) {
var StoreContext = createContext();
function Provider(_ref) {
var children = _ref.children,
initialData = _ref.initialData;
var store = useMemo(function () {
return createStore(typeof model === 'function' ? model(initialData) : model, config);
}, []);
return React.createElement(StoreContext.Provider, {
value: store
}, children);
}
function useStore() {
return useContext(StoreContext);
}
return {
Provider: Provider,
useStore: useStore,
useStoreState: createStoreStateHook(StoreContext),
useStoreActions: createStoreActionsHook(StoreContext),
useStoreDispatch: createStoreDispatchHook(StoreContext),
useStoreRehydrated: createStoreRehydratedHook(StoreContext)
};
}
/**
* Some good references on the topic of reinitialisation:
* - https://github.com/facebook/react/issues/14830
*/
function createComponentStore(model, config) {
return function useLocalStore(initialData) {
var store = useMemo(function () {
return createStore(typeof model === 'function' ? model(initialData) : model, config);
}, []);
var previousStateRef = useRef(store.getState());
var _useState = useState(function () {
return store.getState();
}),
currentState = _useState[0],
setCurrentState = _useState[1];
useEffect(function () {
return store.subscribe(function () {
var nextState = store.getState();
if (previousStateRef.current !== nextState) {
previousStateRef.current = nextState;
setCurrentState(nextState);
}
});
}, [store]);
return [currentState, store.getActions()];
};
}
function StoreProvider(_ref) {
var children = _ref.children,
store = _ref.store;
return React.createElement(StoreContext.Provider, {
value: store
}, children);
}
/**
* The auto freeze feature of immer doesn't seem to work in our testing. We have
* explicitly disabled it to avoid perf issues.
*/
setAutoFreeze(false);
export { StoreProvider, createComponentStore, createContextStore, createTypedHooks, useStore, useStoreActions, useStoreDispatch, useStoreRehydrated, useStoreState };
//# sourceMappingURL=easy-peasy-react.esm.js.map