Search code examples
javascriptreactjsreact-router-domdestructuring

Why is it not object destructuring when calling useActionData() in react router dom?


When I call useActionData(), I don't get any object destruction. When I print to the console, I get name: 'isakGo'. The code looks like this

export async function action({request}) {
  const formData = await request.formData();
  const name = { name: formData.get("name") }
  return { name };
}

export default function FormTest() {
  const { name } = useActionData(); // error has occured here

  // other codes
}

We're creating a router with createBrowserRouter() and serving it with RouterBrowser.

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: "/FormTest",
        element: <FormTest />,
        action: formTestAction,
      }
    ]
  },
])

The following error occurs.

Cannot destructure property 'name' of '(0 , react_router_dom__WEBPACK_IMPORTED_MODULE_1__.useActionData)(...)' as it is undefined.

If you change your code to one that doesn't do object destruction, it will work cleanly.

export async function action({request}) {
  const formData = await request.formData();
  const name = { name: formData.get("name") }
  return name;
}

export default function FormTest() {
  const name = useActionData(); 

  // other codes
}

When I write something like loader() in react router dom, i.e. const { name } = useLoaderData();, it works fine. action() is not destroying the object. what am I missing?

I'm using the create-react-app and the [email protected] version.

Edit

We found additional factors that may be directly related to the error.

  1. useActionData() is called when FormTest() is loaded, so the empty name variable is empty.
  2. for some reason, it is called 4 times when I print the results to the console.

Solution

  • Documentation

    This hook provides the returned value from the previous navigation's action result, or undefined if there was no submission.

    function SomeComponent() {
      let actionData = useActionData();
      // ...
    }
    

    The most common use-case for this hook is form validation errors. If the form isn't right, you can return the errors and let the user try again:

    Code Example

    import {
      useActionData,
      Form,
      redirect,
    } from "react-router-dom";
    
    export default function SignUp() {
      const errors = useActionData();
    
      return (
        <Form method="post">
          <p>
            <input type="text" name="email" />
            {errors?.email && <span>{errors.email}</span>}
          </p>
    
          <p>
            <input type="text" name="password" />
            {errors?.password && <span>{errors.password}</span>}
          </p>
    
          <p>
            <button type="submit">Sign up</button>
          </p>
        </Form>
      );
    }
    
    export async function action({ request }) {
      const formData = await request.formData();
      const email = formData.get("email");
      const password = formData.get("password");
      const errors = {};
    
      // validate the fields
      if (typeof email !== "string" || !email.includes("@")) {
        errors.email =
          "That doesn't look like an email address";
      }
    
      if (typeof password !== "string" || password.length < 6) {
        errors.password = "Password must be > 6 characters";
      }
    
      // return data if we have errors
      if (Object.keys(errors).length) {
        return errors;
      }
    
      // otherwise create the user and redirect
      await createUser(email, password);
      return redirect("/dashboard");
    }