React Js state error (Cannot update during an existing state) - javascript

I wanted to set state for logged in user if user not logged in then navigate is true by this.setstate() method. but problem its showing error at console and not working the process here is error bellow
0.chunk.js:219252 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
in Header (at App.js:9)
in App (at src/index.js:17)
in ErrorBoundary (at src/index.js:16)
in Router (created by BrowserRouter)
in BrowserRouter (at src/index.js:15)
Here bellow my app.js code
import React from 'react';
import './App.scss';
import Header from './components/Header/Header';
import Sidebar from './components/Sidebar/Sidebar';
import Routes from './routes/Routes';
function App() {
return (
<>
<Header />
<Sidebar />
<Routes />
</>
);
}
export default App;
Signin.js code
export default class SignIn extends Component {
constructor(props) {
super(props)
this.state = {
redirect: false,
};
}
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />;
}
}
onSignInHandler = () => {
.......
this.setState({
....
redirect: true,
});
}
At Header.js where logout button have. I wanted to give this link onclick() handler. here below are code
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false
}
}
onLogoutHandler = () => {
....
this.setState({
navigate: true,
});
};
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
return(<header></header>)
}
}
At header.js I have logout link which will trigger the onLogoutHandler and state will then will redirect to signin page. please help me for this issue

Please check if this works
onLogoutHandler = async () => {
....
await this.setState({
navigate: true,
});
};

You should have return statement at the end of your render() method from your class component
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
// you should also have return statement in this line here
// return something like
return null;
}

Related

Props are not passed with React.cloneElement in the render() method

I have two react components, a Layout class and a HomePage class:
HomePage is a component which needs to have a products prop.
HomePage.js
import React, { Component } from 'react';
export class HomePage extends Component {
render() {
if (!this.props.products) {
return (<div>Products not loaded yet</div>);
}
return (<div>Products loaded!</div>);
}
}
Layout is a component that displays children coming from routes established with react-router.
This class is in charge to pass the products prop to children using React.cloneElement
Layout.js
import React, { Component } from 'react';
import { NavMenu } from './NavMenu';
import { Footer } from './Footer';
export class Layout extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
// Make an api call when the component is mounted in order to pass
// additional props to the children
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading) {
return (<div>App loading</div>);
}
const childrenWithProps = React.Children.map(this.props.children, child => {
const props = { products: this.state.products };
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
}
return child;
});
return (
<div>
<NavMenu />
{childrenWithProps}
<Footer />
</div>
);
}
}
The routing is made in an App component:
App.js
export default class App extends Component {
render () {
return (
<Layout>
<Route exact path='/'
component={HomePage}/>
</Layout>
);
}
Hence, I am expecting to
Have a page with the App loading message while the API call hasn't been made
Have a page with the Products not loaded yet message while the prop hasn't been passed to the Layout children
Have a page with the Products loaded! message
However, the application is stuck at step two: the products prop is never received by the children components. The code compiles, there are no runtime errors, and the back-end Api is triggered and sends a valid response.
Why the product props will never be available in the render() method of the child HomePage component?
EDIT:
Following #Nikita Chayka's answer, the props should be passed at routing:
Layout.js
export class Layout extends Component {
render() {
return (
<div>
<NavMenu />
{this.props.children}
<Footer />
</div>
);
}
}
App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('/api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading)
return (<div>App loading</div>);
return (
<Layout>
<Route exact path='/'
render={(props) => (<HomePage {...props} products={this.state.products}/>)}/>
</Layout>
);
}
}
Your Layout component will pass products prop to Route component, not Home component, basically you will have
<Route products={} component={Home} path="/" exact/>
But you need to pass it down to Home, you could check for ideas here - https://ui.dev/react-router-v4-pass-props-to-components/
EDIT
You should not provide component property to Route, only render.

Component rendering too early

I am trying to create a PrivateRoute(HOC) to test if a user has been authenticated(check is 'auth' exist in redux store) before sending them to the actual route. The issue is the privateroute finishes before my auth shows up in redux store.
The console.log runs twice, the first time, auth doesnt appear in the store, but it does the second time, but by that time, its already routed the user to the login screen.... How can I give enough time for the fetch to finish? I know how to do this condition when I simply want to display something conditionally(like login/logout buttons) but this same approach does not work when trying to conditionally route someone.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route } from 'react-router-dom'
class PrivateRoute extends Component {
render() {
const { component: Component, ...rest } = this.props
console.log(this.props)
return (
<Route {...rest} render={(props) => (props.auth ? <Component {...props} /> : props.history.push('/login'))} />
)
}
}
function mapStateToProps({ auth }) {
return { auth }
}
export default connect(mapStateToProps)(PrivateRoute)
I didn't use redux here, but I think you would get the main point. Hope this will help and feel free to ask any questions!
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import Dashboard from "path/to/pages/Dashboard";
class App extends Component {
state = {
isLoggedIn: null,
};
componentDidMount () {
// to survive F5
// when page is refreshed all your in-memory stuff
// is gone
this.setState({ isLoggedIn: !!localStorage.getItem("sessionID") });
}
render () {
return (
<BrowserRouter>
<Switch>
<PrivateRoute
path="/dashboard"
component={Dashboard}
isLoggedIn={this.state.isLoggedIn}
/>
<Route path="/login" component={Login} />
{/* if no url was matched -> goto login page */}
<Redirect to="/login" />
</Switch>
</BrowserRouter>
);
}
}
class PrivateRoute extends Component {
render () {
const { component: Component, isLoggedIn, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
}
class Login extends Component {
state = {
login: "",
password: "",
sessionID: null,
};
componentDidMount () {
localStorage.removeItem("sessionID");
}
handleFormSubmit = () => {
fetch({
url: "/my-app/auth",
method: "post",
body: JSON.strigify(this.state),
})
.then(response => response.json())
.then(data => {
localStorage.setItem("sessionID", data.ID);
this.setState({ sessionID: data.ID });
})
.catch(e => {
// error handling stuff
});
};
render () {
const { sessionID } = this.state;
if (sessionID) {
return <Redirect to="/" />;
}
return <div>{/* login form with it's logic */}</div>;
}
}
When your action creator return the token, you need to store it in localStorage. and then you can createstore like below,
const store = createStore(
reducers,
{ auth: { authenticated : localStorage.getItem('token') }},
applyMiddleware(reduxThunk)
)
if user already logged in then token will be there. and initial state will set the token in store so you no need to call any action creator.
Now you need to secure your components by checking if user is logged in or not. Here's the HOC for do that,
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};

React cascade rendering

I've got react-toastify element in my App.js component implemented this way:
class App extends Component {
componentDidUpdate(prevProps) {
const { toast } = this.props
if(toast.id !== prevProps.toast.id) {
this.notify(toast)
}
}
notify = (data) => {
switch(data.type) {
case TOAST.TYPE.ERROR:
...
return toast.show()
}
}
render() {
return (
<BrowserRouter>
<div className="app">
<Switch>
<Route path={ getRoutePath('password.set') } component={ PasswordSet } />
<Route path={ getRoutePath('password.reset') } component={ PasswordReset } />
<Route path={ getRoutePath('login') } component={ LoginSection } />
<Route path={ getRoutePath('home') } component={ AuthenticatedSection } />
</Switch>
<ToastContainer
className="custom-toastify"
autoClose={ 5000 }
hideProgressBar={ true }
closeButton={ <CloseButton /> }
/>
</div>
</BrowserRouter>
)
}
}
function mapStateToProps({ toast }) {
return { toast }
}
Now consider the following scenario: I've got a UsersAdmin PureComponent inside AuthenticatedSection where you can enable/disable users. When you click on enable/disable button, the UsersAdmin component re-renders because of users redux state change and then it also re-renders, because I'm showing toast on success/error api call.
toggleUsersDisabled = (user) => () => {
const { modifyUser, showToast } = this.props
modifyUser(user.id, {
disabled: user.disabled === 0 ? 1 : 0
}).then((response) => {
showToast(`${response.value.name} has been ${response.value.disabled ? 'disabled' : 'enabled'}`)
}).catch(_noop)
}
The showToast dispatches new message to redux state for toasts. Is it possible to somehow prevent re-rending of child components when the toast is shown?
Edit:
added UsersAdmin redux connection including selector
// users selector
import { createSelector } from 'reselect'
const getUsers = state => state.users.get('data')
const getIsFulfilled = state => state.users.get('isFulfilled')
export const getFulfilledUsers = createSelector(
[getUsers, getIsFulfilled],
users => users
)
// UsersAdmin
const mapStateToProps = (state) => {
return {
users: getFulfilledUsers(state)
}
}
UsersAdmin.propTypes = {
users: PropTypes.object.isRequired,
fetchUsersList: PropTypes.func.isRequired,
modifyUser: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired,
showToast: PropTypes.func.isRequired
}
export default connect(mapStateToProps, { fetchUsersList, modifyUser, deleteUser, showToast })(UsersAdmin)
I don't really get why you added all the toasts login inside your App.js
if you look over the docs in:
https://github.com/fkhadra/react-toastify#installation
the only thing you need to do is adding <ToastContainer /> to your app and you are done, exactly like you have in your example.
Now for calling toasts you just import:
import { toast } from 'react-toastify';
in to any component you like in the system, and now you just run the toast function and you got yourself a toast.
ie:
import { Component } from 'react';
import { toast } from 'react-toastify';
class SomeComponent extends Component {
showToast() {
toast('you now see a toast');
}
render() {
return <button onClick={()=>this.showToast()}>toast it</button>;
}
}

Can only update a mounted or mounting component error. Conditional rendering

So I have a login page that directs users to a profile page in which an asynchronous request is made retrieving a user's id number initiated at the componentDidMount event. Once I get the results back, I setState on the id with the data retrieved.
import React, { Component } from 'react';
import {Navbar} from 'react-materialize';
import {Link, Redirect} from 'react-router-dom';
import helper from '../utils/helper';
import axios from 'axios';
import logo from '../logo.svg';
import '../App.css';
class Profile extends Component {
constructor(props){
super(props);
this.state = {id: null, loggedIn: true};
this.logOut = this.logOut.bind(this);
}
componentDidMount(){
axios.get('/profile').then((results) => {
if(!this._unmounted) {
this.setState({id: results.data})
}
})
}
logOut(event){
axios.post('/logout').then((results) => {
console.log(results);
})
this.setState({loggedIn: false});
}
render() {
if(!this.state.loggedIn){
return <Redirect to={{pathname: "/"}}/>
}
if(this.state.id == null){
return <Redirect to={{pathname: "/login"}} ref="MyRef" />
}
return (
<div>
<Navbar brand='WorldScoop 2.0' right>
<ul>
<li><Link to="#" onClick={this.logOut}>Logout</Link></li>
</ul>
</Navbar>
<h1>Profile Page</h1>
<h2>Welcome {this.state.id} </h2>
</div>
)
}
}
export default Profile;
I am trying to make it so that someone cannot just type the '/profile' path in the url and be taken to a profile page. To do this I tried conditional rendering based on whether an id was retrieved from proper login authentication.That is why if you notice
if(this.state.id == null){
return <Redirect to={{pathname: "/login"}} ref="MyRef" />
}
this will redirect users back to the login page if they do not supply an email and password. I have tried making sure my profile component mounts and unmounts after receiving the data, but I still keeping getting the error message:
Can only update a mounted or mounting component. I am confused when the component 'unmounts' .
If you want to check whether component is mounted or unmounted by this._isunmounted, you should make it true in componentWillUnmount.
componentWillUnmount() {
this._isunmounted = true;
}
render method is resolving so therefore rerouting you before your axios call finishes, so the solution is to not change locations before your call finishes, usually a loading indicator is used. Also i changed the lifecycle hook from didMount to willMount so the state will reflect before the render.
import React, { Component } from 'react';
import {Navbar} from 'react-materialize';
import {Link, Redirect} from 'react-router-dom';
import helper from '../utils/helper';
import axios from 'axios';
import logo from '../logo.svg';
import '../App.css';
class Profile extends Component {
constructor(props){
super(props);
this.state = {id: null, loggedIn: true, loading: false};
this.logOut = this.logOut.bind(this);
}
componentWillMount(){
this.setState({ loading: true });
axios.get('/profile')
.then((results) => {
// usually data is an object so make sure results.data returns the ID
this.setState({id: results.data, loading: false})
})
.catch(err => {
this.setState({ loading: false, id: null })
})
}
logOut(event){
axios.post('/logout').then((results) => {
console.log(results);
})
this.setState({loggedIn: false});
}
render() {
if(!this.state.loggedIn){
return <Redirect to={{pathname: "/"}}/>
}
if (this.state.loading) return <div>loading...</div>
if(this.state.id == null){
return <Redirect to={{pathname: "/login"}} ref="MyRef" />
}
return (
<div>
<Navbar brand='WorldScoop 2.0' right>
<ul>
<li><Link to="#" onClick={this.logOut}>Logout</Link></li>
</ul>
</Navbar>
<h1>Profile Page</h1>
<h2>Welcome {this.state.id} </h2>
</div>
)
}
}
export default Profile;
I have succeeded to remove the error message: 'Can only update a mounted or mounting component' by using window.location.replace().
render() {
window.location.replace(`/profile`); // This is how to redirect
return (null);
}

requireAuth with react-router v4 and redux

I have HOC for check is logged user.
import React from 'react';
import { Redirect } from 'react-router-dom';
export default function requireAuthComponent(store, Component, redirectTo = '/') {
return class RequireAuthComponent extends React.Component {
render() {
const state = store.getState();
const auth = state.auth;
if (auth.logged) {
return <Component {...this.props} />;
}
return <Redirect to={redirectTo} />;
}
};
}
And route
import PrivatContainer from './container/PrivatContainer';
import requireAuth from '../../components/requireAuth';
export default store => ({
path: '/privat',
exact: true,
component: requireAuth(store, PrivatContainer),
});
PrivatComponent
import React from 'react';
export default ({ auth: { logged }, toggleLogin }) => (
<div>
<h1>Privat Route</h1>
<h3>User is {logged.toString()}</h3>
<button onClick={() => toggleLogin()}>Logout</button>
</div>
);
When first enter on route all work fine, but when I change store with logged: false, I still stay on this component(route) because route call once and don't update on store update, how it fix or subscribe RequireAuthComponent on store update ?
Maybe it is not properly, so if you have better idea please tell me :)
export default function requireAuthComponent(store, Component, redirectTo = '/') {
return class RequireAuthComponent extends React.Component {
state = {
listener: null,
}
componentWillMount() {
const listener = store.subscribe(() => {
this.forceUpdate();
});
this.setState({ listener });
}
componentWillUnmount() {
const { listener } = this.state;
listener();
this.setState({ listener: null });
}
render() {
const state = store.getState();
const auth = state.auth;
if (auth.logged) {
return <Component {...this.props} />;
}
return <Redirect to={redirectTo} />;
}
};
}

Categories