skip to Main Content

So I have build a demo site with a form of submiting a new user.

The files:

App.jsx:

import React from 'react';
import './App.css';
import {
  RouterProvider,
  createBrowserRouter
} from 'react-router-dom';
//components
import Home from './pages-views/Home.jsx';
import AboutUs from './pages-views/AboutUs.jsx';
import Products from './pages-views/Products.jsx';
import Product from './pages-views/Product.jsx';
import Users from './pages-views/Users.jsx';
import UsersError from './pages-views/UsersError.jsx';
import User from './pages-views/User.jsx';
import UserError from './pages-views/UserError.jsx';
import NewUser from './pages-views/NewUser.jsx';
import ContactUs from './pages-views/ContactUs.jsx';
//layouts
import RootLayout from './components/RootLayout.jsx';
import ErrorLayout from './components/ErrorLayout.jsx';
//loaders
import { loadUsers, loadUser } from './loaders/loaders.jsx';
//actions
import { validateAndSubmitNewUser } from './actions/validateAndSubmitNewUser.jsx';

const myRouter = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    //catches every error on every child Route that NOT HAVE a errorElement property
    errorElement: <ErrorLayout />,
    children: [
      {
        path: '/',
        element: <Home />,
      },
      {
        path: '/about-us',
        element: <AboutUs />,
      },
      {
        path: '/products',
        element: <Products />,
      },
      {
        path: '/product/:id',
        element: <Product />,
      },
      {
        path: '/users',
        element: <Users />,
        loader: loadUsers,
        errorElement: <UsersError />,
      },
      {
        path: '/user/:id',
        element: <User />,
        loader: loadUser,
        //errorElement: <UserError />,
      },
      {
        action: validateAndSubmitNewUser,
        path: '/new-user',
        element: <NewUser />,
      },
      {
        path: '/contact-us',
        element: <ContactUs />,
      },
    ],
  },
]);

export default function App() {
  return (
    <>
      <RouterProvider router={myRouter} />
    </>
  );
}

MainBody.jsx:

import { Outlet, useRouteError } from 'react-router-dom';

export default function MainBody() {

  let error = useRouteError();
  
  const isError = (error != null);
  console.log('is Error? ', isError);
  console.log(error);
  
  return (
    <>
      <Outlet />
      {isError ? (
        <>
          <p>Error details:</p>
          {/*printed when I throw new Error('User not found') on loader*/}
          <p>{error.name}</p>
          <p>{error.message}</p>
          <p>{error.stack}</p>
          <p>----------</p>
        </>
      ) : (
        <p></p>
      )}
    </>
  );
}

Users.jsx:

import { Link, useLoaderData, useNavigation } from 'react-router-dom';
import UserCard from '../components/UserCard.jsx';

export default function Users() {
  
  let pageState = useNavigation().state; //return idle | submitting | loading as value
  console.log(pageState);

  const users = useLoaderData();

  return (
    pageState === 'loading' ? (
      <>
        <p>Products page</p>
        <p>Loading....</p>
      </>
    ) : (
      <>
        <p>This is Users page</p>
        <Link
          to="/new-user"
          class="btn btn-outline-primary"
          style={{ margin: '8px' }}
        >
          Add new User
        </Link>
        {users.map((user) => (
          <Link to={'/user/' + user.id} key={user.id}>
            <UserCard name={user.name} userData={user} />
          </Link>
        ))}
      </>
    )
  );
}

validateAndSubmitNewUser.js (this is the action of Route path "/new-user")

export async function validateAndSubmitNewUser({ params, request }) {
    
    let formUserData = await request.formData();
    const user = {
        name,
        username,
        email,
        street,
        streetNumber,
        city,
        zipcode,
        phone,
        website,
    };
    
    user.name = formUserData.get('name');
    user.username = formUserData.get('username');
    user.email = formUserData.get('email');
    user.street = formUserData.get('street');
    user.streetNumber = formUserData.get('street-number');
    user.city = formUserData.get('city');
    user.zipcode = formUserData.get('zipcode');
    user.phone = formUserData.get('phone');
    user.website = formUserData.get('website');

    //variable validate will be the errors obj or null: that is what validateNewUser(user) returns
    let validate = validateNewUser(user);//works fine

    if (validate === null) {
        //just a api call to https://jsonplaceholder.typicode.com/users/ to submit user
        //returns the submited data as json
        let data = await submitNewUser(user);
    }
    
    return validate;
    
}

NewUser.jsx (here is the problem)

import clsx from 'clsx';
import { Form, useActionData, useNavigate } from 'react-router-dom';

export default function NewUser() {
    const navigate = useNavigate();
    const errors = useActionData();
    console.log('display errors ', errors);

    //if there is not errors redirect-navigate to /users page
    if (errors === null) {
        console.log("navigate(/users)");
        navigate("/users");//** (I think so) THIS IS RESPONSIBLE FOR THE PROBLEM-ERROR. See error stack below
    }

    return (
        <Form method="post" action="/new-user">
            <div className="input-group mb-4">
            <span className="input-group-text" id="name">
                Name
            </span>
            <input
                type="text"
                name="name"
                className={clsx('form-control', errors?.name && 'is-invalid')}
                aria-label="name input"
                aria-describedby="nameFeedback"
            />
            <div id="usernameFeedback" className="invalid-tooltip">
                {errors?.name}
            </div>
            </div>
            <div className="input-group mb-4">
            <span className="input-group-text" id="username">
                Username
            </span>
            <input
                type="text"
                name="username"
                className={clsx('form-control', errors?.username && 'is-invalid')}
                aria-label="username input"
                aria-describedby="usernameFeedback"
            />
            <div id="usernameFeedback" className="invalid-tooltip">
                {errors?.username}
            </div>
            </div>
            <div className="input-group mb-3">
            <span className="input-group-text" id="email">
                Email
            </span>
            <input
                type="text"
                name="email"
                className={clsx('form-control', errors?.email && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="emailFeedback"
            />
            <div id="emailFeedback" className="invalid-tooltip">
                {errors?.email}
            </div>
            </div>

            <div className="input-group mb-3">
            <span className="input-group-text" id="street">
                Street
            </span>
            <input
                type="text"
                name="street"
                className={clsx('form-control', errors?.street && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="streetFeedback"
            />
            <div id="streetFeedback" className="invalid-tooltip">
                {errors?.street}
            </div>
            </div>
            <div className="input-group mb-3">
            <span className="input-group-text" id="street-number">
                Number
            </span>
            <input
                type="text"
                name="street-number"
                className={clsx('form-control', errors?.streetNumber && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="street-number-feedback"
            />
            <div id="street-number-feedback" className="invalid-tooltip">
                {errors?.streetNumber}
            </div>
            </div>
            <div className="input-group mb-3">
            <span className="input-group-text" id="city">
                City
            </span>
            <input
                type="text"
                name="city"
                className={clsx('form-control', errors?.city && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="cityFeedback"
            />
            <div id="cityFeedback" className="invalid-tooltip">
                {errors?.city}
            </div>
            </div>
            <div className="input-group mb-3">
            <span className="input-group-text" id="zipcode">
                Zipcode
            </span>
            <input
                type="text"
                name="zipcode"
                className={clsx('form-control', errors?.zipcode && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="zipcodeFeedback"
            />
            <div id="zipcodeFeedback" className="invalid-tooltip">
                {errors?.zipcode}
            </div>
            </div>
            <div className="input-group mb-3">
            <span className="input-group-text" id="phone">
                Phone
            </span>
            <input
                type="text"
                name="phone"
                className={clsx('form-control', errors?.phone && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="phoneFeedback"
            />
            <div id="phoneFeedback" className="invalid-tooltip">
                {errors?.phone}
            </div>
            </div>
            <div className="input-group mb-3">
            <span className="input-group-text" id="website">
                Website
            </span>
            <input
                type="text"
                name="website"
                className={clsx('form-control', errors?.website && 'is-invalid')}
                aria-label="Sizing example input"
                aria-describedby="websiteFeeback"
            />
            <div id="websiteFeeback" className="invalid-tooltip">
                {errors?.website}
            </div>
            </div>
            <button type="submit" className="btn btn-outline-primary" name="submit">
            Submit
            </button>
        </Form>
    );
}

What I want to achieve is after a success validation and submition to be redirected to path "/users".
Now what happen is that the browser memory/processing looks to overflow, because of a rendering loop(I think so)

/*
Warning: Cannot update a component(`RouterProvider`) while rendering a different component(`NewUser`).
To locate the bad setState() call inside`NewUser`,
follow the stack trace as described in https://reactjs.org/link/setstate-in-render
NewUser@http://localhost:5173/src/pages-views/NewUser.jsx?t=1707325029056:22:20
RenderedRoute@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:3550:7
Outlet@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:3918:3
MainBody@http://localhost:5173/src/components/MainBody.jsx:21:15
RootLayout
RenderedRoute@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:3550:7
RenderErrorBoundary@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:3507:5
DataRoutes@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:4660:7
Router@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:3932:7
RouterProvider@http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=6a7590ff:4477:7
App react-dom.development.js:86:29

​*/

2

Answers


  1. because you are trying to update the state by redirect while the prosses of rendering NewUser this create an infinite loop

    usuing useEffect here will help you perform update after the component render

     export default function NewUser() {
            const navigate = useNavigate()
            const errors = useActionData()
            console.log('display errors ', errors);
        
            useEffect(() => {
                // Check if there are no errors, then navigate
                if (errors === null) {
                    console.log("navigate(/users)")
                    navigate("/users")
                }
            }, [errors, navigate])
        
            return (
                //the rest of you code 
            );
        }
    
    Login or Signup to reply.
  2. In the NewUser component you are attempting to navigate as an unintentional side-effect from directly in the function component body. This is the cause of the "Cannot update a component(RouterProvider) while rendering a different component(NewUser)." warning.

    To resolve place the side-effect logic into a useEffect hook call with appropriate dependencies such that it is now an intentional side-effect.

    Example:

    export default function NewUser() {
      const navigate = useNavigate();
      const errors = useActionData();
      console.log('display errors ', errors);
    
      useEffect(() => {
        // if there is not errors redirect-navigate to /users page
        if (errors === null) {
          console.log("navigate(/users)");
          navigate("/users", { replace: true });
        }
      }, [errors, navigate]);
    
      ...
    

    You can also return/throw a redirect directly from route loaders and actions. If using this then remove the useNavigate hook call and redirect logic from the NewUser component since it’s all handled in the action now.

    Example:

    import { redirect } from 'react-router-dom';
    
    export async function validateAndSubmitNewUser({ params, request }) {
      const formUserData = await request.formData();
      const user = {
        name: formUserData.get('name'),
        username: formUserData.get('username'),
        email: formUserData.get('email'),
        street: formUserData.get('street'),
        streetNumber: formUserData.get('street-number'),
        city: formUserData.get('city'),
        zipcode: formUserData.get('zipcode'),
        phone: formUserData.get('phone'),
        website: formUserData.get('website'),
      };
    
      const validate = validateNewUser(user);
    
      if (validate === null) {
        // returns the submitted data as json
        const data = await submitNewUser(user);
        return redirect("/users", data);
      }
        
      return validate;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search