Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 622
Tom Richard Macfie Avatar asked Aug 27 '26 15:08

Tom Richard Macfie


2 Answers

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');
  }
};
like image 180
Drew Reese Avatar answered Aug 29 '26 04:08

Drew Reese


In React Router it is currently not possible for the TypeScript compiler to infer a parameter value is not undefined.

Instead what you can do is use invariant checks. This method is widely popular and it is used internally in React Router to assert a given property of the program state that is always true.

An invariant check works as follows:

const {id} = useParams();
invariant(id); // From this point on "id" is correctly treated as non-undefined

An implementation of "invariant" might look like this:

function invariant(value: unknown): asserts value {
  if (value) {
    return;
  }
  
  throw new Error("Invariant violation");
}

Alternatively you can download one of the many implementations of invariant from https://npmjs.com Do note: The mirror of Facebook's invariant contains error handling logic designed specifically for Facebook's purposes.

like image 41
Benjamin Avatar answered Aug 29 '26 05:08

Benjamin