Jest testing, useMemo() inputs, controlled inputs and onChange() value parsing

A React project created with Create-React-App includes a test.js file in the src folder and comes pre-configured with Jest.

Jest is a testing framework, it organizes and runs the test file with the describe() and it() functions. It can be used to test any JavaScript code.

The @testing-library/react package provides the tools and utilities for component testing on React. It allows you to virtually render components and make assertions about their behavior.

//The describe(string, fn) groups related test into a test suite.
//The it(string, fn) handles teh singular tests.
"dependencies": {
  "@testing-library/jest-dom": "^5.17.0",
}

//App.test.js
import { render, screen, act, fireEvent, waitFor } from "@testing-library/react";
import Secondo from './components/Secondo';

describe("App", () => {
  it("Singular test", async () => {
    ...
  });
  ...
}
chevron-rightImported Utilities from Testing Libraryhashtag
  • render: function. It mounts the React component into a virtual testing environment.

  • screen: object. It provides query functions to find rendered elements within the rendered component.

  • act: function. It wraps any code that interacts with the component, ensuring that all its React updates are batched during tests.

  • fireEvent: function. It simulates user interactions on rendered components. It can trigger events like click(), input() and submit().

  • waitFor: function. It handles asynchronous operations in tests, including those commonly used with React Hook Form.

The getByRole() locates DOM elements for fireEvent() to trigger events on. Its option object finds elements by their accessible name using a regular expression.

The button role is implicit in the <button> element. A button's accessible name is often its text content. It can also be explicitly set with aria-label or a <label> element.

The act() function wraps the code that tests updates on the rendered component. The await keyword ensures that all async state changes are completed before the next assertion is executed.

Most useForm() operation, are implicitly asyncronous, they trigger and update side effects in the formState andtheir tests require to be wrapped in await act().

We check for Form submission errors by verifying its displayed error messages or by testing a mock function on form submit.

The fireEvent.input() simulates updating event.target.value by using a mock event object. The DOM element found by getByRole() is set as the target, and its value property is updated.

The Jest beforeEach() function sets up the different test environments. It renders a new form component before each test, preventing any test interference.

The Jest matcher() functions verify test conditions within expect() calls. The .not modifier checks for the absence of a condition.

The waitFor() utility handles asynchronous operations in tests. It is specifically for operations that are not triggered by user events and are not within an act () block.

The useMemo() input components on useForm()

The memo() is a React higher-order component that memoizes a function component, preventing unnecessary re-renders.

It differs from the useMemo() and useCallback() hooks.

  • memo() prevents functions components from re-rendering.

  • useMemo() memoizes values to avoid unnecessary recalculations.

  • useCallback() memoizes functions to prevent the creation of new function instances, which can help prevent excessive re-renders.

The memo() accepts an optional comparison function to control how props are checked for changes between renders.

By default, memo() uses a shallow comparison (using ===) and the component re-renders only if the comparison function returns false. You can provide a custom comparison function that receives the previous and current props.

chevron-rightObject references and memo() re-render exceptionshashtag

An object variable holds a reference, not the object's data, of the data memory location.

By default, memo() compares only prop references, not their values (==). That's why internal changes to the useForm() control prop are not detected by the memo() shallow comparison.

Form state internal changes, via the control, cause useFormState() to return a new object and trigger a re-render.

The controlled and uncontrolled input components

There are 2 types of input components, contolled and uncontrolled.

React-Hook-Form uses uncontrolled inputs for performance. It stores updated input values in the DOM and retrieves them via refs when needed.

External form libraries often use controlled inputs. These inputs store every change in the form's state, triggering a re-render.

The <Controller> component adapts controlled inputs to work like uncontrolled inputs. It filters the onChange() events from its rendered controlled input, only propagating changes when the value has actually changed.

Value parsing on input change

The useForm() returns all input values as strings, regardless of the input type. The valueAs function can parse these strings on submit, but empty inputs can still cause errors. We can parse on Change allowing the immediate setValue() reset of invalid input values.

The input step attribute controls the valid numeric increments and decimal values allowed for form submission.

Last updated