history.push not causing the location to change when ssr - react-router

I have the following server code:
const store = configureStore({}, history);
const context: any = { store };
const app = (
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<Application />
</StaticRouter>
</Provider>
);
// render app
And the following client side Connected Router:
<Provider store={store}>
<ConnectedRouter history={history}>
<Application />
</ConnectedRouter>
</Provider>
I can see that the middleware is correctly hooked up and the history.push method is being called here.
But the browser does not redirect.
What would cause the browser not to redirect?

It turns out I had 2 different instances of history, 1 in the router middleware and 1 in the ConnectedRouter.
Ensuring they both had the same instance fixed the problem.
I have this file which I import it wherever I need it:
import { createBrowserHistory } from 'history';
import { createMemoryHistory } from 'history';
import { RouterHistory } from '../types';
const selectedHistory: RouterHistory = typeof window !== 'undefined' ? createBrowserHistory : createMemoryHistory;
export const history = selectedHistory();
And then make sure I import this file and don't make multiple copies of the history:
import { history } from '../../routes/history';
//etc.
<ConnectedRouter history={history}>

Related

redux persist in the next js project persist:root is not creating localstorage

using the nextjs framework the redux-persist is not creating the local storage values. After login the persist:root is not showing in the local
In the reactjs framework, I have tried the redux persist the persist:root created in the local storage but in the nextjs framework the same method I am following the errors not coming but the persist:root is not showing
in the local storage
//store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
import { persistReducer } from 'redux-persist';
import nextConnectRedux from 'next-connect-redux';
import storage from 'redux-persist/lib/storage/session';
const persistConfig = {
key: "root",
storage: storage,
}
const persistedReducer = persistReducer(persistConfig, reducers)
const middleware = [thunk];
const composeEnhancers =
typeof window !== 'undefined' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
);
const store = () => {
return createStore(
persistedReducer,
{},
enhancer
)};
const nextConnect = nextConnectRedux(store)
export default nextConnect;
// index.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import nextConnect from '../store';
import Route from '../routes';
import { BrowserRouter } from "react-router-dom";
class App extends Component {
render () {
const persistor = persistStore(nextConnect);
return (
<Provider store={nextConnect}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<Route/>
</BrowserRouter>
</PersistGate>
</Provider>
)
}
}
export default App;
I want the persist:root in the local storage to be showed
if you are rendering React on the server, you cant use redux-persist with default configs
When you are using redux-persist with the default configuration, means you are storing your data on browser storage and you don’t have a browser on the server
The following implementation shows you how to integrate Redux Persist into Next.js
rootReducer.js
store.js
_app.js
That's it
resource
Redux Persist with Next.js

MemoryRouter and jest test

https://reacttraining.com/react-router/web/guides/testing
The react-router testing documentation is bit obscure to me.
How to write a test to check a route is rendered
A Component. - APage.js
import React, { Component } from 'react'
export default class APage extends Component {
render() {
return (
<div>
A Page
</div>
)
}
}
Writing a unit test to check , as per documentation.
routes.test.js
import React from 'react'
import { render } from "react-dom";
import APage from './APage'
import {MemoryRouter} from 'react-router-dom';
test("render route", () => {
render(
<MemoryRouter initialEntries={["/apage"]}>
<APage />
</MemoryRouter>
);
});
It gives an error,
Invariant Violation: Target container is not a DOM element.
for render.
How do I write a basic test, like to test a component is rendered on a route.
I'd like to comment on Remi's solution, since the API in React Router v6 is a little different (and the link to the docs leads now to a 404):
import { Router } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
render(
<Router
location={history.location} // history.location has a default value of '/'
navigator={history}
>
<APage />
</Router>
);
})
see github repo here
I think you should use Router instead. Since that uses BrowserRouter. (see alternatives section on react router example page)
import { Router } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
history.push('/apage');
render(
<Router history={history}>
<APage />
</Router>
);
});
It could be that you should also add your page in a Route, but I'm not sure.
Then it would be something like:
import { Router, Route } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
history.push('/apage');
render(
<Router history={history}>
<Route path='/aroute' render={(props) => (<APage {...props} />)} />
</Router>
);
});
Ok. Route testing has to be done by enzyme. not just using jest.
Followed https://medium.com/#antonybudianto/react-router-testing-with-jest-and-enzyme-17294fefd303
Used enzyme mount to test.

How to Access History Object Outside of a React Component

First of all, I am pretty familiar with the withRouter HoC, however, in this case, it doesn't help because I do not want to access the history object in a component.
I am trying to achieve a mechanism that will redirect the user to the login page if I receive back a 401 from a API endpoint. For making http requests I am using axios. I have around 60 endpoints that I need to cover, that are used in a dozen of components throughout my app.
I want to create a decorator function to the axios instance object, that:
1. makes the request
2. if fail && error_code = 401, update user route to `/login`
3. if success, return promise
The problem I have with the above is to update the route of the user. Previously, in react-router-v3, I could have imported the browserHistory object directly from the react-router package, which is no longer possible.
So, my question is, how can I access the history object outside of the React Component without passing it trough the call stack?
react-router v4 also provides a way to share history via the history package, namely createBrowserHistory() function.
The important part is to make sure that the same history object is shared across your app. To do that you can take advantage of the fact that node modules are singletons.
Create a file called history.js in your project, with the following content:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
You can then just import it in your application via:
import history from "./history.js";
Please note that only Router accepts the history prop (BrowserRouter does not), so be sure to update your router JSX accordingly:
import { Router } from "react-router-dom";
import history from "./history.js";
// and then in your JSX:
return (
<Router history={history}>
{/* routes as usuall */}
</Router>
)
A working example can be found at https://codesandbox.io/s/owQ8Wrk3
Today, I faced the same issue. Maybe my solution helps somebody else.
src/axiosAuthenticated.js
import axios from 'axios';
import { createBrowserHistory } from 'history';
const UNAUTHORIZED = 401;
axios.interceptors.response.use(
response => response,
error => {
const {status} = error.response;
if (status === UNAUTHORIZED) {
createBrowserHistory().push('/');
window.location.reload();
}
return Promise.reject(error);
}
);
export default axios;
Also, if you want to intercept any request to add token stored in LocalStorage:
let user = JSON.parse(localStorage.getItem('user'));
var authToken = "";
if (user && user.token)
authToken = 'Bearer ' + user.token;
axios.defaults.headers.common = {'Authorization': `${authToken}`}
To use it, instead of importing from 'axios', import from 'axiosAuthenticated' like this:
import axios from 'utils/axiosAuthenticated'
Here is a solution that worked for me in latest version(5.2.0)
router/index.js
import { BrowserRouter, Switch } from "react-router-dom";
import { Routes } from "./routes";
export const Router = () => {
return (
<BrowserRouter>
<Switch>
<Routes />
</Switch>
</BrowserRouter>
);
};
router/routes.js
import React, { createRef } from "react";
import { Route, useHistory } from "react-router-dom";
import { PageOne, PageTwo, PageThree } from "../pages";
export const historyRef = createRef();
export const Routes = () => {
const history = useHistory();
historyRef.current = history;
return (
<>
<Route exact path="/" component={PageOne} />
<Route exact path="/route-one" component={PageTwo} />
<Route exact path="/route-two" component={PageThree} />
</>
);
};
And use it as below
historyRef.current.replace("/route-two");
I just encountered this same issue, and following is the solution I used to solve this problem.
I ended up creating a factory function which returns an object that has all my services functions. In order to call this factory function, an object with the following shape must be provided.
interface History {
push: (location: string) => void;
}
Here is a distilled version of my factory function.
const services = {};
function servicesFactory(history: History) {
const countries = countriesFactory(history);
const local = {
...countries,
};
Object.keys(local).forEach(key => {
services[key] = local[key];
});
}
Now the file where this function is defined exports 2 things.
1)This factory function
2)the services object.
This is what the countries service looks like.
function countriesFactory(h: History): CountriesService {
const countries: CountriesService = {
getCountries() {
return request<Countries>({
method: "get",
endpoint: "/api/countries",
}, h)
}
}
return countries;
}
And finally here is what my request function looks like.
function request<T>({ method, endpoint, body }: Request, history: History): Promise<Response<T>> {
const headers = {
"token": localStorage.getItem("someToken"),
};
const result: Response<T> = {
data: null,
error: null,
};
return axios({
url: endpoint,
method,
data: body,
headers,
}).then(res => {
result.data = res.data;
return result;
}).catch(e => {
if (e.response.status === 401) {
localStorage.clear();
history.push("/login");
return result;
} else {
result.error = e.response.data;
return result;
}
});
}
As you can see the request function exepcts to have the history object passed to it which it will get from the service, and the service will get it from the services factory.
Now the cool part is that I only ever have to call this factory function and pass the history object to it once in the entire app. After that I can simply import the services object and use any method on it without having to worry about passing the history object to it.
Here is the code of where I call the services factory function.
const App = (props: RouteComponentProps) => {
servicesFactory(props.history);
return (
// my app and routes
);
}
Hope someone else who finds this question will find this useful.
I am providing my solution here as accepted answer does not address the new versions of React Router and they require reload of the page to make that solution work.
I have used the same BrowserRouter. I have created a class with static functions and a member history instance.
/*history.js/
class History{
static historyInstance = null;
static push(page) {
History.historyInstance.push(page);
}
}
/*app-router.js/
const SetHistoryInstance = () => {
History.historyInstance = useHistory();
return (null);
};
const AppRouter = () => {
return (
<BrowserRouter>
<SetHistoryInstance></SetHistoryInstance>
<div>
<Switch>
<Route path={'/'} component={Home} />
<Route path={'/data'} component={Data} exact />
</Switch>
</div>
</BrowserRouter>
)};
Now you can import history.js anywhere in your app and use it.
One simple way is to useHistory() in App.js and then use render and pass history as an attribute of the component:
function App() {
const history = useHistory();
<Router>
<Route
path={nav.multiCategoriesNoTimer}
render={() => <MultiCategoriesNoTimer history={history} />}
/>
</Router>
}
const MixMultiGameNoTimer = (props: any) => {
if (true) {
return (
<NoQuestionsHereScreen history={props.history} />
);
}
}
const NoQuestionsHereScreen = (props: any) => {
return (
<div className='no-questions-here' >
<Button
title="Go back"
onClick={() => props.history.push(nav.home)}
/>
</div>
);
};
There is a bit of drilling, but it works and that for many future versions too>
I created a solution that could solve this issue.
Access react router dom history object outside React component
I think this approach will work with both React-router v4 and v5.

Routing is not work with react-router-redux actions

When use the Link, router works as expected, though i get a warning [history] pushState is deprecated; use push instead.
Using routeActions from react-router-redux does't work, url was change (http://localhost:3002/addCity), but view still the same (Home) or show error if i go to page by url for example: localhost:3002/addCity.
git: https://github.com/kirsanv43/Weather.git
reducers:
export default combineReducers({
cities,
routing: routeReducer
});
store config: https://github.com/kirsanv43/Weather/blob/master/app/redux/config/store.js
import rootReducer from '../reducers'
export default function configureStore(initialState) {
const history = createHistory();
const middleware = syncHistory(history)
const finalCreateStore = compose(
applyMiddleware(middleware)
)(createStore)
const store = finalCreateStore(rootReducer)
middleware.listenForReplays(store);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
Router:
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="addCity" component={AddCity}/>
</Route>
</Router>
</Provider>
,
document.getElementById('root')
);
Component:
class CitiesList extends React.Component {
onAddCity = () => {
this.props.route.push('/addCity')
};
render() {
return <div className="weatherContainer">
<ul>
<li className="listItem addBtn"><a onClick={this.onAddCity}><span>ADD CITY</span></a></li>
</ul>
</div>
}
}
function mapDispatchToProps(dispatch) {
return {
route:bindActionCreators(routeActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CitiesList)
insted createHistory need use createHashHistory from 'history'
that working for me
Please try to change "react-router-redux" version to "^5.0.0-alpha.8". It solved my problem

React-Router 1.0 - 100% Client Side Routing - Page Refresh causes 404 error

I am creating a website for a client that will use strictly client side react-routing script.
Here is a sample of the router ....
import React from 'react';
import { Route } from 'react-router';
import { generateRoute } from '../utils/localized-routes';
export default (
<Route component={ require('../components/APP') }>
{ generateRoute({
paths: ['/', 'audience'],
component: require('../components/Audience')
}) }
{ generateRoute({
paths: ['speaker'],
component: require('../components/Speaker')
}) }
{ generateRoute({
paths: ['board'],
component: require('../components/Board')
}) },
{ generateRoute({
paths: ['questions'],
component: require('../components/parts/AskQuestion')
}) }
<Route path="*" component={ require('../pages/NotFound') } />
</Route>
);
With this being the code for generateRoute:
export function generateRoute({ paths, component }) {
return paths.map(function(path) {
const props = { key: path, path, component };
// Static `onEnter` is defined on
// component, we should pass it to route props
if (component.onEnter) props.onEnter = component.onEnter;
return <Route {...props} />;
});
}
Problem:
While I understand the Links will bypass server navigation and utilize transition to (client side), on page refresh, I get a "Page Cannot Be found".
If I manually put a hash tag before the browser's url input (myexample.com/#speaker), the page appears, but of course I cannot expect the user to do that.
If I must use hash tags to allow client side routing, where do I put them? I put them in the and/or the router, neither work.
Alternatively, can I achieve total client side routing w/o the ugly hash tags? If, so, how do I do it?
I'd much prefer a solution based on #3, but if all else fails I'll take a solution based on #2.
Any ideas?
Thanks in advance.
I could only find a solution using step #2 above and am stuck with hashtags.
import React from 'react';
import ReactDOM from 'react-dom';
import Router from 'react-router';
import createHistory from 'history/lib/createHashHistory'; <-- using this
// import createBrowserHistory from 'history/lib/createBrowserHistory';
const routerProps = {
routes: require('./router/routes'),
history: createHistory({ <--- added this to remove ugly querystring
queryKey: false
}),
createElement: (component, props) => {
return React.createElement(component, { ...props });
}
};
ReactDOM.render(
React.createElement(Router, { ...routerProps }),
document.getElementById('root')
);
Would still like to know how I can remove the hashtags completely with client-side routing.