captionless image

React.js is a library for building web and native user interfaces. It allows developers to create user interfaces using individual pieces called components, which are written in JavaScript.

State, in the broader sense in the context of software, refers to the current status of data or a process. In React.js, states hold information about components. For instance, a countdown component can have a state ranging from 0 to 10, depending on the duration of the countdown. Managing and rendering states correctly are integral parts of React.js because changes in state make a component re-render. States control the behavior of a component, and updating state is asynchronous.

import React, { useState } from 'react';
// Define a functional component named Example
function Example() {
  // Declare a state variable 'count' and a function 'setCount' to update it
  // Initialize 'count' to 0
  const [count, setCount] = useState(0);
  // Return the JSX to render the component
  return (
    <div>
      {/* Display the current value of 'count' */}
      <p>You clicked {count} times</p>
      {/* Button that increments 'count' by 1 each time it is clicked */}
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
// Export the Example component as the default export
export default Example;

Due to the asynchronous nature of updating state, it becomes necessary to ‘step outside of React’ to perform operations known as side effects. Simply put, a side effect is ‘something that happens after something has happened.’ Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects.

import React, { useState, useEffect } from 'react';
// Define a functional component named Example
function Example() {
  // Declare a state variable 'count' and a function 'setCount' to update it
  // Initialize 'count' to 0
  const [count, setCount] = useState(0);
  // useEffect hook to perform side effects in the component
  // This effect updates the document title whenever 'count' changes
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  }, [count]); // The effect depends on 'count' and runs after every render where 'count' changes
  // Return the JSX to render the component
  return (
    <div>
      {/* Display the current value of 'count' */}
      <p>You clicked {count} times</p>
      {/* Button that increments 'count' by 1 each time it is clicked */}
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
// Export the Example component as the default export
export default Example;

The compositional nature of React.js is one of its core principles and is fundamental to how applications are built with this library. It revolves around the idea of creating small, reusable components that can be composed together to build more complex user interfaces. Because of this, state flows from parent components to child components. However, this can pose a technical challenge known as prop drilling. Prop drilling refers to the situation where a parent component shares state with many levels of child components. To allow these child components to access the state, props are used to pass it down the component chain. Prop drilling can be hectic and bug-prone, making code changes inconvenient. The Context API was created as a solution to this technical challenge. It provides a global store for application-wide state that can be utilized at every level of the component tree without the need for props. With context, state can be read and updated directly, regardless of the component’s hierarchy within the DOM tree.

import React, { createContext, useContext, useState, useEffect } from 'react';
// Create a context for count state
const CountContext = createContext();
// Create a provider component
export const CountProvider = ({ children }) => {
  // Declare a state variable 'count' and a function 'setCount' to update it
  // Initialize 'count' to 0
  const [count, setCount] = useState(0);
  // Return the CountContext provider with 'count' and 'setCount' as its value
  // This makes 'count' and 'setCount' available to any child component that consumes the context
  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
};
// Custom hook to use the CountContext
// This provides an easy way to access 'count' and 'setCount' from the context
export const useCount = () => {
  return useContext(CountContext);
};

The code snippet above demonstrates the implementation of a React context to manage and share state across different components in a React application. This approach leverages the Context API to solve the problem of prop drilling, providing a cleaner and more efficient way to manage state at various levels of the component tree.

The code begins by importing necessary functions from the React library. These include createContext, useContext, useState, and useEffect. The createContext function is then used to create a context named CountContext. This context will be used to share state across the component tree without having to pass props down manually at every level.

The CountProvider component is defined and exported. This component will wrap the parts of the application that need access to the shared state.

The CountContext.Provider component is used to make the count state and the setCount function available to any descendant components. The value prop of the Provider is an object containing count and setCount. This allows any component within the CountProvider to access and modify the count state.

CountProvider renders its children components. This enables any component within the CountProvider to consume the context and access the shared state.

Finally, wrap your main application or the part of the application where you want to use this context with the CountProvider.

import React from 'react';
import ReactDOM from 'react-dom';
import { CountProvider } from './CountProvider';
import Counter from './Counter';
const App = () => {
  return (
    <CountProvider>
      <Counter />
    </CountProvider>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));

The created context is then exported to using a custom hook, useCount, which returns the objects defined within context provider value prop.

Custom hooks are functions that implement commonly used logic within a component. They are a mechanism to reuse stateful logic. Every time a custom hook is used, all states and effects are fully isolated. Custom hooks follow the rules of built-in React hooks and, as such, can only be called inside other hooks and components. Custom hooks are created with the “use” prefix. A good custom hook is declarative and carries out a single task. Hooks follow the programming paradigm of DRY (Don’t Repeat Yourself).

Usually, values returned from custom hooks are completely independent, except if stored in context.

import React from 'react';
import { useCount } from './CountProvider';
const Counter = () => {
  const { count, setCount } = useCount();
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
};
export default Counter;

By using a combination of context and custom hooks, you can efficiently manage and access shared state across your application. The CountProvider component wraps around the part of the application that needs access to the count state, and the custom useCount hook provides an easy way to access this state and its updater function.

This method makes the state management more modular and maintainable, allowing you to keep the state logic centralized and reusable. Any component within the CountProvider can now easily access and update the count state, ensuring a straightforward and efficient data flow throughout the component tree.