UNPKG

@easy-peasy/react

Version:
207 lines (178 loc) 6.7 kB
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