I have setup routes that are meant to be authenticated to redirect user to login page if unauthenticated. I have also setup redux-persist to auto dehydrate my auth state so user remains login on refresh. The problem is this rehydration is too late and user is already redirected to login page
The 1st location change is to an authenticated route, the 2nd is to login. Notice rehydrate comes after these. Ideally it should be right after ##INIT at least?
The persistStore function which is used to make your store persistent have a third param callback which is invoked after store rehydration is done. You have to start your app with some sort of preloader, which waits for rehydration to happen and renders your full application only after it finishes.
redux-persist docs even have a recipe for this scenario. In your case all the react-router stuff should be rendered inside initial loader as well:
export default class Preloader extends Component {
constructor() {
super()
this.state = { rehydrated: false }
}
componentWillMount(){
persistStore(this.props.store, {}, () => {
this.setState({ rehydrated: true });
})
}
render() {
if(!this.state.rehydrated){
return <Loader />;
}
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
);
}
}
const store = ...; // creating the store but not calling persistStore yet
ReactDOM.render(<Preloader store={store} />, ... );
When user refresh the page, The first LOCATION_CHANGE will fire because you sync history with store. However, it only syncs the history and does not redirect user to anywhere yet.
The only thing matters is the second LOCATION_CHANGE, which should occur after persistStore(). Good news is persistStore() has a call back.
const store = ...
// persist store
persistStore(store, options, () => {
const states = store.getState();
// if authenticated, do nothing,
// if not, redirect to login page
if (!states.auth.isAuthenticated) {
// redirect to login page
}
});
render(...);
Hope this can help to solve your problem.
Related
I am using redux saga in my app, I have a login form and when I get authenticated I use the Redirect Component to move to the app, I do this of course after changing my connectedUser state, that is like that :
const initialState = {
loading: false,
user: {},
status: ""
};
When authenticating my state change to status:"AUTH" and user: { // user data }, And this is how I redirect to the Application component and this is how my Authentication component rendering method looks like :
render() {
if (this.props.connectedUser.status === "AUTH") {
return <Redirect to="/" />;
}
return MyLoginForm
}
My routes are defined in the whole app container :
function App(props) {
return (
<ThemeProvider theme={theme}>
<I18nProvider locale={props.language.language}>
<BrowserRouter>
<Switch>
<Route exact path="/" component={Application}></Route>
<Route path="/authenticate" component={Authentification}></Route>
</Switch>
</BrowserRouter>
</I18nProvider>
</ThemeProvider>
);
}
const msp = state => ({ language: state.language });
export default connect(msp, null)(App);
This is working fine, the disconnect button is doing fine too, but the problem occurs when I am redirected to my app ( meaning this route "/" ).
I want that when I refresh the page, I don't get redirected to the authentication page, Why I am going there ? it is obvious that my state is the initial state again, the status is "" again.
I am not asking for code but an idea, how to implement this.
I was trying to put that status in my localStorage but I don't feel like it is a good practise, also I found a problem whith this because If I change the localStorage then I will have to re render the component so the condition can be verified.
I want that when I refresh the page, I don't get redirected to the authentification page, Why I am going there ? it is obvious that my state is the initial state again, the satus is "" again.
Sure! Refresh page will initial state again.
First of all, you need store current authentification after signed in. I suggest you use the redux-sessionstorage
Next, in Authentication component you should dispatch an action to get current authentification and update status in your state.
I'm new at React+Redux+Router and I'm scratching my head over how to have the user forwarded to the PrivateRoute requested after successful login.
react-cognito fires COGNITO_LOGIN action and sets cognito.state = "LOGGED_IN" in the store at successful login. I know react-router stores the requested route in location.state.from.pathname, and I know that to redirect the BrowserRouter I can use history.replace().
I just don't know where my code goes so that it executes after the react-cognito onSubmit login function asynchronously completes, given there's no callback provided by this package.
In my login form component I tried
componentWillReceiveProps(nextProps) {
if (nextProps.cognito.state === 'LOGGED_IN') {
// logged in, let's show redirect if any, or show home
try {
const { from } = this.props.location.state || {
from: { pathname: "/" }
};
nextProps.history.replace(from);
} catch (err) {
nextProps.history.replace("/");
}
}
}
...but it executed too early. I don't think something like this belongs in a reducer since these seem intended only to directly manipulate the store, but I could be way off there.
Is there some functionality within the react-router-redux package that may provide something helpful here?
Am I stuck without a callback? Is there no way to watch for the change of cognito.state?
Suggestions welcome.
TIA
The standard way of doing it with react-router-v4 is to render a <Redirect /> which will take care of redirection.
In your case:
render() {
if (this.props.cognito.state === 'LOGGED_IN') {
return (<Redirect to="/" />)
}
//...
}
This replaces the current location by default.
See this example for further information.
You could use push method in your action to dispatch an action which will redirect your app to desired location/route.
action.js
import {push} from 'react-router-redux';
/*** write your API here and after success redirect your route ***/
.then(() => {
dispatch(push(args.redirectRoute))
})
Make sure you configured your React Router with Redux.
I'm building an application with React. If someone wants to search the following happens:
State is updated: this.props.Address.selectedAddress
Click on a search button the search event is trigger AND my URL is changed:
this.props.handleSearch(address);
let city = format_city(this.props.Address.selectedAddress);
browserHistory.push('/searchfor/'+city);
That works fine. Now I would like that a search event is also triggered if someone puts a new url in the application. What I did so far:
class ResultsList extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.unlisten = browserHistory.listen( (location) => {
console.log('route changes');
if (this.selectedAdressNOTMatchUrl()){
this.handelAdressSearch()
}
});
}
componentWillUnmount() {
this.unlisten();
}
Now the problem that the click event changes the url and the result list component thinks it have to handle this event: triggering search and updateting selectedAddress.
I really think it is a bad design how I have done it. Of course I could add a timestamp or something like this to figure out if browserHistory.push('/searchfor/'+city); happend right before the url change event is fired, but that is a bit ugly.
Every idea is appreciated!
Here is my setup:
export const store = createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(
applyMiddleware(
thunkMiddleware,
promiseMiddleware()),
autoRehydrate()
)
);
const persistor = persistStore(store, {storage: localForage});
render(
<Provider store={store} persistor={persistor}>
<Router history={browserHistory}>
<Route path="/" component={Home}/>
<Route path="/searchfor/:city" component={Suche}/>
</Router>
</Provider>,
PS: One more remark
I tried react-router-redux. It saves the url state. BUT it doese not solve the issue. If for example I enter a new url. Then the Event LOCATION_CHANGE is fired. BUT short after that, my persist/REHYDRATE event is fired. So that the url changes back. That makes the behavior even worse...
PPS: To make it more clear. Everything works fine. I want to add one more feature. If the users enters a new url himself, then the application should handle this the same way if he clicked on the search button. BUT it seems a bid of hard, since in that case in my application the rehydrate event is fired...
Not sure how redux-persist ("rehydrating") works so maybe I'm missing something crucial.. But I'm thinking it could be possible to solve your issue through connecting your ResultsList component to Redux so that it passes the react-router url-parameter to it, something like this
// ownProps is be the props passed down from the Route, including path params
const mapStateToProps = (state, ownProps) => {
return {
searchTerm: ownProps.city, // the url param
results: state.searchResults // after a manual url entry this will be undefined
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSearch: searchTerm => dispatch(searchAction(searchTerm))
};
};
(That kind of code should be possible according to these docs here)
Then, inside the ResultsList, you could initiate the search.
componentDidMount() {
const {searchTerm, handleSearch} this.props;
handleSearch(searchTerm);
}
}
render() {
const {results} = this.props;
return !results ?
<p>Loading..</p> :
<div>
{ results.map(renderResultItem) }
</div>
}
That way the component would not have to care if the URL-param came from a manually entered url, or from a programmatically pushed URL from the search page.
I'm new to ReactJS, but I have a simple use case: I have a login form that sets the user's state (full name, etc.) and then I use the React Router to browserHistory.push('/') back to the home page. I can confirm that if I stay on the login page that my states actually get saved, however when I go back to the homepage, and into my "parent" component (App.js) and run this before the render method:
console.log(this.state) // this returns null
It always returns true. My constructor is not even doing anything with the state. If I put a log in the constructor on my App.js parent component I can verify that the page is not actually being reloaded, that the component is only mounted once (at least the constructor on App.js is only called once during the whole homepage > login > homepage lifecycle). Yet again, the state seems to be removed after changing pages.
What simple thing am I missing?
Edit: some code, trying to simplify it:
// LoginForm.js
//Relevant method
handleSubmit() {
this.login(this.state.username, this.state.password, (success) => {
if (!success)
{
this.setState({ isLoggedIn: false })
this.setState({ loginError: true })
return
}
this.setState({ isLoggedIn: true })
browserHistory.push('/') // I can verify it gets here and if at this point I console.log(this.isLoggedIn) I return true
})
}
// App.js
class App extends React.Component {
constructor(props) {
super(props);
console.log('hello')
}
render() {
const { props } = this
console.log(this.state) // returns null
return (
<div>
<AppBar style={{backgroundColor: '#455a64'}}
title="ADA Aware"
showMenuIconButton={false}>
<LoginBar/>
</AppBar>
<div>
{props.children}
</div>
</div>
)}
//Part of my routes.js
export default (
<Route path="/" component={App}>
<IndexRoute component={HomePage}/>
<Route path="/login" component={LoginForm}/>
<Route path="*" component={NotFoundPage}/>
</Route>
);
Where you call handleSubmit(), what component calls it?
If it is <LoginForm> or <LoginBar> or something like this, your this. means that component, non <App>.
To set parent's state (<App> in your case) you should pass to child a property with a handler function (for example onLogin={this.handleAppLogin.bind(this)}) so your child must call this prop (this.props.onLogin(true)) and handler in the <App> will set App's state: handleAppLogin(isLoggedIn) { this.setState({isLoggedIn}); }
But for the "global" state values such as login state, access tokens, usernames etc, you better shoud use Redux or some other Flux library.
This was a closed issue router project and discussed in many SO articles:
https://github.com/ReactTraining/react-router/issues/1094
https://github.com/reactjs/react-router-redux/issues/358
Redux router - how to replay state after refresh?
However, it is persistent real world need. I guess the pattern of how to handle this is based on Redux and/or browser storage.
I have a react app like where I have App.js which includes my Headers and Footers ..
In there I am storing my state to local storage.. After storing I can retrieve it ..
But I am having problem on loading it.. In my browser when I do localstorage['key'] it gives me the data but I am unable to load.
Where should I load it first ?
I have Root.js :
class Root extends Component {
render() {
const { store } = this.props
return (
<Provider store={store}>
<ReduxRouter />
</Provider>
)
}
}
Root.propTypes = {
store: PropTypes.object.isRequired
}
export default Root
and index.js at outer level :
const store = configureStore()
render(
<Root store={store} />,
document.getElementById('root')
)
Where should I load my localStorage ?
Need help . I need to know what is called first in react so that I can make a call to load localStorage there .. If it is not bad idea
You're using redux, so you should read the data as the result of an action.
The simplest way to make this work would be:
Under ReduxRouter there should be a connected component. See docs
In that connected component, add componentDidMount to call an action getStuffFromLocalstorage. See React lifecycle events
In your reducer, get what you want from localStorage and populate the state
It might be an antipattern to do it in your reducer, but it will at least make the redux-flow go around.
I would suggest to fetch the local storage data from the componentDidMount method inside the app.js file.
Please find the official reference here:
https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount
if you wanna update the localStorage data before the component gets unmounted you can use the method componentWillUnmount:
https://facebook.github.io/react/docs/component-specs.html#unmounting-componentwillunmount