React Server Side Rendering not outputting index page - javascript

I've been playing with Server Side Rendering (SSR) with React and I've pretty much got it working, with the exception of the homepage not loading from the server.
If I use alternative routes e.g /test or /404 it loads from the server. If I go to the / index path, it just loads my react app, but not html from the server. I tested this by disabling javascript and noticed it happening.
Here is my server code I'm using:
import path from 'path';
import fs from 'fs';
import React from 'react';
import express from 'express';
import cors from 'cors';
import configureStore from '../src/redux/store/configureStore';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom';
import { renderToString } from 'react-dom/server';
import Client from '../src/client/index.jsx';
/* Express settings */
const app = express();
const PORT = process.env.PORT || 3334;
app.disable('x-powered-by');
app.use(cors());
app.use(express.static('./dist'));
app.use(handleRender);
/* Handle React Application */
function handleRender(req, res) {
const context = {};
const store = configureStore({});
const indexFile = path.resolve('./dist/index.html');
const html = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<Client />
</StaticRouter>
</Provider>
);
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err);
}
res.status(context.status||200).send(
data.replace(
'<div id="root"></div>',
`
<div id="root">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(store)}
</script>
`
)
);
});
}
/* Listen for connections */
app.listen(PORT, () => {
console.log(`💻 😎 Server is running on port ${PORT}`);
});
Here is my primary react component with the routes:
import React from 'react';
import {
Switch,
Route
} from 'react-router-dom';
import App from './components/App/index.jsx';
import NotFound from './NotFound.jsx';
const Test = () => (
<h1>Test!</h1>
);
const Bruh = () => (
<h1>Bruh!</h1>
);
const Client = () => (
<Switch>
<Route path="/" exact component={App} />
<Route path="/test" exact component={Test} />
<Route path="/bruh" exact component={Bruh} />
<Route component={NotFound} />
</Switch>
);
export default Client;
Finally, here is my index.js file for my react app:
import React from 'react';
import { hydrate } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/store/configureStore';
import Client from './client/index.jsx';
import './styles/index.css';
/* Redux */
const preloadedState = window.__PRELOADED_STATE__ = {}; //initial state
delete window.__PRELOADED_STATE__;
const store = configureStore(preloadedState);
const unsubscribe = store.subscribe( () => {
console.log("Store: ", store.getState());
});
/* Render / Hydrate App */
hydrate(
<Provider store={ store }>
<BrowserRouter>
<Client />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
I think that's everything. Let me know if you need more info. Any advice and help would be greatly appreciated.
Thanks!

Related

Keycloak-js & #react-keycloak/web - Authenticate without login page

with my enterprise we are passing to Keycloak & I'm making test on our existing apps.
Anyway, I am testing actually on a React App which is working to a Symfony API. I created the two clients in Keycloak, with the roles, and users.I adapted the API authenticator for Keycloak, and it's working great.
On front side, I changed the Axios middleware to send the Keycloak's token to the API.
When I'm testing my requests on Insomnia, I get my token and refresh token, and all requests that I'm sending works nicely too ! And on Keycloak's Admin panel the sessions are active as well.
Problem is on client side. I installed keycloak-js and #react-keycloak/web because of the hooks that are usefull for me, to test if user is authenticated, to get roles, to protect pages depending on the role etc...
The thing is when I'm login, I get my tokens, session is active on KC side, but KC-js adapter don't tell user is authenticated, I think because user is not login with keycloak.login which is the Login page of KC. I don't want to log on his page, application already have its own login page. But whatever I'm testing, even I ge my token and the session is well active on KC, the hook keycloak.authenticated is always on false.
Have you any suggests on that ?
index.tsx
import ReactDOM from 'react-dom/client';
import 'bootstrap/dist/css/bootstrap.css'
import './index.css';
import './i18n';
import App from './App';
import i18next from 'i18next';
import { I18nextProvider } from 'react-i18next';
/*------ STORE REDUX PART ------*/
import { Provider, TypedUseSelectorHook } from "react-redux";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import rootReducer from './Api/Reducers/rootReducer';
import { configureStore, PreloadedState } from "#reduxjs/toolkit"
import { ReactKeycloakProvider } from '#react-keycloak/web';
import keycloak from './components/keycloak';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
export const makeStore = (preloadedState?: PreloadedState<RootState>) => {
return configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false
}),
devTools: true,
preloadedState
})
}
const store = makeStore();
const tokenLogger = (tokens: unknown) => {
console.log('onKeycloakTokens', tokens)
}
const keycloakProviderInitConfig = {
onLoad: 'check-sso',
}
root.render(
<ReactKeycloakProvider
authClient={keycloak}
onTokens={tokenLogger}
initOptions={keycloakProviderInitConfig }
>
<Provider store={store}>
<I18nextProvider i18n={i18next}>
<App />
</I18nextProvider>
</Provider>
</ReactKeycloakProvider>
);
export default store;
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof makeStore>;
export type AppDispatch = typeof store.dispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = () => useDispatch<AppDispatch>();
intercept.tsx
import axios from "axios";
export const instance = axios.create({
baseURL: process.env.REACT_APP_API_ADDRESS,
headers: {'X-gravitee-api-key': process.env.REACT_APP_API_KEY}
});
const kcInstance = axios.create({
baseURL: process.env.REACT_APP_KEYCLOAK_ADDRESS,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded',
'X-gravitee-api-key': process.env.REACT_APP_API_KEY
}
})
/*----- API Middleware Request -----*/
/*----- Pour tester le SessionID -----*/
kcInstance.interceptors.request.use(
(config) => {
let profile = localStorage.getItem('profile');
let kcStorage = JSON.parse(localStorage.getItem('keycloak'));
if(kcStorage){
let kcToken = kcStorage.access_token;
config.headers['Authorization'] = `Bearer ` + kcToken;
if(profile) {
const userDatas = JSON.parse(profile);
let token = userDatas.sessionId;
let language = localStorage.getItem('language');
let datas = {
sessionId: token,
language: language
}
const bearerInstance = axios.create({
baseURL: process.env.REACT_APP_API_ADDRESS,
headers: {
'X-gravitee-api-key': process.env.REACT_APP_API_KEY,
'Authorization': `Bearer ` + kcToken
}
})
bearerInstance.post('/api/check-session', datas)
.then(res => {
if(res.status === 200) return axios(config);
})
.catch(
(error) => {
if(error.response?.status === 404 && error.response?.data.message.includes("Session")) {
localStorage.clear();
window.location.href= ("/");
}
else if(error.response?.status === 404 && error.response?.data.message.includes("session")) {
localStorage.clear();
window.location.href= ("/");
}
})
}
}
return config;
},
error => {
console.log('error request :' + error)
Promise.reject(error)
});
App.tsx
import { Suspense, useEffect, useState } from 'react';
import SiderLayout from './components/Menu/Layout';
import Assistance from './pages/Assistance';
import ChangePassword from './pages/ChangePassword';
import ForgetPassword from './pages/ForgetPassword';
import Login from './pages/Login';
import RenewIDToken from './pages/RenewIDToken';
import {
BrowserRouter as Router,
Navigate,
Route,
Routes
} from 'react-router-dom'
import ConsultOrder from './pages/ConsultOrder';
import ConsultRepair from './pages/ConsultRepair';
import StockManagement from './pages/StockManagement';
import Consumptions from './pages/Consumptions';
import Contractuals from './pages/Contractuals';
import DetailSerial from './pages/DetailSerial';
import DetailsMortgage from './pages/DetailsMortgage';
import Home from './pages/Home';
import GlobalStyles from './GlobalStyles';
import { useKeycloak } from '#react-keycloak/web';
import PrivateRoute from './Keycloak/PrivateRoute';
import HasAccess from './Keycloak/HasAccess';
function App() {
const { initialized, keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
if(!initialized) {
return <h3>Chargement... !!!</h3>
}
return (
<Suspense fallback="loading">
<Router>
<GlobalStyles/>
<Routes>
<Route path="/assistance" element={<Assistance/>} />
<Route path="/forget-password" element={<ForgetPassword/>} />
<Route path="/change-password" element={<ChangePassword/>} />
<Route path="/renew-token" element={<RenewIDToken/>} />
{ isLoggedIn ?
<Route path="/" element={<SiderLayout profil={HasAccess(['app-admin'])} />}>
<Route index element={<Home/>}/>
<Route path="/consult-repair" element={<ConsultRepair/>} />
<Route path="/stock-management" element={<StockManagement/>} />
<Route path="/detail-serial/:nno/:sn" element={<DetailSerial/>} />
<Route path="/detail-mortgage/:mortgageNumber" element={<DetailsMortgage/>} />
{ HasAccess(['app-admin']) &&
<>
<PrivateRoute path="/consult-order" component={<ConsultOrder/>} roles={["app-admin"]} />
<Route path="/consumptions-indicators" element={<Consumptions/>} />
<Route path="/contractual-indicators" element={<Contractuals/>} />
</>
}
</Route>
:
<Route path="/" element={<Login/>} /> }
<Route path="*" element={<Navigate to="/" replace/>} />
</Routes>
</Router>
</Suspense>
);
}
export default App;

My react route is redirecting to localhost:3000 and not the page it should redirect to

I have 2 components both are exactly the same. one is redirected to when I click on a Navlink inside of my navbar that I created using react-bootstrap. The other component that is exactly the same just redirects to localhost:3000 and not "./member" when I click on the html button that should redirect to that component. Please help me.
the html button and the function to redirect look like
import {Link, Route, withRouter, useHistory} from 'react-router-dom'
const Posts = (props) => {
const dispatch = useDispatch();
const history = useHistory();
const getProfile = async (member) => {
// const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
history.push('/member')
}
return (
<div>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</div>
)
}
export default withRouter(Posts);
The routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
</Switch>
)
}
export default Routes
The component that I am trying to redirect to is exactly the same as one that is redirected to and working when I click on it from the navlink. I have downgraded to history 4.10.1
My index.js is
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Router, Route } from 'react-router-dom';
import * as history from 'history';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer)
const userHistory = history.createBrowserHistory();
ReactDOM.render(
<Provider store = {store}>
<Router history={userHistory}>
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
</Router>
</Provider>,
document.getElementById('root'));
serviceWorker.unregister();
When I wrap the app route in the url goes to ./member but it does not load.
<Switch>
<Route path="/" exact component={app} />
<Route path="/member" component={Member} />
</Switch>
<button onClick={() => history.push(`/${p.publisher}`)}>Profile</button>

What does it meant - Browser history needs a DOM? [duplicate]

I am trying server side rendering using react-router 4. I am following the example provided here https://reacttraining.com/react-router/web/guides/server-rendering/putting-it-all-together
As per the example on server we should use StaticRouter. When I import as per the example I am seeing StaticRouter as undefined
import {StaticRouter} from 'react-router';
After doing some research online I found I could use react-router-dom. Now my import statement looks like this.
import {StaticRouter} from 'react-router-dom';
However when I run the code I am getting Invariant Violation: Browser history needs a DOM in the browser.
my server.js file code
....
app.get( '*', ( req, res ) => {
const html = fs.readFileSync(path.resolve(__dirname, '../index.html')).toString();
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context} >
<App/>
</StaticRouter>
);
if (context.url) {
res.writeHead(302, {
Location: context.url
})
res.end();
} else {
res.send(html.replace('$react', markup));
}
} );
....
And my client/index.js code
....
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), root);
....
Update v1
Reduced my example to a bear minimum and still getting the same error.
clientIndex.js
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from '../App'
ReactDOM.render((
<BrowserRouter>
<App/>
</BrowserRouter>
), document.getElementById('app'))
serverIndex.js
import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from '../App'
createServer((req, res) => {
const context = {}
const html = ReactDOMServer.renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
)
res.write(`
<!doctype html>
<div id="app">${html}</div>
`)
res.end()
}).listen(3000);
App.js
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import routes from "./client/routes";
const App = ( ) => (
<Router>
<Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
</Router>
)
export default App;
You need to use different history provider for server side rendering because you don't have a real DOM (and browser's history) on server. So replacing BrowserRouter with Router and an alternate history provider in your app.js can resolve the issue. Also you don't have to use two wrappers. You are using BrowserRouter twice, in app.js as well as clientIndex.js which is unnecessary.
import { Route, Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
const history = createMemoryHistory();
<Router history={history}>
<Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
</Router>
You can now replace StaticRouter with ConnectedRouter which can be used both in client and server. I use the following code to choose between history and export it to be used in ConnectedRouter's history.
export default (url = '/') => {
// Create a history depending on the environment
const history = isServer
? createMemoryHistory({
initialEntries: [url]
})
: createBrowserHistory();
}
In clientIndex.js
Rather than BrowserRouter use StaticRouter.
import { BrowserRouter } from 'react-router-dom';
import { StaticRouter } from 'react-router-dom'
As is essentially noted in the comments, one may hit this error (as I have) by accidentally wrapping your App component in a <BrowserRouter>, when instead it is your client app that should be wrapped.
App.js
import React from 'react'
const App = () => <h1>Hello, World.</h1>
export default App
ClientApp.js
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import ReactDOM from 'react-dom'
import App from './App'
const render = Component => {
ReactDOM.render(
<BrowserRouter>
<Component />
</BrowserRouter>,
document.getElementById('app')
)
}
render(App)
See also the React Router docs.

react-router doesn't render components on server side

I using react-router and server side rending . but my server side not work with router . it change URL but doesn't render components
My server side code:
import express from 'express';
import cors from "cors";
import { renderToString } from "react-dom/server";
import App from '../shared/App';
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from "../shared/reducers/index";
import { StaticRouter, matchPath } from "react-router-dom"
import { create } from 'domain';
const app = express();
app.use(express.static("public"));
app.use(cors());
app.use("*", (req, res, next) => {
let context = {};
const store = createStore(rootReducer)
const markup = renderToString(
<Provider store = {store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
)
const preloadedState = store.getState();
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR with RR</title>
</head>
<body>
<div id="app">${markup}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}
</script>
<script src = "/bundle.js"></script>
</body>
</html>
`)
});
app.listen(3000, () => {
console.log('Server is running ....')
});
And this is my App code :
import React from 'react';
import { Route } from 'react-router-dom';
import List from "./List";
import { Link } from 'react-router-dom';
import Home from './Home';
import { connect } from "react-redux";
class App extends React.Component {
render() {
return (
<div>
Hello
<ul>
<li><Link to="/list">List</Link></li>
<li><Link to="/home"> Home</Link></li>
</ul>
<Route component={List} path="/list" />
<Route component={Home} path="/home" exact />
</div>
);
}
};
export default connect(null,null)(App);
my List and Home components render string "Home" and "List"
my index.js code
import React from "react";
import ReactDOM from "react-dom";
import App from "../shared/App";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import rootReducer from "../shared/reducers/index";
import { BrowserRouter } from 'react-router-dom';
const preloadedState = window.__PRELOADED_STATE__;
delete window.__PRELOADED_STATE__
const store = createStore(rootReducer, preloadedState);
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById("app"));
This is my all code it CHange URL but dont load component
Solved the problem , i have to add withRouter from react-router-dom to app component
withRouter(connect(null,null)(App));

Testing react-router v4 routing with redux connected components

I am trying to verify that my routes are properly set up. As in when I have a certain URI, then the appropriate component gets loaded. Just to prevent me from mistyping a path accidentally.
Here's a sample connected page:
import React from 'react';
import {connect} from 'react-redux';
export const ConnectPage = (props) => {
return <div>{props.code}</div>;
};
export default connect(
(state) => ({}),
(dispatch) => ({})
)(ConnectPage);
Main Page:
import React from 'react';
import LoginPage from './LoginPage';
const MainPage = () => (
<LoginPage/>
);
export default MainPage;
Here's my routes file:
import React from 'react';
import {Route, Switch, Redirect} from 'react-router-dom';
import qs from 'query-string';
import MainPage from './MainPage';
import ConnectPage from './ConnectPage';
class Routes extends React.Component {
render() {
return (<Switch>
<Route path={'/connect/:provider'} render={(matches) => {
const search = qs.parse(matches.location.search);
if (search.code) {
return <ConnectPage code={search.code}/>;
}
return <Redirect to={'/'}/>;
}}/>
<Route exact path={'/'} component={MainPage}/>
</Switch>
);
}
}
export default Routes;
And here's my test that I'm having troubles with:
import React from 'react';
import {mount} from 'enzyme';
import {MemoryRouter} from 'react-router';
import configureStore from 'redux-mock-store';
import Chance from 'chance';
import Routes from './Routes';
import MainPage from './MainPage';
import ConnectPage from './ConnectPage';
import {createProvider as Provider} from 'react-redux';
const chance = new Chance();
const middlewares = [];
let mockStore;
describe('The Routes', () => {
beforeAll(()=>{
mockStore = configureStore(middlewares);
});
it('loads the Main Page on /', () => {
const component = mount(
<MemoryRouter initialEntries={['/']} initialIndex={0}>
<Routes store={mockStore}/>
</MemoryRouter>
);
expect(component.find(MainPage).length).toBe(1);
});
it('redirects to the main page if no code', () => {
const url = '/connect/someprovider';
const component = mount(
<MemoryRouter initialEntries={[url]} initialIndex={0}>
<Routes/>
</MemoryRouter>
);
expect(component.find(MainPage).length).toBe(1);
});
it('loads the Connect Page on /connect/provider', () => {
const code = chance.word({length: 32});
const url = `/connect/provider?code=${code}`;
const component = mount(
<Provider store={mockStore}>
<MemoryRouter initialEntries={[url]} initialIndex={0}>
<Routes/>
</MemoryRouter>
</Provider>);
try {
expect(component.find(ConnectPage).length).toBe(1);
} catch (e) {
console.log('Used url:', url);
throw (e);
}
});
});
I can't for the life of me figure out how to make the 3rd test pass. I would just want to make sure that the ConnectedPage gets put in place when it's appropriate.
In this state, I get:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
in createProvider (created by WrapperComponent)
in WrapperComponent
I have found an answer in the meantime, so I'll post it here for prosperity. If you know anything better, please let me know!
Here's the revised test case:
it('loads the Connect Page on /connect/provider', () => {
const code = chance.word({length: 32});
const url = `/connect/provider?code=${code}`;
const component = mount(
<MemoryRouter initialEntries={[url]} initialIndex={0}>
<Routes/>
</MemoryRouter>,
{
context: {
store: mockStore()
},
childContextTypes: {
store: PropTypes.object.isRequired
}
});
try {
expect(component.find(ConnectPage).length).toBe(1);
} catch (e) {
console.log('Used url:', url);
throw (e);
}
});
Calling the mockStore seemed to be the main solution instead of just passing it as is in the context. I'm not sure why this works, but it does. I'll roll with it for now, but would love to hear some explanations so that I can learn.

Categories

Resources