- What Client Side Routing is & Why Use it?
- Using React Router
- Data Fetching & Submission
- create a new
README.md
file - run
cd backend && npm install && npm start
- run
cd frontend && npm install && npm start
- add a new
.gitignore
file & putnode_modules
inside
-
Add five new (dummy) page components (content can be simple
<h1>
elements)- HomePage
- EventsPage
- EventDetailPage
- NewEventPage
- EditEventPage
- run
cd frontend/ && npm install react-router-dom
- add a new
pages
folder & add inside of it the 5 dummy pages
-
Add routing & route definitions for these five pages
- / => HomePage
- /events => EventsPage
- /events/
<some-id>
=> EventDetailPage - /events/new => NewEventPage
- /events/
<some-id>
/edit => EditEventPage
- in
App.js
, useRouterProvider
for the route definitions - then, use
createBrowserRouter()
for rendering the routes
-
Add a root layout that adds the
<MainNavigation>
component above all page components- add a new
Root.js
file in thepages
folder - inside of it, render the
<MainNavigation>
&<Outlet>
components - in
App.js
, define the<RootLayout>
- convert the paths into relative paths to the parent root
- turn the
HomePage
path into an index route
- add a new
-
Add properly working links to the MainNavigation
- in
MainNavigation.js
, replace<a>
by<Link>
- set the
to
attribute to absolute paths
- in
-
Ensure that the links in MainNavigation receive an "active" class when active
- in
MainNavigation.js
, use the special<NavLink>
component instead of the regular<Link>
component - add the
className
prop & set to it a function with theisActive
object as a parameter - set the
end
prop to the Home Navlink
- in
-
Output a list of dummy events to the EventsPage
- Every list item should include a link to the respective EventDetailPage
- in
Events.js
, add someDUMMY_EVENTS
array - render the list of events dynamically
-
Output the ID of the selected event on the EventDetailPage
- in
EventDetail.js
, importuseParams
fromreact-router-dom
& store the returned value in aparams
constant - use the
params
constant to output the event id
- in
-
BONUS: Add another (nested) layout route that adds the
<EventNavigation>
component above all /events... page components- in
Add.js
, add a new route with apath
set toevents
- add a new
EventsRoot.js
file inside of thepages
folder
- in
- replace the content of the
Events.js
file with the provided one - in
App.js
, add the extraloader
property to your route definitions so that the fetched data load before the component
- in
Events.js
, get access to the data returned by theloader
function with help ofuseLoaderData
- store the returned value of this
userLoaderData
function in aevents
constant - pass this
events
constant as a value to theevents
prop of the<EventList>
component
- use the
useLoaderData
hook inEventsList.js
instead ofEvents.js
- don't use
useLoaderData
in higher level from where you defined theloader
function - put back the
useLoaderData
usage inEvents.js
- put the
loader
function code into your component file where you need it, so inEvents.js
- in
Events.js
, export a function that you could nameloader()
& put yourloader
code into that function - in
App.js
, import that{loader}
function & give it an alias likeeventsLoader
- use that
eventsLoader
pointer a that function as a value for thatloader
property
- in
backend\routes\events.js
, add a timer inside the code responsible to send the events data to the frontend - quit the server & restart it
- by default, React Router will wait for the data to be fetched (so for the loader to be finished) before it then renders the page with the fetched data
- therefore, you don't need to render a loading state on the
EventsPage
component
- give the user some feedback that something is going when he clicks on the
Events
navigation link- in
Root.js
, use theuseNavigation
hook - use the
state
property of thisnavigation
object returned from theuseNavigation
hook to show a loading text dynamically
- in
- remove this loading text & the
useNavigation
- remove the timer in
backend\routes\events.js
- in
Events.js
, create a new response object by instantiating the built-inResponse
constructor function in the browser - whenever you return such a response in your loaders, the React Router package will automatically extract the data from your response when using
useLoaderData
- so you can take that
response
object you get fromfetch()
and return that in yourloader()
- you don't need to manually extract the data from the response
resData
, instead you can return yourresponse
straight away - and
useLoaderData
will then automatically give us the data that os part of theresponse
- now, store the returned value of
useLoaderData
in adata
constant & extract yourevents
from thatdata
object
- in
Events.js
, if the response is not sent, return an object withisError
&message
keys - in the
EventsPage
component, check if theisError
is truthy & renderdata.message
in paragraph - as an alternative, you can
throw
an error & construct a new error object with the built-innew Error
constructor or any kind of object - add an
Error.js
page - in
App.js
, add theerrorElement
property to the root route & render the<ErrorPage>
component
- add a new
PageContent.js
file inside of thecomponents
folder - render the
<PageContent>
component inError.js
- differenciate between the different types of errors by throwing a
new Response
instead of an object inEvents.js
& set thestatus
to500
- get hold of the data that is being thrown as an error inside of the component that is being rendered as an
errorElement
with help of the special{useRouteError}
hook imported fromreact-router-dom
- use the
status
field of this{useRouteError}
hook that is provided when you throw anew Response
which in our case reflects the500
status of the response we threw
- in
Events.js
, instead of throwing anew Response
,throw
thejson()
helper utility imported fromreact-router-dom
- this function creates a response object that includes data in the JSON format
- now you can remove the
JSON.parse()
function in the place where you use that response data (in our case inError.js
)
- in
EventsList.js
, use the<Link>
component to navigate to theEventDetailPage
component upon clicking on an event - in
EventDetail.js
, output the<EventItem>
component - set to it the
event
prop & pass to it the event data for the event for which you want to view the details- load the event details by exporting another
loader()
function - use the
params
object to get theeventId
- register the
loader
in the route definitions to the<EventDetailPage>
route & import the loader fromEventDetail.js
inApp.js
- in
EventDetail.js
, use theuseLoaderData
hook to get the event data
- load the event details by exporting another
- in
EventItem.js
, use the<Link>
component to redirect the user to theEditEventPage
upon a click - in
EditEvent.js
, show the<EventForm>
component - prepopulate that event form with the data for the event which you try to add it
- as we fetched the
EventDetail
data on theEventDetailPage
, fetch it also on theEditEventPage
because that where we need the data for theEventForm
- in
App.js
, add a new route definition & set thepath
toeventId
& just setchildren
- add a
loader
to this new route & set theeventDetailLoader
to it - in
EditEvent.js
, use theuseLoaderData
hook - in
EventForm.js
, use the event data to set it as a default value to this form with thedefaulltValue
prop provided by React - to make sure that you use the
loader
data from the parent route, you must add the specialid
property inApp.js
- in
EventDetail.js
&EditEvent.js
, use theuseRouteLoaderData
hook instead ofuseLoaderData
& pass to it theevent-detail
id
- as we fetched the
- display the
<EventForm>
component inNewEvent.js
- plan how you will send data to the backend API when clicking on the
Save
button- function
submitHandler
(regular method) - or add
actions
to send data (recommended method when using React Router)
- function
- in
App.js
, add the specialaction
property to theNewEventPage
route definition - just like
loader
, theaction
property wants a function - add the
action
function inNewEvent.js
- in this
action
function send a POST request to the backend with thefetch()
function - extract the data from that form
- make sure that all the inputs have the
name
attribute - replace the
<form>
element by the special<Form>
component provided byreact-router-dom
- add to it the
method
property & set it topost
- in
NewEvent.js
, get hold of that request that is captured by the<Form>
tag & forwarded to theaction
property with therequest
property & theformData()
method & theget()
method
- make sure that all the inputs have the
- add the data that was submitted with the form to the request via the
body
property - in
App.js
, import thisaction
function for thisNewEventPage
route definition & use it as a value for thisaction
property - in
NewEvent.js
, navigate the user away after submitting successfully the form with theredirect
function
- in
EventForm.js
, you could send the request to a different route with theaction
attribute - a different way of triggering an action
- in
EventItem.js
, inside of thestartDeleteHandler
function, show a confirmation prompt with thewindow.confirm
function - if it returns
true
, trigger an action that deletes this event - in
App.js
, add anaction
property to theEventDetailPage
route - in
EventDetail.js
, add theaction
function - in
EventItem.js
, don't trigger this action with the special<Form>
component so you can display the prompt - instead, trigger the action & submit the data programmatically with the special
useSubmit
hook importedreact-router-dom
- in
- in
backend\routes\events.js
, add a short delay before sending the post request - restart the backend server
- when adding a new event, the submission will take a short while & we don't get any feedback regarding that
- in
EventForm.js
, get some feedback & disable theSave
button after submission with help of theuseNavigation
hook - in
backend\routes\events.js
, remove thesetTimeout()
function & restart the backend server
- in
EventForm.js
, show the users any validation errors for example because some user disabled client side validation - in
NewEvent.js
, react to potential backend validation errors (status 422)- don't show your default error page & throw an error response,
- instead, show the validation errors above the
<EventForm>
component so that you don't discard the values entered by the users - to do that, return the data (
response
) you want to output above the form in theaction
function - use the returned
action
data inEventForm.js
with help of theuseActionData
hook
- support submission for the
EditEvenPage
component - in
App.js
, register anaction
for thisEditEventPage
route - to do that, reuse the
action
which you are using for theNewEventPage
route- move the
action
function fromNewEvent.js
intoEventForm.js
- make the code in this
action
function a bit more dynamic to be able to send both a request for adding a new event as well as for editing an existing event - set a
method
prop - in
NewEvent.js
, set themethod
topost
& inEditEvent.js
, set themethod
topatch
- in
EventForm.js
, extract themethod
with therequest
object & use it for themethod
of the request we are sending to the backend - make the URL in the
action
function dynamic inEventForm.js
- in
App.js
, make sure thisaction
that you could namemanipulateEventAction
is used on all the routes wheren you need to use it - so add this
manipulateEventAction
to theNewEventPage
&EditEventPage
routes
- move the
- add all the new provided files
- trigger the
newsletterAction
whenever theNewsletterSignup
form is submitted which is part off all pages because it is part of theMainNavigation
- in
NewsletterSignup.js
, use theuseFetcher
hook imported fromreact-router-dom
- use the
fetcher.Form
component to still trigger an action (or loader) without initializing a route transition (so without navigating to the page where the action or the loader belongs) - add the
action
attribute to the form and trigger/newsletter
- but, with
fetcher.Form
you make sure that you don't load the element that belongs to this route component - get some feedback & update the UI with the fetcher's
data
&state
properties &useEffect()
- in
- go back to
backend\routes\events.js
, bring back a timer on the get route - in
Events.js
, load the page before the data is there by deferring loading- grab the code inside of the
loader()
function & outsource it into a separateloadEvents()
function - in the old
loader()
function, use thedefer
function imported fromreact-router-dom
- use the
Await
component imported fromreact-router-dom
instead of the<EventList>
component you were rendering - set to it the
resolve
prop & pass to it thedata
deferred value which we namedevents
in thedefer
function - between the
<Await>
tags, output a dynamic value which must be a function that will be executed by React once that promise resolves - wrap the
<Await>
component with the<Suspense>
component imported fromreact
to show a fallback whilst we're waiting for other data to arrive (in this case for these events to be fetched) - don't return the
response
anymore inside of theloadEvents()
helper function, but parse it manually
- grab the code inside of the
- in
EventDetail.js
, besides the<EventItem>
component, output the<EventsList>
component - fetch the
events
on this page- copy the
loadEvents()
fromEvents.js
& paste it inEventDetail.js
- add a new
loadEvent()
function where you pass anid
as an argument & paste inside of it the code from theloader
function - use these 2 helper functions inside of the
loader()
function to defer again with help of thedefer()
function - use the
event
&events
keys set in thedefer()
function with theuseRouteLoaderData()
function to get the data of these 2 defer requests so to say - use 2
<Await>
components to wrap every component you want to render to await these 2 different requests - use 2
<Suspense>
components to wrap every<Await>
component
- copy the
- this is still not the perfect solution because you can sometimes see the 2 loading messages of both deferred pieces of data
- tell React Router to wait with displaying the
EventDetailPage
until the details have been loaded - it should then load the
EventsList
component after we navigated to theeventDetailPage
- to do so, in
EventDetail.js
, add theawait
keyword beforeloadEvent(id)
inside thedefer
function - with that, you will never see the "Loading..." message for the event detail when navigating to the
EventDetailPage
- tell React Router to wait with displaying the
- side note: in
EventsList.js
, convert the links into absolute paths
- finalize the
HomePage
component - use the
<PageContent>
component to render a title and a paragraph