Photo by Alexander Schimmeck on Unsplash
Most React applications have multiple pages and you need to set up some type of routing. Let's take a look at how to add multiple pages to a React application created with ReScript and Vite.
We'll be starting with a basic React app created with ReScript, Vite, and Tailwindcss. This guide will help you get that set up.
@rescript/react
comes with a router already so we don't need to install anything else to get started. Here are the full docs, but let's walk through each step in detail to make sure we understand it.
Create two page components
We'll need to create a component for each page we want to be able to navigate to. Let's make a page called dog
and one called cat
. We're using Tailwind for our styles.
// src/Dog.res
@react.component
let make = () => {
<div>
<h2 className="text-xl"> {React.string("Dog")} </h2>
<img
className="max-w-full mx-auto mt-5"
alt="a dog"
src="https://media.giphy.com/media/4Zo41lhzKt6iZ8xff9/giphy.gif"
/>
</div>
}
// src/Cat.res
@react.component
let make = () => {
<div>
<h2 className="text-xl"> {React.string("Cat")} </h2>
<img
className="max-w-sm mx-auto mt-5"
alt="a cat"
src="https://media.giphy.com/media/7YCA4XbA0ArQuWEfqJ/giphy.gif"
/>
</div>
}
You will also want to create an interface file (.resi
) for each of these components to make sure Vite's HMR works correctly and to speed up the already fast ReScript compiler. If you are using VSCode with the ReScript extension you can do this automatically by hitting f1
and looking for ReScript: Create an interface file for this implementation file
. It's always good practice to create .resi
files.
// src/Dog.resi
@react.component
let make: unit => React.element
// src/Cat.resi
@react.component
let make: unit => React.element
Note:
unit
means the function doesn't take in any parameters.
Creating a Page component to handle what to render
We'll need a Page
component to handle rendering the correct content based on the route.
This will use ReScript's Pattern Matching feature to handle what we want to show based on the route. We'll switch
on the url.path
, which is a list
. In our case we want to render <Dog/>
if the list has one item that is "dog"
, which would be a path of /dog
. If the path had multiple parts, like /dog/poodle/fluffy
we would have #list{"dog", "poodle", "fluffy"}
. For now let's just focus on handling "dog" and "cat".
@react.component
let make = () => {
let url = RescriptReactRouter.useUrl()
switch url.path {
| list{"dog"} => <Dog />
| list{"cat"} => <Cat />
| _ => <h2> {React.string("404: page not found")} </h2>
}
}
Remember to generate the .resi
file for this component!
Add the Page component to App.res
Clear out the contents of the existing src/App.res
file and set it up to render our new Page component.
@react.component
let make = () =>
<div className="text-center p-5 text-gray-700">
<h1 className="text-3xl m-5"> {React.string("Cats or Dogs?")} </h1>
<Page />
</div>
You should now see our 404 error.
If you manually add /cats
to the url you should see the cat page.
Creating our navigation bar
We'll need a quick nav bar to move between pages:
// src/Nav.res
@react.component
let make = () => {
<nav className="flex justify-center space-x-2 w-full mx-auto">
<button className="bg-blue-300 p-2 mx-1 rounded text-white"> {React.string("cat")} </button>
<button className="bg-blue-300 p-2 mx-1 rounded text-white"> {React.string("dog")} </button>
</nav>
}
Add it to App.res
:
@react.component
let make = () =>
<div className="text-center p-5 text-gray-700">
<h1 className="text-3xl m-5"> {React.string("Cats or Dogs?")} </h1>
<Nav />
<Page />
</div>
Now we have buttons, but they don't do anything, and we start out on a 404 page.
Hooking it all up.
Redirect to a default page
If a user opens our app we don't want them to see a 404 page, so let's redirect them to "/cat".
We can get the current url from RescriptReactRouter.useUrl()
and then switch on it inside of a useEffect
hook. url.path
is a list
and we can pattern match for an empty list and replace the url with cat
or do nothing by returning ()
(that's how you write unit
).
Here's our new code:
let url = RescriptReactRouter.useUrl()
React.useEffect0(() => {
switch url.path {
| list{} => RescriptReactRouter.replace("cat")
| _ => ()
}
None
})
You'll notice we used useEffect0
instead of useEffect
. In ReScript a value can only have one type so we need different functions depending on how many values we plan on using in the dependency array. We only want to do this once so we would be passing it an empty array if we were in normal JavaScript, but here we just use useEffect0
. If we had one item we would use useEffect1
and so on.
ReScript forces us to always return something as part of cleaning up a useEffect
hook, and since we don't have anything to clean up here we just use None
as the last expression.
Here's what App.res
looks like now:
@react.component
let make = () => {
let url = RescriptReactRouter.useUrl()
React.useEffect0(() => {
switch url.path {
| list{} => RescriptReactRouter.replace("cat")
| _ => ()
}
None
})
<div className="text-center p-5 text-gray-700">
<h1 className="text-3xl m-5"> {React.string("Cats or Dogs?")} </h1>
<Nav />
<Page />
</div>
}
Set up navigation
We need to show a new page after a user clicks on a navigation button.
Let's make on function to handle clicking on a navigation button:
let handleClick = React.useCallback0(path => {
RescriptReactRouter.push(path)
})
.push
will change our URL in a way that allows the browsers back button to still work and it will stay in sync with the useUrl
hook we have in the Page.res
component.
We can call this from each navigation button with the correct path.
// src/Nav.res
@react.component
let make = () => {
let handleClick = React.useCallback0(path => {
RescriptReactRouter.push(path)
})
<nav className="flex justify-center space-x-2 w-full mx-auto">
<button onClick={_ => handleClick("cat")} className="bg-blue-300 p-2 mx-1 rounded text-white">
{React.string("cat")}
</button>
<button onClick={_ => handleClick("dog")} className="bg-blue-300 p-2 mx-1 rounded text-white">
{React.string("dog")}
</button>
</nav>
}
You now have a small app with functioning navigation!
Please let me know in the comments if you have any questions.