Push history at actions react-router v4 doesn't navigate - reactjs

Need help with redirect at action.js
import axios from "axios";
import createHistory from 'history/createBrowserHistory';
import {Notification} from "modules/shared/components";
import * as types from "./actionTypes";
import {API_URL} from "utils/constants";
const history = createHistory();
export function signUp({name, email, password}) {
return (dispatch) => {
dispatch({type: types.AUTH_REQUEST});
axios.post(`${API_URL}/auth/register`, {name, email, password})
.then(response => {
history.push('/');
})
.catch(error => {
const {title, message} = error;
dispatch(Notification('error', title, message));
dispatch({type: types.AUTH_ERROR, payload: error});
})
}
}
At past I can use browserHistory.push('/') and it will redirect me to /. After using React Router v4 browserHistory func is gone.And I change it using createHistory. Yes it work but it only change my url never redirect me to the url.
Any solution?

In react router v3, browserHistory was a singleton and you can use it anywhere to navigate to a specific route. But in v4, this won't work as <BrowserRouter> creates its own history instance. Therefore, you should always change your route inside a component where you have access to history instance of the router.
Here is the approach which I use in this kind of scenarios. Instead of trying to navigate inside your action creator, you can dispatch AUTH_SUCCESS action in axios success callback with the response as your action payload. Then make your reducer to change the state based on this action. As an example let's say your reducer change the user attribute in your state as follows.
case types.AUTH_SUCCESS:
return Object.assign({}, state, {
user: action.user,
});
Also, your SignUp component (or whatever the component which you call signUp method) should have been connected to user attribute in the state as a prop. If you are using redux and react-redux it might be something looks like this.
function mapStateToProps(state) {
return {
user: state.user
};
}
export default connect(mapStateToProps)(SignUp);
Now when the user change as result of signUp method, SignUp component will receive the user as a new prop. So you can use the component's componentWillReceiveProps method to change your route if the user prop is defined.
componentWillReceiveProps(newProps){
if(newProps.user){
this.props.history.push('/')
}
}
To get the router's history instance as a prop, either SignUp component should have been rendered with Route or wrapped with withRouter
Or as an alternative you can use new <Redirect/> component in your render method as follows.
render(){
return this.props.user ? (<Redirect to="/" />) : (...your current JSX code for SignUp component);
}

Related

How to pass state to React JS High Order Component

I am using OIDC redux connector for user state. I have a few components that require authentication. I would like to use something like export default connect(mapStateToProps, mapDispatchToProps)(withAuth(Component)); and request data from state inside my authentication service.
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
export const withAuth = (Component) => {
return props => {
return <Component {...props} />
}
}
Is it possible to get state in the render function? So I can check the user beinig logged in and redirect to the sign-in page if there is no user signed in?
BTW: How would I redirect? I have tried using redirect-router-dom and <Redirect /> But then it complains about set state being changed too often ... But that might be my mistake. I get this error when I render a Redirect: Error: Maximum update depth exceeded.
If I understand correctly you want to "decorate" your component with additional logic to handle an authentication redirect?
I suggest using a "decorator" pattern here e.g.:
export const withAuth = (Component) => {
const mapStateToProps = (state, ownProps) => ({
authenticated: state.authenticated // Or what you need to do to determine this
});
return connect(mapStateToProps)(class extends React.Component {
render() {
const { authenticated, ...componentProps } = props;
if (authenticated) {
return <Component {...componentProps }>;
}
return <Redirect to="/login" />;
}
});
}
Then when you need this you can do things like:
export default withAuth(connect(yourOwnMapStateToProps)(YourComponent))
Just figured it out, I changed the store so instead of returning a function, it returns the object. So I can load in all js files. It might not be the best solution. If there is a better way to get the store in code, I would love to hear about how to do that. The configurestore function is what I found in quite a lot of examples.
import { store } from '../configureStore';
Using store.getState() I can get the current state.
The redirect issue I am having is similar to: How to use react-transition-group with react-router-dom

React v4 and Redux: Update URL after API call to show new route?

In my React application, I am trying to direct the user to "/" after a successful login. If possible, I need the page to not refresh after a successful login. Here is the code I have for when the API needs to be called (resp.data is sent when the user is authenticated):
handleFormSubmit = event => {
event.preventDefault();
API.signin({
username: this.state.username,
password: this.state.password
}).then(resp => {
if (resp.data) {
// console.log(resp);
// window.location.replace(resp.data);
window.history.pushState({}, null, resp.data);
} else {
console.log("Authentication was unsuccessful, or passport did not send back information.")
}
}).catch(function (error) {
console.log(error);
});
}
Using window.history.pushState({}, null, resp.data); works for changing the URL. However, the components don't update (I have BrowserRouter routes specified for /login, /signup, and /). Additionally, window.location.replace(resp.data); refreshes the page. Is there a way to do this through react?
You can use the withRouter HoC provided by the react-router package to inject some properties into your components. You're interested in the history object from react-router, not the history object on window.
import {withRouter} from 'react-router'
class MyComponent extends React.PureComponent {
handleFormSubmit = event => {
...
API.signin({
...
}).then(resp => {
if(resp.data) {
const {history} = this.props
history.push('/', {
data
})
}
})
}
}
export default withRouter(MyComponent)
Using withRouter will make {match, location, history} objects available in your component. With the above pattern your user will be redirected to your '/' route provided you have a <Route path='/'/> component in your <Router/> tree. The data will be available on location.state.data. (You'll need to wrap your home component with withRouter as well to have access to the location object.

BrowserRouter - how to access history? Change location in component or in action?

I use BrowserRouter in my React/Redux app, so I don't create history object nor pass it to BrowserRouter, as it handles its own history. At the moment, I mocked login functionality, and this is function in Login component (which calls login thunk in authActions):
handleSubmit = (e) => {
e.preventDefault();
this.props.authActions.login({
email: this.state.email,
password: this.state.password
});
};
AuthActions are mapped to component props:
function mapDispatchToProps(dispatch) {
return {
authActions: bindActionCreators(authActions, dispatch)
}
}
And this is login thunk in authActions:
export function login(userData) {
return function (dispatch) {
if(userData.email === 'test#test.com' && userData.password === 'pw'){
sessionStorage.setLoggedUser(JSON.stringify(userData));
dispatch(loginSuccess(userData));
this.context.router.history.push('/');
} else {
dispatch(loginFail());
}
}
}
I get error that context is undefined, but after successful Login, app navigates to "/" route. I passed context as second parameter to constructor and super methods in Login component.
class Login extends Component {
constructor(props, context) {
super(props, context);
Now, my question is. What is the best place to do location changes? Component itself, or in actions (thunks)? I mention thunks for purpose, as I will change code and have some async calls to backend. How to access history object when using `BrowserRouter, in both component and actions bound to it?
my suggestion is: change location in componentDidMount lifecycle hook.
you have there some props to compare, make some actions etc...
https://reactjs.org/docs/react-component.html#componentdidmount.
even better when you create HOC for managing location changes
UPDATE
first, NO change location in render func, this will generate error.
there any many many soloutins, based on your app structure...
another solution is use 'componentWillReceiveProps'
componentWillReceiveProps(nextProps, nextState){
if(nextProps.isLoggedIn===true&&this.props.isLoggedIn===false){
nextProps.history.push('path_for_logged_in')
}
}
for access history in props, you need wrap your component with withRouter HOC
import { withRouter } from 'react-router'
withRouter(YourComponent)

How to redirect in axios?

Can I redirect in .then instead of console.log?
axios
.post('/api/users/login', user)
.then(res => console.log(res.data))
.catch(err => this.setState({ errors: err.response.data }))
Is it possible and if it is how can I do it?
I would suggest that you should not perform side operations inside the actions (in this case axios call). This is because you would eventually add these calls inside redux thunk/saga or some other middleware.
The pattern which I follow is you return the promise and add the redirection logic inside your components(or containers). Following are the benefits of it:-
It makes your code cleaner and avoids side affects inside your API calls.
It makes easier to test your API calls using mock data.
Showing alert in case the API fails without passing callback as function parameter.
Now, that being said you can use the above logic in following ways:-
// Component code
import { browserHistory } from 'react-router';
class TestContainer extends React.Component {
auth = () => {
login()
.then(() => {
browserHistory.push("/addUser");
})
.catch(() => {
// Show alert to user;
})
}
render () {
return (
<div>
<button onClick={this.auth}>Auth</button>
</div>
);
}
}
// Action code
const login = () => {
return axios
.post('/api/users/login', user);
}
U can use: location.window.href
Usually I use react-router to redirect
history.js
import createHistory from 'history/createBrowserHistory';
export default createHistory()
Root.jsx
import { Router, Route } from 'react-router-dom'
import history from './history'
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
another_file.js
import history from './history'
history.push('/test') // this should change the url and re-render Test component
Ref: https://github.com/ReactTraining/react-router/issues/3498
Your question is not about reactjs or axios, its just pure javascript.
Use location.href

URL Redirect In Redux Action With Server-Side React & React Router v4

I have been trying to figure out for a long time the best way to handle a redirect for a server-side rendered react app, using react-router v4 and redux.
My App fetches data from an API - sometimes the API responds in a way that makes me need to redirect the user to another URL automatically.
If the API responds in a way that causes me to need to redirect, I store the path that the user should be directed to in the redux store. (My API returns an error object with a "redirect" variable I can lookup in my routes file to insert into the store as the redirect path).
Importantly, this is just storing the path in the redux store.
case (typeof error["redirect"] !== "undefined" && error["redirect"] !== null): {
dispatch({
type: RENDER_REDIRECT,
payload: routes[error["redirect"]]
});
break;
}
I have a component called "RenderRedirect", this component is always rendered in the main app, but takes special action if this.props shows the redirect as "null" and nextProps redirect as !null.
This means a redirect has been triggered.
It uses history.push to change the URL, and then clears the redirect out of the store using another action.
This works particularly well because I don't have to worry about server-side rendering erring, because this situation can only happen client-side.
Anytime I need to trigger a redirect I can easily dispatch the above action with a path as a payload.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from "react-router-dom";
import { clearRedirect } from '../../../actions';
class RenderRedirect extends Component {
componentWillReceiveProps(nextProps) {
// Detect redirect, perform redirect, clear redirect
const { redirect, history, clearRedirectAction } = this.props;
// Detect redirect
if(redirect === null && nextProps.redirect !== null) {
history.push(nextProps.redirect);
clearRedirectAction();
}
}
render() {
const { redirect } = this.props;
if (redirect !== null) {
return (
<div>
<p className={"profile-instructions"}>Redirecting...</p>
</div>
)
} else {
return null;
}
}
}
const mapStateToProps = (state) => ({
redirect: state.redirect
})
const mapDispatchToProps = (dispatch) => ({
clearRedirectAction: () => dispatch(clearRedirect())
})
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(RenderRedirect));

Resources