Tutorials on Usemap

Learn about Usemap from fellow newline community members!

  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL
  • React
  • Angular
  • Vue
  • Svelte
  • NextJS
  • Redux
  • Apollo
  • Storybook
  • D3
  • Testing Library
  • JavaScript
  • TypeScript
  • Node.js
  • Deno
  • Rust
  • Python
  • GraphQL

Testing a Custom React Hook (useMap)

Developers of the most popular React Hooks libraries rely on tests to enforce the overall quality of their libraries' code. Tests cover a wide variety of different use cases, give developers confidence that everything works as intended and serve as a form of documentation. Anytime a test fails, developers know which set of arguments to use to replicate the bug encountered and squash it lands in the distribution package. When writing a custom React Hook for a library, the Hook should be tested regularly and against the fringest of edge cases to appeal to a greater number of projects. Setting up a testing environment that executes tests fast and reliably requires the proper tools: This testing environment allows you to write tests that closely resemble your user's actions. For example, @testing-library/react comes with methods for rendering a React component ( render ) to a container ( document.body by default) and finding an element within the rendered content of this container via the element's label ( screen.getByLabelText ): With just these two methods, our test mimics, at a high-level, the browser rendering a contact form to the screen and the user searching for an e-mail address input field. In the case of React Hooks, you may not want to create dummy components for the sole purpose of testing your Hooks. A unit test for a React Hook should only test the Hook's functionality (independent of any component calling it). We should reserve the testing of Hooks called within a component for integration tests. Fortunately, the @testing-library/react-hooks library gives us testing utilities for testing Hooks in isolation without having to render any dummy components. Below, I'm going to show you how to test a custom React Hook built for a React Hooks library with the @testing-library/react-hooks library and Jest. To get started, clone this React Hooks library template from GitHub: This template has ESLint, TypeScript and Jest already configured and comes with a custom React Hook, useMap , which we will write tests for. Additionally, the @testing-library/react-hooks library has already been installed as a dev. dependency. The useMap Hook wraps around a Map object and mimics its API. If you would like to learn how to implement this Hook from scratch, then check out the blog post here . Within the __tests__ directory, create a new file, useMap.test.ts . We will write unit tests for the useMap Hook within this file. Within this file, import two methods from the @testing-library/react-hooks library: Then, import the useMap Hook. ( __tests__/useMap.test.ts ) Add a describe block with the text "useMap" to group all tests related to the useMap Hook within this one describe block. ( __tests__/useMap.test.ts ) Alongside the map state variable, which represents the Hook's current Map object, the useMap Hook provides several action methods for updating this state variable: From this point on, all describe blocks and tests will be written within the useMap describe block. Let's write a describe block for the set action method that covers two cases involving this action method: ( __tests__/useMap.test.ts ) For the "should update an existing key-value pair," we should render the useMap Hook using the renderHook utility method from the @testing-library/react-hooks library: ( __tests__/useMap.test.ts ) Here, we call the Hook with an array of one key-value pair. We pass this to a new Map object to create the initial map . The renderHook method returns an object with a result field. This field is a React ref. By reading the ref's current field, you can access the Hook's API (an array that contains the map state variable and the action methods). For now, let's omit the map state variable. I will explain why later on. ( __tests__/useMap.test.ts ) Let's double-check that our initial state was set correctly to a Map object with the key-value pair 1: "default" . ( __tests__/useMap.test.ts ) Save the changes. In the terminal, run the test npm script to verify that the initial state is set correctly: Now, let's call the set action to update the key-value pair of 1: default to 1: changed . We need to call the set action within the act method to flush any changes to the state into the simulated DOM before running any subsequent assertions. ( __tests__/useMap.test.ts ) Check that the key-value pair has been updated. The 1 key should have a corresponding value of "changed" . ( __tests__/useMap.test.ts ) Once again, save the changes. Run the test npm script to verify that the state has correctly changed: ( __tests__/useMap.test.ts ) With these few steps, you can write tests for the remaining action methods: ( __tests__/useMap.test.ts ) Remember how we omitted the map state variable and only accessed it from result.current[0] ? This is because all action methods are immutable. Therefore, anytime we call any one of these action methods, the map state variable destructured from the initial result.current will reference the initial Map object it's set to, not the new Map object that set it to internally in the Hook. Essentially, after the action method call, result.current references a completely different Map object than the one referenced by the destructured map state variable. Let's add a new describe block labeled "hook optimizations." Inside of this describe block, write a test to confirm this behavior: ( __tests__/useMap.test.ts ) Finally, let's write a test to make sure our useCallback and useMemo optimizations maintain reference equality after state changes. The reference to the action methods should never change. ( __tests__/useMap.test.ts ) Run the tests one final time and watch them all pass! Altogether... ( __tests__/useMap.test.ts ) For a final version of this code, visit the GitHub repository here . Try testing your own custom React Hooks with the @testing-library/react-hooks library.

Thumbnail Image of Tutorial Testing a Custom React Hook (useMap)

Implementing a useMap React Hook

Ever since the introduction of Hooks into the React library, creating cleaner, reusable components has become much easier. Instead of using render props and higher-order components, Hooks provide us a way to share stateful logic across multiple components without making any modifications to an application's component hierarchy. React comes with several built-in Hooks for handling component state and lifecycle, such as useState and useEffect , that can be composed to create all kinds of different Hooks. Creating your own custom Hooks can be tricky. Once you decide upon the part of a component to extract out into a separate function as a custom Hook, you need to carefully refactor this stateful logic to... For example, a Hook that manages authentication should... Keeping the Hook simple, broad and flexible opens up the components that can use it. In this case, any component that depends on the user's authentication status, such as a navigation bar to determine whether or not to display a login button or a dropdown toggle, can call this Hook and have access to its state and methods, all with a single line. When done properly, you can even abstract your Hooks into a React Hooks library for other developers to use in their own applications (irrespective of business logic). Below, I'm going to show you how to implement a custom React hook, useMap , within a React Hooks library. The useMap Hook wraps around a Map object and mimics it's API. This allows your React components to leverage Map objects without having to worry about low-level details like try...catch error handling. To get started, clone this React Hooks library template from GitHub: This template has ESLint, TypeScript and Jest already configured so that we can write, lint, test and build the useMap Hook for distribution. Within the src directory, create a useMap.ts file, which will contain the source code of the useMap Hook. Unlike plain JavaScript objects, Map objects can set objects as keys. This is quite useful for situations like mapping DOM elements to a corresponding piece of data or functionality. Additionally, Map objects are iterable, and keys set on the Map object will not conflict with properties inherited from the Object prototype like toString and constructor . The useMap Hook's API will closely follow the original React Hooks API of returning a pair of values: the current state and action methods for updating it. The Hook will return a Map object as the state and the following action methods for interacting with this object: Each time we perform any of these actions on the Map object, we create a new instance of a Map object to make it immutable to change. Here's how a component would call this Hook: Note : In the above code, the Map object maps DOM elements to strings. Let's start by implementing the Hook's state and setValue action method: ( src/useMap.ts ) The Hook can be passed a Map object or an array of key-value pairs (entries) to initialize the keys and values of the map state variable. If nothing is passed to the Hook, then initialState is set to a new, empty Map object by default. Define the clear action method, which empties the Map object of its key-value pairs: ( src/useMap.ts ) Define the set action method, which adds a new key-value pair or replaces an existing key-value pair: ( src/useMap.ts ) Define the deleteByKey action method (publicly exposed as delete ), which deletes a key-value pair: ( src/useMap.ts ) Define the initialize action method, which initializes map to a new Map object based on a passed tuple (or other iterable object). ( src/useMap.ts ) If any of these action methods are passed to a child component via props, then anytime map changes and causes the calling parent component to re-render, this triggers a re-render of its child components even though none of these methods changed. To prevent this unnecessary re-render, we should wrap each action method with useCallback and memoize the actions object with useMemo , like so: ( src/useMap.ts ) Since the keys and values of the map state variable can be of any type, they should be generically typed . Let's denote the generics by the type variable <K, V> : K for a key and V for a value. This type variable preserves the type information of the key-value pairs. Let's make use of this type variable by defining a type MapOrEntries that describes... ( src/useMap.ts ) Rewrite the useMap function as a generic function and annotate the initialState and mapOrTuple parameters with this type, like so: ( src/useMap.ts ) Finally, we need to define a type for the array, consisting of the map state variable and the action methods, returned by the Hook. Name this type UseMap , and define it as the following: ( src/useMap.ts ) Now, define the UseMapActions type, which provides a type definition for each action method: ( src/useMap.ts ) The Dispatch type tells TypeScript that setValue is a function that returns nothing. This is perfectly valid because this method simply updates the map state variable. That's it. The SetStateAction type tells TypeScript that setValue can be passed either... Altogether... ( src/useMap.ts ) Don't forget to export the types and the Hook inside the src/index.ts file! ( src/index.ts ) For a final version of this Hook, visit the GitHub repository here . Try refactoring your custom React Hooks into a separate React Hooks library, and share them with other team members and/or developers.

I got a job offer, thanks in a big part to your teaching. They sent a test as part of the interview process, and this was a huge help to implement my own Node server.

This has been a really good investment!

Advance your career with newline Pro.

Only $30 per month for unlimited access to over 60+ books, guides and courses!

Learn More