How to Add Deeplinking and React Portals to Storybook
Let's analyze the restaurant detail page component and add it to Storybook, including deeplinking and React portals support.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the Storybook for React Apps course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Storybook for React Apps, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:07] In this lesson, we're going to be adding the restaurant detail page to Story book. And it's a very interesting component because it's got a loading state while the data is being fetched.
[00:08 - 00:14] It's got a 404 scenario in case the restaurant is not found. It's got a 500 if the server breaks.
[00:15 - 00:22] And also it's got a success state which is interactive. In fact, we have a prototype from Figma which will show you the interaction.
[00:23 - 00:29] So once you click over here, you will see some highlighted elements. So if you click on a food item, a model should appear.
[00:30 - 00:37] And once you select some items, on the top right, you will see a button that shows the sidebar. So all of these interactions can happen in the page.
[00:38 - 00:47] And that means we have a lot of stuff to show in Storybook. But before going to Storybook, we should dive deep into the page and understand what exactly is needed in order for the page to render correctly.
[00:48 - 00:54] So let's go to the code. And then under Pages > Restaurant > Detail Page, we see the component.
[00:55 - 01:07] And in this component, as you can see, it's got a lot of stuff. So it's got a bunch of styled components as well as it uses use params and use navigate both coming from React Router Dome.
[01:08 - 01:18] It fetches some data which has different statuses and the statuses are used to render different states of the page. If the server returns with the status 500, then we have an error.
[01:19 - 01:26] If it's 404, the page is not found. If it's loading, then we show the spinner. And if it's successful, it just continues and renders the component.
[01:27 - 01:41] And we also see here that we're using an app selector to get some data from Redux, which will provide the cart items used in the page. And we also are dispatching actions so that we add items to cart or we clear the item from the cart.
[01:42 - 01:53] So I've got the app running over here. And then to get to the restaurant's detail page, you can either click on the element right here or you go to View All Restaurants, you select a category, and then you select the restaurant.
[01:54 - 02:01] And if you click on any of the food items, you will see that there's a model. And in order to render a model, a lot of React applications use portals.
[02:02 - 02:14] So if we inspect the element over here, we're going to see that there is a div ID root and that is actually the entire application. But the model is actually rendered outside of the application and that is by using portals.
[02:15 - 02:23] So in order to understand that, we need to go back to public and an index HTML. And this is actually where the elements are being defined.
[02:24 - 02:48] So we've got an element which will render the app, but we also added a new element to project elements into as a model. In the restaurant's detail page, there is a component called food item model, which if you go inside all the way to the implementation from it, you're going to see a model component, which does some animations and it's got a portal element with the selector targeting a ID of model.
[02:49 - 03:04] So basically our restaurant's detail page needs support for styled components, state management as we can see over here, and routing with possible custom curriculums. It also has to be able to render a model and we also need to mock fetch requests coming from here.
[03:05 - 03:18] So now that we're familiar with the technology, let's add a new file over here, which is called restaurant's detail page, the stories dot t's x. And I'm going to paste the code because we already have been doing this for a long time.
[03:19 - 03:29] We essentially are adding components story and meta together with the restaurant detail page. We're using a layout full screen for removing the paddings and we've got a success state using the template.
[03:30 - 03:35] And we have the design defined over here and this is actually pretty new. So the design is a prototype.
[03:36 - 03:46] So if we open the prototype and we click on share prototype and copy the link, we can actually put it over here. And the add on designs in Storybook also supports prototypes and that's pretty cool.
[03:47 - 04:00] So if we open our stories after adding all of these things and then we go to Storybook, there should be a new story called the restaurant detail page. And if we click on it, we see the restaurant detail page rendering, but that's really not correct.
[04:01 - 04:10] So what's actually happening here is that the page is using a deep link. So it's getting the ID from the route, which is not being passed in our story.
[04:11 - 04:18] As you can see in the restaurant detail page over here, we have restaurant slash two. So if we change to one, for instance, this is fetching data from a different restaurant.
[04:19 - 04:32] So in our story, because there's no routing with the deep linking mechanism, there's no ID to fetch from. So it's actually trying to fetch real data with an unexisting ID and that's showing you the 404 state.
[04:33 - 04:43] But you know, we're almost there. So let's go back to our stories file and improve the implementation that we had for a routing mechanism so that we can allow for deep linking and that's going to fix our problem.
[04:44 - 04:53] So we go back to our decorators file under storybook folder and then we revisit the with router decorator. So the with router decorator, it's really simple.
[04:54 - 05:12] It's just adding support so that components using hooks like use params or use navigate will render correctly. But in this case now, let's suppose that we have a page that has some sort of like path being defined, which is something like slash restaurants slash ID, which we can actually find in the routes file.
[05:13 - 05:23] And we can see restaurants slash ID and then that path will render a element, which is the restaurant detail page. So if we go to our decorators again, the path is something like that.
[05:24 - 05:35] And then we also have something which is like the route, which is restaurants slash, let's say one. So we need to find a way to render a component as if it's being visited in that particular configuration.
[05:36 - 05:42] And that's basically a deep linking functionality. So what we can do here is we can create a parameter.
[05:43 - 05:49] So let's use the story context to get parameters. And then let's call the parameter deep link because why not?
[05:50 - 05:57] And that parameter will be defined in the stories. So that should contain both the path and the route so that the component renders correctly in that route.
[05:58 - 06:05] And we can do the following thing. So if we can check if there is no deep link being passed, so this will be undefined.
[06:06 - 06:19] And if there is no deep link, we're just going to render the component as it was working before. However, if there is a deep link, then we need to use a different router, which can be used from React Router, though it's called memory router.
[06:20 - 06:25] And we need to wrap it under two things. So it's called routes and route.
[06:26 - 06:34] And then over here, if it contains a deep link, we can get some data out of deep link. And that would be both the path and the route.
[06:35 - 06:44] And then we can return essentially a memory router. And the memory router has to receive initial entries, which is an array.
[06:45 - 06:58] And then we could just pass the route here, but we should be encoding the route with encode URI, so that it will be following the behavior of the browser. And then we need routes so that there is support for the routing.
[06:59 - 07:15] And also now we're going to render a route element that receives a couple properties. So it has a path, which we know because it's being passed by the deep link, and then it renders an element just like it's being used in the routes element, as you can see over here.
[07:16 - 07:19] So the element is going to be the story function. And there you go.
[07:20 - 07:34] So essentially we've got a with router decorator, which is applied globally, so we don't need to change anything else anymore. And now if we go back to our stories, what we can do here is add a new parameter to our meta, which is going to be called deep link.
[07:35 - 07:47] And because I already had this information, let me just copy this here and paste it right there. So we essentially are going to be passing a path and a route, which follows that configuration.
[07:48 - 08:01] So now when we go back to storybook and refresh this, you're going to see that this actually fetches the data from the correct restaurant. And if you were to, let's say, change this to number two, this is actually going to render and fetch the data from restaurant number two.
[08:02 - 08:10] And this is a great functionality for our decorator. Let's say you have a component that relies on a career parameter to render different states, such as a wizard form.
[08:11 - 08:19] You can actually now reproduce each one of them as stories by using this decor ator. So now if we open the add-on panel, we actually have a design, which is super cool.
[08:20 - 08:31] And now the design has a prototype that we can interact with. And then we can also interact with the page. And supposedly it's going to do the same thing. But if we click on the food items, nothing is happening.
[08:32 - 08:39] And I guess you already know the reason why this is not happening. It's because the component is using React portals to project the model into a div.
[08:40 - 08:52] But that div is not present in storybook because it's only used in the index HTML in the public folder for the application. So what we can do is we just copy this element over here and we go back to our stories file and then we can add to our story.
[08:53 - 09:04] So we wrap this in a fragment and then we basically just paste the model there. So now when we're rendering the story, we also have this placeholder. So if we click on the element, we should see a model.
[09:05 - 09:16] And given how the page is connected to Redux, we can actually interact with it and we can see everything happening correctly. So if we open the sidebar and let's say we change the quantity of a cheeseb urger, this should also reflect in the element over here.
[09:17 - 09:33] And that's pretty cool. In fact, if you want to exercise, you can create a new story that renders with items in the cart by passing some initial Redux state just like we did in the previous lesson for the page template stories. So from the list of things we needed to support, we basically tackled routing with a deep linking functionality.
[09:34 - 09:50] We tackled a model support, styled components and Redux. But there's one missing piece which is essentially marking the data because, you know, this is actually fetching real data and we don't want Storybook to be doing that. We'll be creating the missing stories in the next lesson. I'll see you then.