I am trying to use history.push method in my redux react app. Its working fine but the problem is my component won't change, do you guys know why?
route.js:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { history } from '../helper/history'
export default class route extends React.Component {
render() {
return (
<Provider store={store}>
<Router history={history}>
<Switch>
<Route exact path="/login" component={Login} />
<Route path="/error" component={Error} />
</Switch>
</Router>
</Provider>
)
}
}
Redux action.js where a history is called, this is where I dispatch my actions:
export const loginRequest = () => {
return {
type: userType.LOGIN_REQUEST,
}
}
export const loginSuccess = () => {
return {
type: userType.LOGIN_SUCCESS,
}
}
export const loginFailure = () => {
return {
type: userType.LOGIN_FAILURE,
}
}
export const fetchUser = (data) => {
return (dispatch) => {
dispatch(loginRequest)
axios
.post(env.API_URL + 'login', { email: data.email, password: data.password })
.then((res) => {
dispatch(loginSuccess(res.data.user))
history.push({
pathname: '/profile',
})
})
.catch((err) => {
dispatch(loginFailure(error))
})
}
}
As you are providing history props, you should use Router. See this, Router and BrowserRouter:
import { Router, Route, Switch } from 'react-router-dom'
instead of
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
Related
I am on react 17.x and react-router-dom 5.2. After login, even though I do history.push('/), it doesn't navigate to /. But the URL is updated in the address bar correctly.
App.js:
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, Redirect } from "react-router-dom";
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import { isLoggedInServer } from "../src/utils/auth"
import history from "./utils/history"
import './App.css';
function App() {
const [isAuthed, setIsAuthed] = useState()
useEffect(() => {
if (typeof isAuthed === 'undefined') {
isLoggedInServer().then(function (flag) {
setIsAuthed(flag)
});
}
}, [])
if (typeof isAuthed === 'undefined') {
return (
<div>Please wait...</div>
)
}
if (!isAuthed) {
history.push('/login')
}
return (
<Router history={history}>
<Switch>
<Route path="/">
<Dashboard setIsAuthed={setIsAuthed} />
</Route>
<Route path="/login">
<Login setIsAuthed={setIsAuthed} />
</Route>
</Switch>
</Router>
);
}
export default App;
history.js:
import { createBrowserHistory } from "history";
export default createBrowserHistory();
auth.js
import React, { useEffect } from "react"
import api from "../utils/api"
export const login = () => {
//to be written
}
export const logout = () => {
// to be written
}
function isLoggedInServer() {
let promise = api().get("/api/is-alive")
.then(res => {
console.log(res.data)
return true
})
.catch(err => {
if (err.response) {
console.log(err.response.data.message)
} else if (err.request) {
// client never received a response, or request never left
} else {
// anything else
}
return false
})
return promise
}
export { isLoggedInServer }
Login.jsx (signIn function)
below function gets called when the 'Login' button is clicked.
const signIn = e => {
e.preventDefault()
api().get('/sanctum/csrf-cookie').then(() => {
api().post('/login', formInput).then(response => {
if (response.data.error) {
console.log(response.data.error)
} else {
login()
console.log('routing to /')
history.push('/') // <- this doesn't work
}
}).catch(err => {
if (err.response) {
setErr(err.response.data.message)
console.log(err.response.data.message)
} else if (err.request) {
// client never received a response, or request never left
} else {
// anything else
}
})
})
}
Dashboard.jsx
import React from "react"
const Dashboard = () => {
return (
<div>Dashboard</div>
)
}
export default Dashboard
Try this in app.js replace the return function with below code
return (
<Router history={history}>
<Switch>
<Route path="/login">
<Login setIsAuthed={setIsAuthed} />
</Route>
<Route path="/" exact>
<Dashboard setIsAuthed={setIsAuthed} />
</Route>
</Switch>
</Router>
);
I think the problem is due to that you navigate too soon before all the routers are rendered. Try to move push inside useEffect. or use Redirect component from React Router
useEffect(()=> {
if (!isAuthed) {
history.push('/login')
}
}, [isAuthed]);
I am using react hooks with redux in my project. In the login component from my action file when I try to redirect to another page i.e. another component. It is redirecting to the login component within a few seconds.
Here is the code:
authReducer.js
const authReducer = (state = iState, action) => {
switch (action.type) {
case "IS_LOGIN":
return {
...state,
isLogin: action.payload,
};
}
})
userAction.js
export const loginSubmit = (data, props) => {
return async (dispatch) => {
axios
.post(`${process.env.REACT_APP_API_URL}login`, data)
.then((result) => {
if (result.data.code == 200) {
dispatch({
type: "IS_LOGIN",
payload: {
data: result.data.data,
authToken: result.data.authToken,
},
});
localStorage.setItem("debateAccountToken", result.data.authToken);
localStorage.setItem("email", result.data.data.email);
localStorage.setItem("id", result.data.data.id);
toast.success("Logged in successfully");
// setInterval(() => {
props.history.push("/log");
// }, 3000);
} else {
toast.error("Email or password wrong!!");
}
})
.catch((err) => {
console.log("error .. ", err);
toast.error("Somethihng went wrong!!");
setInterval(() => {
window.location.reload();
}, 3000);
});
};
};
component file -> route of /log
import React from "react";
function LoginPage() {
return <div>hello</div>;
}
export default LoginPage;
route file
import React, { Component } from "react";
import { BrowserRouter, Route } from "react-router-dom";
import Login from "./components/UserLogin";
import Debate from "./components/debate/Debate";
import LandingPage from "./components/LandingPage";
import UserRegister from "./components/UserRegister";
import LoginPage from "./components/LoginPage";
export default class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Route exact path="/" component={LandingPage} />
<Route exact path="/register" component={UserRegister} />
<Route exact path="/debate" component={Debate} />
<Route path="/login" component={Login} />
<Route path="/log" component={LoginPage} />
</BrowserRouter>
</div>
);
}
}
From useraction it is redirecting to /log component but eventually it is returning back to login component too. Where might I be mistaken?
I want to login user automatically if refresh_token exists in localStorage but my issue here is, i am not able to change state to authenticated initially when app start. I tried with componentWillMount but i am getting old state 1st time then i get updated state. <PrivateRoute> here getting called 2-3 time, don't know where i am making mistake.
Expected Flow
autoLogin
PrivateRoute with new state
App.js
import React, { Component } from 'react'
import WrappedLoginForm from './containers/Login'
import ChatApp from './containers/Chat'
import { connect } from 'react-redux'
import { checkAuthentication } from './store/actions/auth'
import PrivateRoute from './route'
import {
BrowserRouter as Router,
Route
} from "react-router-dom";
class App extends Component {
componentWillMount() {
this.props.autoLogin()
}
render() {
return (
<Router>
<Route path='/login' component={WrappedLoginForm} />
<PrivateRoute path="/" component={ChatApp} authed={this.props.isAuthenticated} />
</Router>
)
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.isAuthenticated,
loading: state.loading
}
}
const mapDispatchToProps = dispatch => {
return {
autoLogin: () => {
dispatch(checkAuthentication())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
route.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component: Component, authed, ...rest }) => {
console.log('aaa')
return (<Route
render={props => (
authed
? <Component />
: <Redirect to="/login" />
)}
/>)
};
export default PrivateRoute;
action.js
export const checkAuthentication = () => {
return dispatch => {
dispatch(authStart())
let refresh_token = localStorage.getItem('refresh_token')
axiosInstance.post('/api/token/refresh/', {
refresh: refresh_token
}).then(res => {
if (res.status === 200) {
localStorage.setItem("access_token", res.data.access)
dispatch(loginSuccess())
}
}).catch(error => {
console.log("balle")
dispatch(loginFailed(error))
})
}
}
first thing make sure that in your store the isAuthenticated property is false by default.
For PrivateRoute use :
{this.props.isAuthenticated
? <PrivateRoute path="/" component={ChatApp} />
: null
}
instead of <PrivateRoute path="/" component={ChatApp} authed={this.props.isAuthenticated} />
I am trying to setup an test file to render a route/page on my application. I'm trying to wrap everything with Redux and Router, and this is what I have:
import React from 'react';
import { render } from 'react-testing-library';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from '../../store/reducer';
import {Link, Route, Router, Switch} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import ViewNode from '../Pages/ViewNode';
const customRender = (
ui,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
initialState,
store = createStore(reducer, initialState),
...options
} = {}
) => ({
...render(
<Provider store={store}>
<Router history={history}>{ui}</Router>
</Provider>,
options
),
history,
});
test('can render with redux and router', () => {
const { getByTestId } = customRender(
<Route path="/server/:env/:nodeName">
<ViewNode />
</Route>,
{
route: '/server/prod/some.server.name.com',
}
);
expect(getByTestId('page-content')).toBeVisible()
})
Then I get the following error:
Error: Uncaught [TypeError: Cannot read property 'params' of undefined]
The reason this is throwing the error is because it cannot find the React Router params. It's failing in the component constructor when Im initializing the state:
this.state = {
modal: false,
activeTab: '1',
imageStatus: "loading",
env: props.match.params.env, //failing here
nodeName: props.match.params.nodeName,
environments: props.environments,
}
It seems like it isn't wrapping the router properly with my implementation above.
How would I properly wrap my page component with Redux and Router so that it can get these router params?
You have placed your <ViewNode /> component inside a Route but forgot to pass on the props it receives. That is why props.match is undefined in your component.
You can do this instead:
<Route path="/server/:env/:nodeName">
{props => <ViewNode {...props} />}
</Route>
Basically, you can use one of the 3 ways to render something with a <Route>.
Here is a working example:
import React from 'react'
import {Route, Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import {render, fireEvent} from '#testing-library/react'
import {createStore} from 'redux'
import {Provider, connect} from 'react-redux'
function reducer(state = {count: 0}, action) {
switch (action.type) {
case 'INCREMENT':
return {
count: state.count + 1,
}
case 'DECREMENT':
return {
count: state.count - 1,
}
default:
return state
}
}
class Counter extends React.Component {
increment = () => {
this.props.dispatch({type: 'INCREMENT'})
}
decrement = () => {
this.props.dispatch({type: 'DECREMENT'})
}
render() {
return (
<div>
<div data-testid="env-display">{this.props.match.params.env}</div>
<div data-testid="location-display">{this.props.location.pathname}</div>
<div>
<button onClick={this.decrement}>-</button>
<span data-testid="count-value">{this.props.count}</span>
<button onClick={this.increment}>+</button>
</div>
</div>
)
}
}
const ConnectedCounter = connect(state => ({count: state.count}))(Counter)
function customRender(
ui,
{
initialState,
store = createStore(reducer, initialState),
route = '/',
history = createMemoryHistory({initialEntries: [route]}),
} = {},
) {
return {
...render(
<Provider store={store}>
<Router history={history}>{ui}</Router>
</Provider>,
),
store,
history,
}
}
test('can render with redux and router', () => {
const {getByTestId, getByText} = customRender(
<Route path="/server/:env/:nodeName">
{props => <ConnectedCounter {...props} />}
</Route>,
{
route: '/server/prod/some.server.name.com',
},
)
expect(getByTestId('env-display')).toHaveTextContent('prod')
expect(getByTestId('location-display')).toHaveTextContent(
'/server/prod/some.server.name.com',
)
fireEvent.click(getByText('+'))
expect(getByTestId('count-value')).toHaveTextContent('1')
})
This is how I tested my routes.
You use react-redux for the Provider
You create the initial state for your store
add it to your provider
now you can select elements, expect them to match your html (per example)
import { render } from '#testing-library/react';
import { Router, Switch, Route } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { Provider } from 'react-redux';
import React from 'react';
import createStore from 'redux-mock-store';
jest.mock('../../components/Form/ManagerSelect', () => jest.fn(() => null));
describe('router page', () => {
const createState = state => {
return {
//whatever u need
}
};
const Home = _ => <span>home</span>;
const Profile = _ => <span>profile</span>;
const renderComponent = state => {
const store = createStore()(state);
//this is the "history" of your app like:
// homepage -> about -> contact -> cart page ...
const initialEntries = ['/'];
return render(
<Provider store={store}>
<Router history={createMemoryHistory({ initialEntries })}>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/profile" component={Profile} />
</Switch>
</Router>
</Provider>
);
};
it('missing emergency details should redirect to profile', () => {
const rendered = renderComponent(createState());
expect(rendered.container.innerHTML).toEqual('<span>profile</span>');
});
});
I am trying to implement react router version 4. Please find the bare minimum code which I have now as below:
import React from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom';
import { Menu, MenuItem } from '#progress/kendo-layout-react-wrapper';
import { Switch } from 'react-router-dom';
export default () => (
<BrowserRouter>
<div>
<div className="col-xs-12 col-sm-6 col-md-12">
<header className="App-header">
<h1 className="App-title">TestUsers</h1>
</header>
<Menu>
<MenuItem>
<Link to="/users">Users</Link>
</MenuItem>
<MenuItem>
Shelves
</MenuItem>
<MenuItem>
Products
</MenuItem>
</Menu>
</div>
<Switch>
<Route exact path="/users" component={Users} />
<Route exact path="/users/add" component={Users} />
<Route exact path="/users/:id" component={Users} />
</Switch>
</div>
</BrowserRouter>
)
I have been able to add a user successfully. I want to redirect to user's list page from the action which adds a user. Please find the action code below:
export function addUser(objData) {
return function (dispatch) {
axios.post(
'http://localhost:4000/api/v1/users',
{
'name': objData.name,
'email': objData.email
}
)
.then((response) => {
dispatch({ 'type': ADD_USER, 'payload': true });
// TODO: programmatically redirect using react-router v4
})
.catch((error) => {
console.log(error);
});
}
}
I have been struggling to implement the same. Could anyone please point me in the right direction.
Thanks
You can Use Callback
export function addUser(objData, onSuccess, onFail) {
return function (dispatch) {
return axios.post(
'http://localhost:4000/api/v1/users',
{
'name': objData.name,
'email': objData.email
}
)
.then((response) => {
dispatch({ 'type': ADD_USER, 'payload': true });
onSuccess()
})
.catch((error) => {
//you can handle error on your component
onFail(error);
});
}
}
Then Call addUser like this on the component.
addUser(
objData,
() => this.props.history.push('/your-user-list-component'),
() => {// handle the error here.}
)
you could return Promise from your action:
export function addUser(objData) {
return function (dispatch) {
return axios.post(
'http://localhost:4000/api/v1/users',
{
'name': objData.name,
'email': objData.email
}
)
.then((response) => {
dispatch({ 'type': ADD_USER, 'payload': true });
// TODO: programmatically redirect using react-router v4
})
.catch((error) => {
console.log(error);
});
}
}
then in a component:
componentDidMount(){
this.props.addUser(objData).then(() => {
this.props.history.push('/somwhere')
})
}
use this.props.history.push('/some/path') after dispatch action
I would also advise you to check if response.status is equal to 200
You can use the history module.
Create history.js file
//history.js
import createHistory from 'history/createHashHistory'
export default createHistory()
Update your router with the new history. You need to use Router instead of BrowserRouter.
import React from 'react';
import { Router, Route, Link } from 'react-router-dom';
import { Menu, MenuItem } from '#progress/kendo-layout-react-wrapper';
import { Switch } from 'react-router-dom';
import history from './history';
export default () => (
<Router history={history}>
...
</Router>
);
And now you can navigate programmatically.
import history from './history'
export function addUser(objData) {
return function (dispatch) {
axios.post(
'http://localhost:4000/api/v1/users',
{
'name': objData.name,
'email': objData.email
}
)
.then((response) => {
dispatch({ 'type': ADD_USER, 'payload': true });
// TODO: programmatically redirect using react-router v4
history.push(path)
})
.catch((error) => {
console.log(error);
});
}
}
In react-router 4, there was introduced <Redirect /> component, which is returned in the render() method to achieve what you need. The redirect component is also imported from react-router-dom so: import { BrowserRouter, Route, Link, Redirect } from 'react-router-dom';
class MyComponent extends React.Component {
state = {
redirect: false
}
handleSubmit () {
axios.post(/**/)
.then(() => this.setState({ redirect: true }));
}
render () {
const { redirect } = this.state;
if (redirect) {
return <Redirect to='/somewhere'/>;
}
return <RenderYourForm/>;
}