What is the correct way to create redux store within useEffect
?
I have a use case that a child App.js will be exported as web-component (e.g. a javascript file), then embed (e.g. reference this file in html header) by a parent react app.
One flow is that the parent app will render App.js twice or N times. I put store creation inside useEffect + empty dependency
, so this will be called once, regardless how many times the parent app renders the child app.
Once my store is ready, I render rest of the component + pass the store to context.
In Component.js, I look up the state and want to use them.
The error I have: https://github.com/facebookincubator/redux-react-hook/blob/master/src/create.ts
const state = store.getState();
, getState
does not exist. It seems that either store is not ready when I tried to look up the state.
So I wonder what is the correct way to create store within useEffect
?
App.js
export default function App() {
const store = useRef(null);
const [storeReady, setStoreReady] = useState(false);
const [token, setToken] = useState('');
useEffect(() => {
store.current = createReduxStore();
setStoreReady(true);
return () => {
store.current = null;
};
}, []);
useEffect(() => {
if (storeReady) {
// get token and set in state
token(auth())
}
}, [auth, storeReady]);
// store.current
if (store.current !== null) {
//test
console.log(
'++++++ store.current not null, condi',
store.current !== null,
store.current
);
return (
<>
<StoreContext.Provider value={store.current}>
<MyApp
token={token}
/>
</StoreContext.Provider>
</>
);
} else {
return null;
}
}
Store.js
export function createReduxStore(initialState = {}) {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
return store;
}
Component.js
export default function MyApp() {
const mapState = useCallback(
state => ({
history:
state !== undefined && state.idv !== undefined ? state.idv.history : [],
status:
state !== undefined && state.idv !== undefined
? state.idv.status
: initialStatus,
error:
state !== undefined && state.idv !== undefined ? state.idv.error : ''
}),
[]
);
const {history, status, error} = useMappedState(mapState);
}
// https://github.com/facebookincubator/redux-react-hook/blob/master/src/create.ts
function useMappedState<TResult>(
mapState: (state: TState) => TResult,
equalityCheck: (a: TResult, b: TResult) => boolean = defaultEqualityCheck,
): TResult {
const store = useContext(StoreContext);
if (!store) {
throw new MissingProviderError();
}
// We don't keep the derived state but call mapState on every render with current state.
// This approach guarantees that useMappedState returns up-to-date derived state.
// Since mapState can be expensive and must be a pure function of state we memoize it.
const memoizedMapState = useMemo(() => memoizeSingleArg(mapState), [
mapState,
]);
// getState is undefined
const state = store.getState();
const derivedState = memoizedMapState(state);
Update 1, but not working @backtick
export default function App() {
const duckStore = { getState() {} };
const store = useRef(duckStore);
const [token, setToken] = useState('');
useEffect(() => {
store.current = createReduxStore();
return () => {
store.current = duckStore;
};
}, []);
// store.current
if (store.current !== null) {
return (
<>
<StoreContext.Provider value={store.current}>
<MyApp />
</StoreContext.Provider>
</>
);
} else {
return null;
}
}
Store.js
export function createReduxStore(initialState = {}) {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
return store;
}
Component.js
export default function MyApp() {
// There is no store variable here, useMappedState is able to get the store value.
const mapState = useCallback(
state => ({
history:
state !== undefined && state.idv !== undefined ? state.idv.history : [],
status:
state !== undefined && state.idv !== undefined
? state.idv.status
: initialStatus,
error:
state !== undefined && state.idv !== undefined ? state.idv.error : ''
}),
[]
);
const {history, status, error} = useMappedState(mapState);
}
useMappedState
calls thestore.getState()
on every render, even the first one when the store isn't ready. Just duck-type the store ref's initial value: