Design Systems with Storybook, v2

Using Decorators for Context

Steve Kinney

Steve Kinney

Design Systems with Storybook, v2

Check out a free preview of the full Design Systems with Storybook, v2 course

The "Using Decorators for Context" Lesson is part of the full, Design Systems with Storybook, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Steve explains that decorators allow you to wrap a component in your story with another component.


Transcript from the "Using Decorators for Context" Lesson

>> But now we get to the victory lap portion of the show, which are little things where you're like, yeah, Steve explained this super well because he's really great at what he does and I'm excited to use this and I thought a little bit about the one or two places that you will maybe hit a wall.

And just to talk about them a little bit so that you don't hit a wall because I am a responsible practitioner. Theoretically, if we're implementing a design system, all of our components ought to be totally independent and not rely on any of the context and all the things that we're supposed to do, and maybe if you're starting with a greenfield project, you can do that.

But if you're not, you might have things that are expecting to be in a certain React or Svelte context, I assume other frameworks have context, or do hit an API call or something along those lines. What should you do? You should refactor those components to use dependency injection where you pass in the fetch that we can pass in a mock, because in the same story book test, you can get those mock functions and you can make sure it was called.

You should totally refactor all your code. Of course you should. You're not going to, [LAUGH] so let's talk about how to navigate some of those things. There's a case of, we need something's gonna be in the context API. There is or this is also similarly true if we were going to not be realistic.

I believe I have never, I managed to just avoid the whole CSS and JS craze, but I have seen screenshots of emotion code and I know that it involves some amount of a context that wraps around the entire thing. Or if you're using Redux and there's just something in there there's a time where you need to wrap that component in some kind of parent.

Could you use that render thing that we saw in the very beginning of the workshop that you totally forgot about? Yes, but the component story format is better to not. But if we do need to wrap a set of stories in the context, we can do that, so let's look at what's involved in that.

I have a component in here that maybe is intentionally not involved in best practices. We've got this task list. It's a to-do app. What's a workshop unless somebody has a to-do app at some point or another? And that's the task. There is, I do believe, yeah, it is, it expects to have access to some task list context, or some Emotion, CSS, and JS, I've just like I said, never used CSS and JS and was not going to like learn it just for this one example.

But anything where there's something that their component expects to have access to, and by itself as you pull it in, if I were to go ahead and pull in some of the stories in here, this will break, right? Because you're gonna find out that useContext, it couldn't get the structure of the property because there's no, it's not inside of a tasks context provider.

Could have been Emotion, could have been anything, it doesn't really matter. It's the first example I could come up with. And so we want to put it inside of a context, right? We accidentally saw how to do this earlier when we did the theming. Theoretically, because my light and dark mode theme in the way that I have it implemented in Tailwind, it was just a CSS selector.

All that theme add-on is doing is it's wrapping it in a div with that CSS selector. And if we can wrap our stories in a div with the right CSS selector, one would posit that we could also wrap the stories in a context provider, right? Seems to make sense.

This is in preview.js because I always want to be able to switch into dark mode. Only my task list needs that provider, so I can actually go down a level into the stories themselves. Cool, and so what we can do here is, where is that task list, alphabetical order is a thing in the stories.

We can also add what is called a decorator in here as well. And all a decorator does is it wraps and hugs your story. And at this point, before we wrapped and hugged it in a parent element that had a dark mode class on it, or in this case, data attribute, and now we're gonna wrap and hug it in a context provider.

So we can do decorators. That is an array because you could have more than one, they go in order, I think all the way from preview, then to meta, then to story, but then in order in that array as well. And they are just a function that takes technically two arguments.

It takes a story and a context which is more metadata than you ever thought you needed to know about the story. That's the args, the arg types, the name of the stories, what component, everything you can console log it later if you want or console log it now, I don't care.

And you can go ahead and basically we'll start with the autofilled example. This wraps in a div. I don't need a div, I need a context provider. But you know what, let's console log it just to see real, I'll show you everything in context real quick. It's still going to blow up on this one, so maybe we do it right.

I was like, we go see, but I don't know if it runs if the entire thing still explodes, we don't have a context, so that ad hoc decision maybe isn't the best one. But we've got in TaskListProvider, we do export this task list constant, we do export this TaskListProvider, so we can just pull that in.

So we say import { TaskListProvider } and then through the magic of React Provider. I'll talk about that in a second. I'm just gonna say that tasks Equals Yeah, perfect. Now we have a provider that takes some tasks, and if we go back to the story, look, it works now because we are now wrapped in the context that we ought to have been wrapped in.

And every story, if we add more of them, will also work, and I will add this one to the live coding branch because if one wanted a practice session of writing a bunch of those tests to make sure it works, this is a great place to go do that because there are buttons to hit and there are various labels, there's input fields, there's buttons that are disabled that turned enabled.

You could, this is kind of, we start with a very little button, we work up higher and higher levels. This is a fully component that works. We use a provider in this case, let's console log that. Context now, again, it could be, look, we did already, it could be your theme provider.

I have never used the context of the theme, but I know every example I've ever seen does that. I use Tailwind, so whatever. We go to the console, and we get this object. And this is all of the things we have in there. We have all the args that it got, in this case, none of them, the canvas element that we saw in those play tests that we wrapped around, so now you can see what that is.

So we can look at this context object, we didn't pass any args to that primary story, but if we did, they would be in here. You can see the canvas element, which we saw in our play tests, the ID of the component, which is what's in the URL up top, like the kind, the name, more things than you ever wanted, right?

But it's all in here, and you have access, so if there are other fun things that you seek to do, you have access to it in the context object.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now