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;
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');
}
};
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With