The result of this journey led to a greater understanding of how to implement custom React hooks, and it inadvertantly led to a better understanding of the built-in
useEffect()
hook to handle what should happen when the component renders.
First things first, did you know that there is an HTML <dialog>
element that you can use without having to import some 3rd-party library? It's pretty handy. Take a look at the docs to learn more.
When I am working with software, my mind takes on the learning style of Abstract Random. A few weeks ago, I was writing some HTML and CSS exercises for students at Nashville Software School, and was finishing up one that let's them explore the HTML <dialog>
element when it dawned upon me that I could use them in a sample application that we show to students called Nashville Kennels. It's an application that we build with them as they are introduced to React.
In one of the components, we show them how to ping a local API and use the data to build a list of animals that are currently being cared for or boarded at the kennels. When the user clicks on the animal's name, which is a button, the application originally re-routed the user to a different url (e.g. /animals/:id
) and a detail view was rendered.
I wanted to change it so that when the user clicked on the animals name, a dialog box would appear showing treatment history of the animal instead of rendering a different view.
My Journey With React Hooks
I found the very basics of hooks to be simple to understand and implement, so to deepen my understanding, I wanted to write my own, custom hook that would allow any component to show/hide a <dialog>
element in its DOM based on some user interaction. In my case, that interaction is a MouseEvent generated when the user clicks on the button containing the animal name at the top of the card.
The result of this journey led to a greater understanding of how to implement customer React hooks, and it inadvertantly led to a better understanding of the built-in useEffect()
hook.
Here's what I wanted to accomplish:
- Dialog to appear when user clicked on the animal's name.
- A button inside the dialog DOM that closes it when clicked.
- When user presses ESCAPE, the dialog should close.
- A hook that could be used by any component to show/hide any dialog.
Here's what I needed to implement these goals.
- An
AnimalDialog
component to show the information and the close button. - A
useModal
hook function that exposed a function to show/hide the dialog, and a state variable to determine if the dialog was open or not. - A global event listener that responded to the ESCAPE key and closed any open dialogs.
- A function to invoke in the
onClick()
event on each, individualAnimal
card.
Animal Dialog
This component should display the animal information, but also have the ability to close itself so it needs to render a button that, when clicked, should invoke a function that should remove the open
attribute on the DOM element.
So I define a toggleDialog
and an animal
parameter that will be passed in as props. The component iterates a treatments
array that is on the animal object and displays each one. I absolutely positioned the button in the top-right corner.
useModal Hook
This hook needs to export the function that toggles show/hide and a boolean that lets other components know if the dialog is currently visible or not. I want to it be able to work with any dialog element, so when the hook is invoked the developer can pass in the CSS selector needed to identify the element.
The toggleDialog()
method updates the internal boolean and performs the required changes to the open
attribute on the element. Performing direct DOM manipulation goes against the whole point of using React in the first place, so I know that I have further refactoring to do.
This hook will be used in the individual Animal
components (see code below) and the toggleDialog()
function will be invoked in the onClick()
event for the animal name button.
Implementing the Dialog and Event Listener
The parent component of Animal
is AnimalList
. Since I don't want an individual dialog element for each animal, I'm going to put a single dialog at the list level.
The Animal Card
Finally, the Animal
component uses the showTreatmentHistory()
function passed to it from its parent component. Since that function, in turn, invokes the toggleDialog()
function from the hook, clicking on the title will now open the dialog.
Note: This code has been heavily pared down to only show how the dialog is opened.