I am fetching to get data from the server before the app is rendered, by store that data
into the redux store.
And facing this error:
Error: could not find react-redux context value; please ensure the component is wrapped in a
in _app.tsx
:
function MyApp({ Component, pageProps, data }: AppProps & { data: any }) {
console.log(data); // data is here
const dispatch = useDispatch();
useEffect(() => {
// Dispatch an action to store the fetched data in Redux store
dispatch(saveData(data));
}, [dispatch]);
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
MyApp.getInitialProps = async ({ Component, ctx }) => {
const data = await fetch(`http://base-url/my-route`).then((res) => res.json());
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps, data };
};
export default MyApp;
store is exported from store.ts, and I am able to use those useDispatch and useSelector well in other parts of the app.
You can not use useDispatch
outside the Provider
.
You can use useDispatch
inside your Component
, but not in MyApp
,
because Component
is inside the Provider
with the connected store
, but MyApp
is not:
function MyApp() {
// -- here is outside the Provider (no useDispatch possible) --
return (
<Provider store={ store }>
{/* -- here is inside the provider -- */}
<Component {...pageProps} />
</Provider>
);
}
There are 3 solutions:
store
objectstore
object directly (without useDispatch
)Loader
component, which can use useDispatch
store
objectYou surely have code to create the store somewhere, e.g.:
const myStore = createStore( myReducer, myInitialState );
You might be able to create the initial state myInitialState
with your data from
getInitialProps
already included:
const myStore = useMemo(() => {
const staticInitialState = {
// your static state
};
const myInitialState = {
...staticInitialState,
...dataFromGetInitialProps
};
return createStore( reducer, myInitialState );
}, []);
I think this would be the cleanest solution, because it is exactly inline with both the React and the Redux concepts.
(Apparently createStore
is deprecated and there is a new, much more complicated
syntax with configureStore
and createSlice
, which I'm not yer familiar with.)
store
object directlyYou can not use useDispatch
inside MyApp
, but you do have the store
object available there.
The store
object has a dispatch
and a getState
method, which you can use, e.g.:
function MyApp() {
console.log( store.getState() );
useEffect(() => {
store.dispatch( saveData( data ) );
}, []);
return (
<Provider store={ store }>
<Component {...pageProps} />
</Provider>
);
}
I think this is ok, but more like a "workaround" than a "solution", because using the store
methods directly
seems a bit more low level than using the Provider
, and probably intended for more advanced
use cases.
You can create a component inside the Provider
, and put your dispatch
logic there.
Alternatively you could create a Wrapper HOC
around the Component
, if more appropriate.
function DataLoader({ data }){
const dispatch = useDispatch();
useEffect( () => {
dispatch( saveData( data ) );
}, [] );
return null;
}
function MyApp({ Component, pageProps, data }){
return (
<Provider store={ store }>
<DataLoader data={ data } />
<Component { ...pageProps } />
</Provider>
);
}
This seems pretty hacky to me, because is (mis-)uses a component as a kind of "life cycle hook",
and there is no logical relation between the DataLoader
and the location where it is put inside
the code (it could really be anywhere, as long as the component is rendered at the right time).