DEV Community

Cover image for How to handle routing in a ReScript React app using pattern matching.
Josh Derocher-Vlk
Josh Derocher-Vlk

Posted on

How to handle routing in a ReScript React app using pattern matching.

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>
}
Enter fullscreen mode Exit fullscreen mode
// 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>
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
// src/Cat.resi
@react.component
let make: unit => React.element
Enter fullscreen mode Exit fullscreen mode

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>
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

You should now see our 404 error.
404 screen

If you manually add /cats to the url you should see the cat page.

a cat

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
  })
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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)
  })
Enter fullscreen mode Exit fullscreen mode

.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>
}
Enter fullscreen mode Exit fullscreen mode

You now have a small app with functioning navigation!

Please let me know in the comments if you have any questions.

Top comments (3)

Collapse
 
sethcalebweeks profile image
Caleb Weeks

Neat post! Rescript seems interesting. Hard to read with all the formatting issues, though.

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

I forgot to close a back tick, thanks for calling it out!

Collapse
 
jderochervlk profile image
Josh Derocher-Vlk

Ah, I will fix those! Not sure how they made it in.