Kent C. Dodds: 0:00 Let's start out by making the API that we want to have exist and then we'll go ahead and build that API. What we want to be able to do is have a modal component, and inside of that modal component we want to have a control for the openButton. We'll have a modalOpenButton.
0:18 Inside of that is the button that we want to use for opening this particular modal, which is right here. We'll copy and paste that right there. Then we want to have a modalContents component. Here, we're going to want an aria label, which we have right there. We'll copy that.
0:39 That'll be helpful for screen readers. Here's the contents of our form, which we can come up here and grab right here. That's our closeButton. Here's our title and the actual LoginForm. We'll copy all of that right there.
0:54 This is the closeButton, but we don't want to have to worry about that state that's being managed for us. We're going to wrap this inside our modalDismissButton component that we'll make. Then for our log in on submit, that's going to be this Login function.
1:12 The submitButton is this submitButton that we've got right there. Then for our title, that's this log in right here. Then we won't' need this onClick any more, because our modalDismissButton will handle that for us.
1:27 This is the API that we want to have exist. Let's make this API. We need a modal, modalOpenButton, modalContents, and modalDismissButton. Let's come up here and we'll import those as if they existed today. We'll import them from components/modal.
1:46 We'll get modal, modalDismissButton, modalContents, and modalOpenButton. Now let's build those components. Import React from react. We'll import Dialog from lib. This Dialog is simply the Reach Dialog that we get from Reach UI. It's just been styled with a notion to have the styles that we want.
2:15 We're going to be building our own modal API on top of Reach dialog. Let's make a function for modal. This modal is going to be managing our state. The state we're managing isOpen. We'll have setIsOpen. We'll initialize this to false.
2:33 Then we'll need to render something here, I'm not sure what yet but let's make the rest of our components. We'll need a modalDismissButton. This is going to accept children, but part of the API is just to accept a single child. I'm going to alias this to child to communicate that to people reading this code.
2:52 Somehow this needs to get the setIsOpen to toggle that to false and we need to take that Child button and set it onClick to set that isOpen to false. Let's deal with that later. We're also going to need a modalOpenButton that will operate in a very similar manner.
3:11 Then finally, a modalContents, which is ultimately going to be responsible for rendering the dialogue. We'll take all of the props that we get from this and forward those along to the dialog, specifically, the aria-label and the children.
3:30 How are we going to communicate between the Modal and its compound component children? This modalContents needs to know whether to pass isOpen to the dialogue and the dialogue also has an onDismiss prop that it's going to need to use to close the Modal.
3:48 We want to be able to do all of this while implicitly sharing the state between each of these components, so that users of our components don't have to worry about passing that state around to these different components.
4:00 The way we're going to do that is with a modalContext. We'll use this with React.createContext and then our Modal, which is responsible for managing the state can share that state implicitly by rendering a modalContextProvider. The Modal will just accept all the props and forward them along right here.
4:21 We do need to spell this with the same casing, otherwise, it won't work. We'll provide the value as an array of isOpen and setIsOpen. Now, that means we can get setIsOpen from React.useContext(ModalContext).
4:40 We can do the same in our ModalOpenButton and then down here we can get the isOpen and setIsOpen from that context as well. We'll pass isOpen as a prop here. Then onDismiss, we'll pass a function that calls setIsOpen to false.
4:58 Then for these buttons here, we're going to accept that Child button and we're going to return React.cloneElement(child). We'll make a copy of that button and we'll give it a prop called onClick. That onClick will be a function where we call setIsOpen to false. We'll do a very similar thing for our modalOpenButton, except we'll set it to true.
5:21 Now, this modal is implicitly sharing this state between all of its compound components using React Context. Consumers of the modal components don't have to know anything about the context and state that's being shared between all of these components.
5:37 We're almost done. All we need to do now is export the modal modalDismissButton, the modalOpenButton, and the modalContents. Let's see if that works. We've got a couple extra buttons here.
5:51 Let's go back to our unauthenticated app. We'll remove the login form for the login button there. Now we have a single login that's powered by our new modal and a register that's powered by the old abstraction.
6:03 If I click login, we can say ABC and our password. The whole login works just as long as it had before. Now we can take this more powerful, more flexible abstraction, and use it for registration as well.
6:17 OnSubmit will be register. The modalTitle will be register. Our modalLabelText is going to be registration form. Our submitButton is going to be this secondary register button. Our openButton will be this secondary register button.
6:37 We can get rid of that. Save this. It looks like registration works just as well. There we go. Things are looking great. Now we can get rid of this entire abstraction right here. Let's just take a look at this. I see two bits of code that are pretty similar between these two.
7:01 One other thing I'm going to do is I'm going to grab this and make a React element called circleDismissButton. Then we'll just insert that where it was before. We'll do the same with this one because those two bits of code are exactly the same. It doesn't need to be parameterized, so I don't need to make a component out of it.
7:27 I'm just fine creating a single element and sharing it between both of those. I'm feeling pretty happy with this abstraction because it's nice and declarative, meaning that I can totally control the contents. I can totally control the openButton. It's generic enough to be useful throughout my application.
7:44 Let's review how we made this abstraction. Here, we made a modalContext. That's how we implicitly share some state between all of these modal components. The modal is responsible for rendering that contextProvider and providing that value to all of the models compound components.
8:01 Those children components consume that context and grab the piece of that contextValue that they need to do their job. The dismissButton and openButton have an API that accepts a single child and makes a copy of that child setting the onClick prop to update that isOpenState that is implicitly shared from the modal component.
8:21 Then our modal contents component is the one that's responsible for rendering the reach-ui-dialog. It forwards along the isOpenState. It also handles the onDismiss callback to update the state that's been implicitly shared.