I am trying to implement redux 4.0.0 with redux-persist 5.10.0 in an SSR application and am running into an issue where I cannot properly supply createStore()
with the preloaded state without the app crashing.
What happens is that the application loads with the initial state from the server, but when the app tries to preload the state in createStore()
on the client, the app refreshes and crashes. I assume it's because my preloadedState is not in the proper format?
But I'm not sure because I'm not getting any error messages in the console, the UI, nada.
Here is some relevant code:
store/index.js
export default function configureStore(preloadedState = {}) {
// This will store our enhancers for the store
const enhancers = [];
// Add thunk middleware
const middleware = [thunk];
// Apply middlware and enhancers
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
);
// Set up persisted and combined reducers
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Create the store with the persisted reducers and middleware/enhancers
const store = createStore(persistedReducer, preloadedState, composedEnhancers);
const persistor = persistStore(store, null, () => {
store.getState(); // if you want to get restoredState
});
return { store, persistor };
}
index.js
const preloadedState = window.__PRELOADED_STATE__ ? window.__PRELOADED_STATE__ : {};
delete window.__PRELOADED_STATE__;
// Create redux store
const { persistor, store } = configureStore(preloadedState);
// Get app's root element
const rootEl = document.getElementById("root");
// Determine if we should use hot module rendering or DOM hydration
const renderMethod = !!module.hot ? ReactDOM.render : ReactDOM.hydrate;
renderMethod(
<Provider store={store}>
<PersistGate loading={<Loader />} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>,
rootEl
);
Things persist and whatnot in development on the client, but when I test the SSR the app loads, and then reloads and goes blank. It reloading has me thinking the state is not being hydrated with the same data. it crashing completely has me baffled at the moment.
Any idea of how to proceed??
EDIT
After some old-school debugging, I've found that removing the <PersistGate loading={<Loader />} persistor={persistor}>
line will allow the app to load initially and things are loaded via the server as expected, but the data does not persist properly (obviously).
Is there anything wrong with how I'm using the PersistGate
component?
window.__PRELOADED_STATE__
{
user: {…}, banners: {…}, content: {…}, locations: {…}, news: {…}, …}
banners: {isLoading: 0, banners: Array(2)}
content: {isLoading: 0, errors: {…}, data: {…}}
locations: {countries: Array(0), provinces: Array(0), default_country: null, isLoading: false, error: null, …}
news: {isLoading: 0, hasError: 0}
phoneTypes: {isLoading: false}
profileStatuses: {isLoading: false}
profileTypes: {isLoading: false}
reviewers: {isLoading: false}
route: {}
salutations: {isLoading: false}
sectors: {isLoading: false, sectors: Array(0)}
siteInfo: {pageTitle: "", isLoading: 0, hasError: 0, error: "", site: {…}, …}
sort: {value: "", dir: ""}
user: {isLoading: false, loginChecked: {…}, admin: null, reviewer: null, loginTokenLoading: false, …}
_persist: {version: -1, rehydrated: true}
__proto__: Object
}
I have this working with the following setup within NextJs.
When window isn't defined (on the server), I render the app without the PersistGate.
The store configuration is required to not take storage, which I determine based on the passed prop.
class MyApp extends App {
public render() {
const { Component, pageProps } = this.props;
if (typeof window === "undefined") {
const { store } = configureStore();
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
const { store, persistor } = configureStore(storage);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
);
}
}
export default MyApp;
const configureStore = (passedStorage?: AsyncStorage | WebStorage) => {
const combinedReducers = combineReducers({
conjugations: conjugationReducer
});
if (!passedStorage) {
const store = createStore(combinedReducers);
return { store };
}
const persistConfig = {
key: "root",
storage: passedStorage
};
const persistedReducer = persistReducer(persistConfig, combinedReducers);
const store = createStore(
persistedReducer
);
const persistor = persistStore(store);
return { store, persistor };
};
When you use Redux-persist with SSR, It creates crashes, problems like It shows you white screen for 1-5 seconds and then shows the page.
It's a problem of Persist + Hydrate, To solve it, try below solution. :)
<PersistGate>
and use the code like belowCode
function Main() {
return (
<Provider store={store}>
// Don't use <PersistGate> here.
<Router history={history}>
{ Your other code }
</Router>
</Provider>
);
}
persistor.subscribe(() => {
/* Hydrate React components when persistor has synced with redux store */
const { bootstrapped } = persistor.getState();
if (bootstrapped) {
ReactDOM.hydrate(<Main />, document.getElementById("root"));
}
});
One solution which worked for me is the following -
For me, it works well if all the variables that I'm trying to persist are part of components. Other variables you can manage using post calls to the server (using {axios} within the components).
Check this repo for creating store without redux-persist - follow above steps after that - https://github.com/alexnm/react-ssr/tree/fetch-data
// in client.js
import {createStore as createPersistedStore} from 'redux';
import createStore, { reducers } from './store';
const persistConfig = {
key: 'app',
storage,
}
const persistedReducers = persistReducer(persistConfig, reducer);
// creating a persisting store in client.js only
const store = createStore(persistedReducers, window.REDUX_DATA);
const persistor = persistStore(store);
const jsx = (
<ReduxProvider store={store}>
<PersistGate loading={"loading from store"} persistor={persistor}>
<Router>
<App />
</Router>
</PersistGate>
</ReduxProvider>
);
const app = document.getElementById("app");
ReactDOM.hydrate(jsx, app);
// end client.js
// in server.js - only see createStore as well as const jsx object, and a dummy context
import createStore, { reducers } from './store';
const app = express()
app.get( "/*", (req, res, next) => {
const context = {};
// not created using persisted store in the server - don't have to
const store = createStore(reducer);
// define data with the routes you need (see the github repo)
Promise.all(data).then(() => {
const jsx = (
<ReduxProvider store={store}>
<StaticRouter context={context} location={req.url}>
<App />
</StaticRouter>
</ReduxProvider>
);
const reactDom = renderToString(jsx);
const reduxState = store.getState();
// more code for res.end
});
});
// end server.js
// in store.js
import {createStore, combineReducers, applyMiddleware} from "redux";
// your actions and reducers
export const reducer = combineReducers({
// reducers
)};
export default (reducerArg, initialState) =>
createStore(reducerArg,initialState);
// end store.js
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With