Search code examples
typescripturlreact-hooksparametersreact-router

React useParams returns 'string | undefined'


I've seen this question asked elsewhere but none of the answers helped me. I'm using a parameter in the url and I'm wanting to access it with the useParam hook to then query my backend.

The destructured param is typed as 'string | undefined' - Is there a way to definitely type the destructured query param? I'd like to avoid having undefined values. I'm aware that I can just handle the undefined value in the functions that are expecting the param but I would just rather remove the issue at source, if possible.

Other answers suggested matching the destructured param value to the param name stipulated in the route component, but mine already match. Is it perhaps something to do with the nested routes under '/journalEntries', i.e. because other routes -without params- are possible on the 'journalEntries' route? I tried to figure this out by deleting the other child routes but it didn't help.

Routing:

const Routing = () => {
  return (
    <MainViewContainer>
      <NavBar />
      <>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/thoughtRecords" element={<ThoughtRecordPage />} />

          <Route path="/journalEntries">
            <Route
              index
              element={
                <JournalPage>
                  <JournalContainer />
                </JournalPage>
              }
            />

=========================================================
     ROUTE I WANT TO DESTRUCTURE THE 'id' PARAMETER FROM:

            <Route
              path=":id"
              element={
                <JournalPage>
                  <JournalEntry />
                </JournalPage>
              }
            />
=========================================================

            <Route
              path="create"
              element={
                <JournalPage>
                  <JournalEntryForm />
                </JournalPage>
              }
            />
          </Route>
        </Routes>
      </>
    </MainViewContainer>
  );
};
export default Routing;

Component containing link to single 'Journal Entry':

const JournalContainer = () => {
  const { journalEntries } = useDataContext();

  return (
    <>
      <div className="flex flex-col items-center">
        {journalEntries.map((journalEntry) => {
          return (
            <div
              className="w-1/3 border flex justify-between p-1 m-1"
              key={journalEntry._id}
            >


======================================================================
              LINK TO JOURNAL ENTRY

              <Link
                to={`/journalEntries/${journalEntry._id}`}
                className="w-3/4 flex justify-between"
              >
======================================================================


                <span className="">{journalEntry.title}</span>
                <span className="text-xs">
                  {new Date(journalEntry.createdAt).toDateString()}
                </span>
              </Link>
              <button
                onClick={async () => await deleteJournalEntry(journalEntry._id)}
                className="mr-0"
              >
                Delete
              </button>
            </div>
          );
        })}
        <Link
          className="w-1/3 border text-center p-1 m-1"
          to={'/journalEntries/create'}
        >
          Create a Journal Entry
        </Link>
      </div>
    </>
  );
};

export default JournalContainer;

'Journal Entry' component which relies on the destructured parameter:

  const { id } = useParams(); // is typed as 'string | undefined'

  const methods = useForm({
    defaultValues: async () => await getJournalEntryById(id), // this function expects a string
  });
  const navigate = useNavigate();

  const [saveCopy, setSaveCopy] = useState(false);
  const [readOnly, setReadOnly] = useState(true);

  const submissionHandler: SubmitHandler<CreateJournalEntryType> = async (
    data
  ) => {
    await updateJournalEntryById(id, data); // this function expects a string
    if (saveCopy) {
      writeJournalEntryToFile(data);
    }
    navigate('/journalEntries');
  };

  return (
    <FormProvider {...methods}>
      <form
        className="flex flex-col justify-evenly items-center border p-10 h-1/2"
        onSubmit={methods.handleSubmit(submissionHandler)}
      >
        <button className="mr-auto" onClick={() => navigate(-1)}>
          Go Back
        </button>
        <JournalEntryFormFields readOnly={readOnly} setReadOnly={setReadOnly} />
        <div className="flex w-full justify-evenly items-center">
          <button type="submit" className="border p-2 m-1">
            Submit Journal Entry
          </button>
          <div className="flex items-center border p-1">
            <input
              type="checkbox"
              className="m-1"
              onChange={() => setSaveCopy(!saveCopy)}
            />
            <label className="m-1" htmlFor="save-journal-entry-checkbox">
              Save a copy to file
            </label>
          </div>
        </div>
      </form>
    </FormProvider>
  );
};

export default JournalEntry;
  • made sure the param names matched
  • tried rendering a single route

Solution

  • If you know for a fact that the id route path param will always be a defined string then you can simply type/cast the param value. Something as simple as the follow should/could work.

    const { id } = useParams() as { id: string };
    

    If there's not this guarantee that id will always be a route path parameter then you can provide a fallback value.

    const { id = "" } = useParams();
    

    or check that is is a valid value prior to passing it along.

    const submissionHandler: SubmitHandler<CreateJournalEntryType> = async (
      data
    ) => {
      await updateJournalEntryById(id ?? "", data);
      if (saveCopy) {
        writeJournalEntryToFile(data);
      }
      navigate('/journalEntries');
    };
    

    or

    const submissionHandler: SubmitHandler<CreateJournalEntryType> = async (
      data
    ) => {
      if (id) {
        await updateJournalEntryById(id, data);
        if (saveCopy) {
          writeJournalEntryToFile(data);
        }
        navigate('/journalEntries');
      }
    };