Browser history needs a DOM - reactjs

There are already questions around this topic with solutions pointing to memoryHistory on server. However, I did that and the problem still remains to be on store.js which is inside client folder. Although I am not using this client/store.js it still gives me same error as the subject.
So my guess is there is something wrong in the way I am wrapping up my component on server or client side.
I am working on already loaded project stack - redux, react-redux, react-router-redux, redux-thunk, history etc... and these are honestly daunting for me to make an addition for a setup for SSR -server side rendering (my basic motive)
I will share my structure and important files which are involved in this exercise !
Let's welcome the server first.
server/bootstrap.js
require('ignore-styles');
require('babel-register')({
ignore: [ /(node_modules)/ ],
presets: ['es2015', 'react-app']
});
require('./index');
server/index.js
import express from 'express';
import serverRenderer from './middleware/renderer';
const PORT = 3000;
const path = require('path');
const app = express();
const router = express.Router();
// root (/) should always serve our server rendered page
router.use('^/$', serverRenderer);
// other static resources should just be served as they are
router.use(express.static(
path.resolve(__dirname, '..', 'build'),
{ maxAge: '30d' },
));
router.use('*', serverRenderer);
// tell the app to use the above rules
app.use(router);
// start the app
app.listen(PORT, (error) => {
if (error) {
return console.log('something bad happened', error);
}
console.log("listening on " + PORT + "...");
});
server/middleware/renderer.js
import React from 'react'
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import store from '../../src/store';
import {createMemoryHistory } from 'history';
import { StaticRouter } from 'react-router'
// import our main App component
import App from '../../src/index';
const path = require("path");
const fs = require("fs");
const history = createMemoryHistory({
initialEntries: ['/', '/next', '/last'],
initialIndex: 0
})
export default (req, res, next) => {
const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end()
}
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
);
return res.send(
htmlData.replace(
'<div id="root"></div>',
`<div id="root">${html}</div>`
)
);
});
}
My src folder structure will be
components
containers
app
index.js
styles.js
index.js
store.js
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { StaticRouter } from 'react-router'
import './index.css';
import store, { history } from './store';
import App from './containers/app';
const target = document.querySelector('#root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
target
);
The tough nut to understand
src/store.js
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './reducers';
export const history = createHistory();
const initialState = {};
const enhancers = [];
const middleware = [thunk, routerMiddleware(history)];
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension;
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);
const store = createStore(rootReducer, initialState, composedEnhancers);
export default store;
on running node server/bootstrap.js, it give me an error
Invariant Violation: Browser history needs a DOM and this error generates in store.js
If someone could please let me know by the code shared what and where I am doing wrong to my current create-react-app for SSR!

I was loading the App twice. Once in client and other in server.
I rendered in server alone with the same setup (Provider and connected router) and importantly changed the createBrowserHistory to createMemoryHistory and it worked.

Related

Set up Server Side Rendering for react js but it is not working while deploying to the firebase

I was having a problem with SEO in my react app so I did Server-side rendering. At first, I changed '.render' to '.hydrate' in src/index.js. I created a server folder in it server.js and index.js. But it's still showing only the script tag with 'root' in the source code in the browser after deploying to firebase.
src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.css";
import { hydrateRoot } from "react-dom/client";
const container = document.getElementById("root");
const root = hydrateRoot(container, <App />);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
server.js
import path from "path";
import fs from "fs";
import express from "express";
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "../src/App";
const PORT = 3000;
const app = express();
const router = express.Router();
const serverRenderer = (req, res, next) => {
fs.readFile(path.resolve("./build/index.html"), "utf8", (err, data) => {
if (err) {
console.error(err);
return res.status(500).send("An error occurred");
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
);
});
};
router.use("^/$", serverRenderer);
router.use(
express.static(path.resolve(__dirname, "..", "build"), { maxAge: "30d" })
);
// tell the app to use the above rules
app.use(router);
// app.use(express.static('./build'))
app.listen(PORT, () => {
console.log(`SSR running on port ${PORT}`);
});
index.js
require("ignore-styles");
require("#babel/register")({
ignore: [/(node_modules)/],
presets: ["#babel/preset-env", "#babel/preset-react"],
});
require("./server");
It is working when I'm running 'node server/server.js' in the terminal but when I'm running 'npm start' it's not working or when I'm deploying it to firebase with 'firebase deploy' after 'npm run build'.
Thank you.

Uncaught TypeError: Cannot read properties of undefined (reading 'dispatch')

I'm currently trying to use React + Typescript + Redux and I'm running into an issue. I'm trying to test the Redux Store setup via chrome devTools. I know I butchered the code (very new to Typescript) and I'm getting this error 'Uncaught TypeError: Cannot read properties of undefined (reading 'dispatch')' every time I test it. I tried declaring a global window state, installed redux-dev-tools, but still very lost.
This is what my store/index.tsx file look like:
import {
legacy_createStore as createStore,
combineReducers,
applyMiddleware,
compose,
StoreEnhancer,
} from "redux";
import { devToolsEnhancer } from "redux-devtools-extension";
import thunk from "redux-thunk";
const rootReducer = combineReducers({});
let enhancer;
if (process.env.NODE_ENV === "production") {
enhancer = applyMiddleware(thunk);
} else {
const logger = require("redux-logger").default;
const composeEnhancers =
(window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
enhancer = composeEnhancers(applyMiddleware(thunk, logger));
}
const configureStore = () => {
return createStore(rootReducer, devToolsEnhancer({}));
};
export default configureStore;
and my types/index.d.ts:
import { StoreEnhancer } from 'redux'
export {};
declare global {
interface Window {
store: {};
__REDUX_DEVTOOLS_EXTENSION__?: () => StoreEnhancer;
}
}
And finally my src/index.tsx:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import configureStore from "./store";
const store = configureStore();
if (process.env.NODE_ENV !== "production") {
window.store = store;
};
function Root() {
return (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Root />
</React.StrictMode>
);
I will also attach screenshots of my file setup:
And my console error:
I am open to all suggestions, thank you!
The first issue here is that the store setup code is using outdated patterns and a lot of handwritten code. Today, you should be using our official Redux Toolkit package to write your Redux apps, and as part of that, RTK's configureStore API. It does all that same work with just a few lines:
import { configureStore } from "#reduxjs/toolkit";
const store = configureStore({
reducer: {
posts: postsReducer,
comments: commentsReducer
}
})
That automatically combines reducers, adds the Redux DevTools extension setup, and adds the thunk middleware.
See our docs for guidance on setup:
https://redux.js.org/tutorials/quick-start
https://redux.js.org/tutorials/typescript-quick-start
https://redux.js.org/introduction/why-rtk-is-redux-today
https://redux.js.org/tutorials/essentials/part-2-app-structure
As for the specific error message you're seeing... the code seems like it would run. My guess is that something is wrong with the process.env.NODE_ENV check you added and so it's not assigning window.store.
RTK also works much better with TypeScript than legacy Redux code does.

React with redux-persist when navigate the UI is stuck

When I navigate through the app the UI is stuck although the url changes.
I would like to integrate redux-persist on my current app but it eventually drove me to a strange bug to me.
Note: I use also the redux-saga as middleware on creating the store.
store.js
import { createStore, applyMiddleware, compose } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web and AsyncStorage for react-native
import createSagaMiddleware from 'redux-saga'
import rootReducer from "../reducers/index";
import rootSaga from '../sagas/index'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const sagaMiddleware = createSagaMiddleware()
const middleware = applyMiddleware(sagaMiddleware)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
persistedReducer,
{},
composeEnhancers(middleware)
)
export const persistor = persistStore(store)
sagaMiddleware.run(rootSaga)
export default store
window.store = store
When I comment in the Persist Gate component then the navigation works as intended.
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from 'react-redux'
import registerServiceWorker from "./js/registerServiceWorker";
import { PersistGate } from 'redux-persist/integration/react'
import store, { persistor } from './js/store';
ReactDOM.render(
<Router>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</Router >,
document.getElementById("root")
);
registerServiceWorker();
I hope I made myself clear!
Try wrapping your Router with the PersistGate. The order of these higher order components matters for React Router. The way you have it now, when you change the url it's not triggering a re-render, so swapping the order should fix the issue.

Invariant Violation: Browser history needs a DOM

For the server side, the BrowserRouter will not work and thus StaticRouter is to be used as per documentation. I am doing same but I am still getting the error. Following is my setup
Invariant Violation: Browser history needs a DOM
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter as Router } from 'react-router';
// import our main App component
import App from '../src/App';
const path = require('path');
const fs = require('fs');
export default (req, res) => {
// get the html file created by CRA's build tool
console.log('url : ', req.baseUrl);
const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end();
}
// render the app as a string
const html = ReactDOMServer
.renderToString(<Router location={req.baseUrl}>
<App />
</Router>);
// now inject the rendered app into our html and send it
return res.send(htmlData
.replace('<div id="root"></div>', `<div id="root">${html}</div>`));
});
};

Redux Hot Reload Warning on changes

I get the following warning when I try to update any of my react components...
Provider does not support changing store on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.
As far as I can tell, my code looks like the instructions, but I still get the warning.
client.js
'use strict'
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import createStore from '../shared/store/createStore';
import routes from '../shared/routes';
const store = createStore(window.__app_data);
const history = browserHistory;
if (window.__isProduction === false) {
window.React = React; // Enable debugger
}
if (module.hot) {
module.hot.accept();
}
render (
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>,
document.getElementById('content')
)
configureStore.js
'use strict';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers';
import { selectSubreddit, fetchPosts } from '../actions'
export default function createReduxStore(initialState = {}) {
const store = createStore(reducers, initialState, applyMiddleware(thunk));
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers').default;
store.replaceReducer(nextReducer);
});
}
return store;
};
Server.js
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import webpackConfig from '../../webpack.config.dev';
let compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
hot: true,
noInfo: true,
publicPath: webpackConfig.output.publicPath
}));
app.use(webpackHotMiddleware(compiler));
Is there something I'm missing? Here is a link to the full Github Repo if you want to see the full src.
[Edited] Added server.js and github link.
Found the answer. There were multiple changes needed.
Remove module.hot code from client.js (Hot reloading that file caused Redux and React-Router warnings.
Create an index file for my React page components to export from.
Add module.hot code to newly created index file.
Change all React components to classes. const Page = () => () doesn't re-render with hot reloading.
After making those changes, everything started to work properly and I get no more warnings :-)

Resources