Knowledge Hub
According to a survey conducted by stackoverflow in 2023, React.js dominates as the second most preferred JavaScript technology ranking ahead of Vue.js and Angular. This is largely due to its extensive tooling, active ecosystem, and unmatched flexibility.
Backed by a component-based architecture and virtual DOM implementation, it has continued to excel at building modern and dynamic web applications. React.js remains a framework of choice among software developers, powering numerous websites seamlessly.
Despite its numerous advantages, one of the biggest challenges In working with React is efficient state management. In React, state management involves storing and modifying application data in a central hub, making it easier to share data across components. While this is easy to implement in small applications, it becomes increasingly difficult in bigger applications with numerous components sharing and modifying data.
The React community provides several tools to help developers manage state. However, the rapidly evolving ecosystem presents a challenge for beginners, making it hard even for seasoned developers to choose the best state management tools for a given React project. The aim of this article is to offer insights on the best state managers in React, it’s purpose is to help you enhance your skills and boost productivity
Recoil.js is a state management library developed and open-sourced by Facebook. It offers distinct advantages over several other state management libraries like Redux, MobX, Zustand, etc.
The primary advantages of Recoil are:
this.state
and this.setState
used in traditional state managers.To use Recoil in your projects, start by installing the Recoil library in your project using npm or Yarn.
npm i recoil
# or
yarn add recoil
One of the fundamental steps in incorporating Recoil.js into your React project is wrapping your entire application with the <RecoilRoot>
component provided by the Recoil library. It provides a central hub where your application’s state will be managed.
Below is a code illustration:
import React from 'react';
import ReactDOM from 'react-dom';
import { RecoilRoot } from 'recoil';
import App from './App'; // Replace 'App' with your main application component
ReactDOM.render(
<RecoilRoot>
<App />
</RecoilRoot>,
document.getElementById('root')
);
In this example, you import RecoilRoot
from recoil
and wrap your application’s root component within it. Doing this establishes RecoilRoot
as a provider from which all components can access and modify states.
Recoil has two key features that contribute to its effectiveness in state management. These are Atoms and Selectors.
An atom in Recoil.js is a shareable piece of state that React components can read from and write to. Subscribed components within your project can access and modify state values in an atom. Also, when an atom is modified, each subscribed component is re-rendered with the new value.
Below is an illustration of an atom definition in Recoil:
import React from 'react'; // Importing the React library
import { atom } from 'recoil'; // Importing the 'atom' function from the Recoil library
// Define the Atom
const counter = atom({ // Defining an Atom called 'counter'
key: 'count', // Unique identifier for the Atom
default: 0, // Initial value of the Atom is set to 0
});
export default counter; // Exporting the 'counter' Atom as the default export of the module
The code block above shows how you can use Recoil atoms. It defines an atom called counter
using the atom property provided by Recoil. The atom has a unique identifier and its default value set to 0. Exporting an atom makes it accessible to subscribed components to consume and modify.
After defining an atom, it can be accessed within subscribed components. This is illustrated in the code block below:
import React from 'react'; // Importing the React library
import { useRecoilValue } from 'recoil'; // Importing the 'useRecoilValue' hook from the Recoil library
import { counter } from './atom'; // Importing the 'counter' Atom from the './atom' file
function CounterComponent() { // Declaring a functional component named 'CounterComponent'
const count = useRecoilValue(countAtom); // Using the 'useRecoilValue' hook to get the current value of the 'countAtom' Atom
return (
<div>
<h1>Count: {count}</h1> {/* Displaying the value of 'count' in the component's UI */}
</div>
);
}
export default CounterComponent; // Exporting the 'CounterComponent' as the default export of the module
The code block above shows the consumption of an atom within a subscribed component. It starts by importing the useRecoilValue
hook from Recoil. The CounterComponent
uses this hook to retrieve the current value of the counter atom. It then renders the value of count
in an h1
element.
Recoil.js also provides selectors, which are pure JavaScript functions that enable developers to transform or mutate states synchronously or asynchronously based on the existing state data. These selectors provide a powerful mechanism to manipulate the states and transform data within React applications.
One use case where Recoil selectors particularly shine is in React components that require constant updates, for example, in a shopping cart component.
Let’s assume you are defining a Recoil atom called cartItems
that stores an array of items added to the shopping cart. Each item in the cart has properties, e.g: id
, price
, and quantity
.
You can easily define a Recoil selector called totalPriceSelector
that calculates the total price of all the items in the cart based on the quantity and price properties.
Figure: Recoil data visualization by Jennifer Fu
The image above illustrates how atoms and selectors are linked in Recoil. This interconnection between atoms and selectors allows the modification of states set in atoms by selectors linked to React components
Here's an illustration:
import { selector, useRecoilValue } from 'recoil'; // Importing the 'selector' and 'useRecoilValue' from Recoil library
import { cartItems } from './atom'; // Importing the 'cartItems' Atom from the './atom' file
const totalPriceSelector = selector({ // Defining a selector called 'totalPriceSelector'
key: 'totalPrice', // Unique identifier for the selector
get: ({ get }) => { // 'get' function that calculates the total price based on the cart items
const items = get(cartItems); // Getting the value of the 'cartItems' Atom
let total = 0; // Initializing the total price
items.forEach(item => { // Iterating over the items in the cart
total += item.price * item.quantity; // Calculating the total price based on item prices and quantities
});
return total; // Returning the calculated total price
},
});
function ShoppingCart() { // Declaring a functional component named 'ShoppingCart'
const totalPrice = useRecoilValue(totalPriceSelector); // Using the 'useRecoilValue' hook to get the value of 'totalPriceSelector'
return (
<div>
<h2>Shopping Cart</h2>
<p>Total Price: ${totalPrice}</p> // Displaying the total price in the component's UI
</div>
);
}
The code block above illustrates the use of Recoil selectors to easily modify a derived state: totalPrice
, based on the current state of the cart items. It shows a seamless and reactive approach to managing complex state modifications in the sample shopping cart component.
SWR is a library for asynchronous development, which adopts a Stale-While-Revalidate technique, that serves the user-cached data while concurrently sending a refresh/revalidate request in the background to obtain updated data.
Over time there have been contemplations among developers about the most effective approach to data retrieval, especially the ways to prevent excessive database queries and also eliminate the wait time required to load asynchronous data.
With SWR, you can make asynchronous tasks smoother and easier — it brings some optimizations in performance by minimizing network requests and giving users a smooth experience with the display of some pre-fetched data while making requests for the latest data in the background.
SWR is easy and very flexible to use for handling complex data fetching with React.
Figure: SWR flow chart by Jacob Cofman
The image above is a flowchart illustrating how SWR works behind the scenes. When a GET request is made to the database, SWR checks if a similar request has been made before and if the data has been cached. If the data is found in memory, it serves the user the existing data while simultaneously sending a request for an updated data in the background, else It sends a new request to get the data, cache the data and serve the data to the user.
To use SWR in your React projects, start by installing the SWR library using npm or Yarn.
npm i swr
# or
yarn add swr
The code above installs SWR as a dependency in your React project.
You can then start fetching and managing data in your components by importing the useSWR
from SWR
as shown in the code below:
import React from 'react';
import useSWR from 'swr';
OnFocus
, OnMount
, onHover
, etc.data
, error
, and isValidating
keys.useEffect
hook.Below is an example of SWR implementation in a simple React component:
import React from 'react'; // Importing React library
import useSWR from 'swr'; // Importing the useSWR hook from the SWR library
const fetchData = async (url) => {
const response = await fetch(url); // Making an HTTP request to the provided URL
const data = await response.json(); // Parsing the response data as JSON
return data; // Returning the fetched data
};
function MyDataHandler() {
const { data, error } = useSWR('https://api.sample.com/data', fetchData, {
refreshInterval: 3000, // Refresh data every 3 seconds
revalidateOnFocus: True, // enable revalidation when the component regains focus
});
if (error) {
return <p>Error occurred: {error.message}</p>; // Displaying an error message if an error occurred during data fetching
}
if (!data) {
return <p>Loading...</p>; // Displaying a loading indicator while data is being fetched
}
return (
<div>
<h1>Data: {data}</h1> // Displaying the fetched data
</div>
);
}
export default MyDataHandler; // Exporting the MyComponent function as the default export
The code block above illustrates the use of the useSWR
hook provided by SWR in a MyDataHandler
component. It starts by importing dependencies and defining a function called fetchData
to fetch data from the provided URL.
Inside the MyDataHandler
component, the useSWR
hook is used to handle asynchronous data fetching. It requires configuration parameters and returns two variables: data
and error
.
While the data is being fetched, a loading indicator is shown. Once the data is successfully fetched, it is rendered in a ‘h1' element. If an error occurs during the data fetching process, an error message is shown instead.
Integration of highly responsive forms has become an integral part of software development, this is especially due to the large emphasis on user interaction and data collection in modern applications. Formik is a highly customizable and efficient form management library that simplifies the process of building and managing highly responsive forms.
Its simplicity, built-in state management, Smaller file size, and Integration with the React ecosystem are among the reasons why Formik is a preferred choice for many developers. One of its unique features is the Field Array functionality, which allows developers to add and remove form fields dynamically based on user interactions.
To use Formik in your React projects, start by installing the Formik library in your project using npm or Yarn.
npm i formik
# or
yarn add formik
Below is a simple implementation of Formik in a basic React form component:
import React from 'react';
import { Formik, Field, ErrorMessage, Form, resetForm } from 'formik';
function MyForm() {
// Initial form values
const initialValues = {
name: '',
email: '',
};
// Form validation function
const validateForm = (values) => {
const errors = {};
// Check if name field is empty
if (!values.name) {
errors.name = 'Name is required';
}
// Check if email field is empty or invalid
if (!values.email) {
errors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
errors.email = 'Invalid email format';
}
return errors;
};
// Form submission handler
const handleSubmit = (values, { resetForm }) => {
// Perform form submission logic, e.g API call
console.log(values);
// Reset the form after successful submission
resetForm();
};
return (
<div>
<h1>My Form</h1>
<Formik
initialValues={initialValues}
validate={validateForm}
onSubmit={handleSubmit}
>
<Form>
<div>
<label htmlFor="name">Name:</label>
<Field type="text" id="name" name="name" />
<ErrorMessage name="name" component="div" />
</div>
<div>
<label htmlFor="email">Email:</label>
<Field type="email" id="email" name="email" />
<ErrorMessage name="email" component="div" />
</div>
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);
}
export default MyForm;
The code above illustrates the implementation of a responsive form using Formik.
The MyForm
component defines the initial form values and implements form validation logic using a validateForm
function. The form submission is handled by the handleSubmit
function.
The form structure is encoded in the return statement. The form fields are defined using the Field component. Error messages are also rendered using the ErrorMessage
component provided by Formik.
Upon submission in this illustration, the form values are logged to the console, and the form resets by calling the resetForm
function.
Yup is a JavaScript library used for form data and schema validation. It is commonly used in conjunction with Formik as a tool to define and enforce form validation rules. For example, it is used to ensure that a user enters the correct email or the password entered contains the required characters. It can also perform more advanced data validation functions such as Asynchronous Validation, Custom Validation, Array Validation, etc.
You can install these libraries using the following command:
npm install formik yup
The code block below illustrates the incorporation of the Yup library with Formik for form validation in a React application.
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const MyForm = () => {
const formik = useFormik({
initialValues: {
name: '',
email: '',
password: '',
},
validationSchema: Yup.object({
name: Yup.string().required('Name is required'),
email: Yup.string().email('Invalid email address').required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
}),
onSubmit: (values) => {
// Handle form submission here
console.log(values);
},
});
return (
<form onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
/>
{formik.touched.name && formik.errors.name && <div className="error">{formik.errors.name}</div>}
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="text"
id="email"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email && <div className="error">{formik.errors.email}</div>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password && (
<div className="error">{formik.errors.password}</div>
)}
</div>
<button type="submit">Submit</button>
</form>
);
};
export default MyForm;
In the MyForm
component above, Formik is initialized with the useFormik
function which takes an object configuration. The initialValues
property defines the initial values of the form fields, which include "name," "email," and "password."
The validationSchema
uses Yup schema objects to specify the validation rules for each field. For example, it ensures that the "name" field must contain a non-empty string and renders an error message ('Name is required') when it is left empty. Likewise, the "email" field is required to contain a valid email address format, and the "password" field must be at least 6 characters long, each rendering error messages if these validation rules are not met.
The onSubmit
property defines a function that receives the form values and logs them to the console on submission.
The return statement contains the form structure. Here, Formik is used to manage the state of the input fields. Error messages are also selectively displayed when a field has been edited and contains validation errors.
In conclusion, this code shows a powerful way to validate forms using Formik and Yup by defining validation rules and error-handling mechanisms.
To learn more about the Yup library, check out the documentation.
This article explored three powerful React.js tools: Recoil for general state management, SWR for data fetching, and Formik for reactive forms.
Utilizing these tools, you can enhance your React.js skills and stay ahead in the React ecosystem. For more on these tools, check out: Recoil.js Docs, SWR Docs, and Formik Docs.
That’s all for now, folks. Keep coding, keep learning, and embrace the Code-Eat-Sleep-Repeat mantra for continuous growth. Happy coding!