React JS - Reducers And Context API

Reducers and Context API in details

React JS - Reducers And Context API

useReducer

what is useReducer

useReducer is like a "state manager" that breaks down the state-changing process into three main parts:

  1. State: This is the current state (or the current values/data in your app).

  2. Action: This is what triggers a change in the state. Actions could be something like a user clicking a button, submitting a form, or anything that should modify the state.

  3. Reducer Function: The reducer function decides how to update the state based on the action. It takes the current state and an action, and returns a new state.

Here's a simple diagram of how useReducer works:

ACTION (e.g., button click)  -->  REDUCER FUNCTION  -->  NEW STATE

How to Use useReducer - (Example)

Let's look at a very simple example where we manage a counter with useReducer.

Step 1: Setup a Reducer Function

The reducer function tells how the state should change based on the action type.

// The reducer function
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}
  • state: This represents the current state. For example, if the counter is at 5, state.count would be 5.

  • action: This is the instruction (or "action") to change the state. It usually has a type to describe what kind of action it is.

Step 2: Use useReducer in Your Component

You can now use useReducer in your component.

import React, { useReducer } from 'react';

function Counter() {
  // Initialize useReducer with the reducer function and initial state
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>
        Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>
        Decrement</button>
    </div>
  );
}

Breaking Down the Code:

  1. useReducer: We call useReducer, passing in two things:

    • counterReducer: This is the reducer function that tells how to update the state.

    • { count: 0 }: This is the initial state, meaning our counter starts at 0.

  2. dispatch: This is a special function we get from useReducer. We use it to send an "action" to the reducer function. For example:

    • When you click "Increment", dispatch({ type: 'increment' }) sends an action to the reducer, which increments the count.

    • When you click "Decrement", dispatch({ type: 'decrement' }) sends an action to the reducer, which decrements the count.

  3. state: This is the current state, which includes the count. We use state.count to show the current count on the screen.

Why Use useReducer Instead of useState?

In this simple example, you could just use useState to manage the counter. But imagine you have a more complex state, like an object or an array that has multiple parts. Using useReducer allows you to manage these changes in a cleaner and more organized way by grouping all the logic in one place (the reducer function).

Advantages of useReducer:

  • Predictable State Transitions: Since all state updates go through the reducer function, it’s easy to see what causes the state to change.

  • Better for Complex Logic: If you have multiple actions (like adding, removing, updating), useReducer can make managing those actions easier and cleaner than using multiple useState hooks.

  • Similar to Redux: If you’ve worked with Redux (a state management library), useReducer works in a similar way, making it easier to transition to Redux for larger apps.

When to Use useReducer?

You might want to use useReducer when:

  • The state logic is complex (like managing a form, a to-do list, etc.).

  • You have a state that depends on the previous state.

  • You want to group related state logic together.

  • You need a more predictable and maintainable way of handling state transitions.

Todo App Using UseReducer - Todo App

Context API

The Context API in React allows you to manage and share global state (or data) across different components without having to pass props manually at every level (prop drilling). It's particularly useful when you have multiple components that need access to the same data but are nested deeply.

Steps to Create and Use Context in React

  1. Create the Context: You use React.createContext() to create a new context.

  2. Provide the Context: You use the Provider component to make the context available to child components.

  3. Consume the Context: You access the context data using either the useContext hook (for function components) or the Consumer component (for class components or function components).

1. Creating a Context

To create a context, you call React.createContext() and optionally pass an initial/default value.

import React, { createContext } from 'react';

// Create the context
const ThemeContext = createContext("light"); 
// "light" is the default value

Now we have a ThemeContext that we can use to share data (in this case, theme information) across different components.

2. Providing the Context

To allow child components to access the context, you need to provide the context using the Provider component. The Provider component takes a value prop which contains the data you want to share.

import React, { useState } from 'react';
import { ThemeContext } from './ThemeContext'; 
// Import the created context

const App = () => {
  const [theme, setTheme] = useState("light"); 
    // State to toggle between themes

  return (
    // Provide the context value to the children
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
};

In this example:

  • The App component is the parent.

  • It provides the context with a value object that contains both the current theme (light or dark) and a function to change the theme (setTheme).

  • All child components of the ThemeContext.Provider, including Toolbar, can now access the theme and setTheme.

3. Consuming the Context

The easiest way to consume context in functional components is by using the useContext hook.

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext'; // Import the context

const Toolbar = () => {
  // Access context values using useContext
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Toggle Theme
      </button>
    </div>
  );
};

In this example:

  • The useContext hook is used to access the theme and setTheme values from the ThemeContext.

  • The button toggles the theme between light and dark.

Key Concepts

  1. Provider: Passes data down to any components that need it.

  2. Consumer or useContext: Allows components to subscribe to context changes.

  3. No Prop Drilling: Context helps you avoid prop drilling, where props need to be passed down through multiple levels of components.

  4. Context can have multiple values: You can pass objects, arrays, or any type of data via context, not just single values.

When to Use Context API

  • Global State Management: When you need to share data across multiple components without prop drilling.

  • Theme or Language: For things like themes, user preferences, or languages that apply globally to the app.

  • Avoid Prop Drilling: If passing props through many levels of a component tree becomes cumbersome.

createContext and useContext - CreateContext and UseContext Example

Context Provider by Component - Context Provider Example

Todo List Using useContext and useReducer - Todo List

React Portals

React Portals provide a way to render components outside of their parent DOM hierarchy while still maintaining a connection to the React component tree. This is useful when you need to render UI elements like modals, tooltips, or dropdowns, which should appear on top of everything else but still be logically connected to a component elsewhere in the DOM.

How Portals Work

Normally, React components are rendered inside their parent component's DOM tree, following the structure of the app. However, there are situations where you need to break out of this hierarchy. For example, in modals or tooltips, you may need to:

  • Render a modal at the root level of the DOM to ensure it's not confined by CSS overflow or z-index issues of its parent elements.

  • Render content in a different part of the DOM without disrupting the normal flow of the component tree.

A React Portal allows you to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This is done using the ReactDOM.createPortal() method.

ReactDOM.createPortal()

ReactDOM.createPortal(child, container)
  • child: The React element(s) you want to render.

  • container: The DOM element you want to render the child into (often a root-level DOM node).

Example

Here’s an example where a modal is rendered using a portal:

  1. First, create a modal component using a portal:
import React from 'react';
import ReactDOM from 'react-dom';

const Modal = ({ isOpen, onClose, children }) => {
  if (!isOpen) {
    return null;
  }

  return ReactDOM.createPortal(
    <div className="modal">
      <div className="modal-content">
        <button onClick={onClose}>Close Modal</button>
        {children}
      </div>
    </div>,
    document.getElementById('modal-root') 
// Target element outside the main DOM hierarchy
  );
};

export default Modal;

In this example:

  • We use ReactDOM.createPortal to render the modal's content into an element with the id="modal-root". This is an element outside the typical DOM structure, like under the body tag.
  1. In the main app component:
import React, { useState } from 'react';
import Modal from './Modal';

function App() {
  const [isModalOpen, setModalOpen] = useState(false);

  return (
    <div className="App">
      <h1>React Portal Example</h1>
      <button onClick={() => setModalOpen(true)}>Open Modal</button>
      <Modal isOpen={isModalOpen} onClose={() => setModalOpen(false)}>
        <h2>Modal Content Here!</h2>
      </Modal>
    </div>
  );
}

export default App;

Here:

  • A modal is opened when the button is clicked, and it’s rendered into a completely different part of the DOM (the #modal-root element).

  • Even though the modal is rendered outside the parent component’s DOM hierarchy, event bubbling (like click events) still works as if it was part of the tree.

Steps to Use React Portals

  1. Create a container element in the HTML where the portal will be rendered (e.g., in public/index.html):
<body>
  <div id="root"></div>
  <div id="modal-root"></div> <!-- Portal target -->
</body>
  1. Use ReactDOM.createPortal() to render the component outside the normal DOM hierarchy.

  2. Maintain the connection: Even though the portal component is rendered outside of the usual DOM tree, it’s still part of the React component tree, meaning:

    • The component’s state and props are maintained.

    • Event bubbling from the child components still works as expected.

Why Use Portals?

Portals are particularly useful for UI elements that need to break out of the current DOM structure for styling or interaction reasons. Here are common use cases:

  1. Modals and Dialogs: Often, you need a modal to be rendered at the top level of the DOM to ensure it's above all other content and unaffected by its parent’s layout, scrolling, or z-index.

  2. Tooltips and Popovers: Similar to modals, tooltips often need to escape overflow rules and appear over other elements.

  3. Global Notifications or Toasts: These often need to be displayed at the root level of the DOM for consistent positioning.

Key Points to Remember

  • Event Bubbling: Even though a portal renders outside the parent DOM hierarchy, event bubbling still works through the portal to the parent component. This allows things like event handling and context propagation to remain unaffected.

  • DOM Structure vs. Component Tree: While the DOM structure changes (by moving the portal’s rendered content outside of the usual DOM tree), the React component hierarchy remains unchanged.

  • Styling and Positioning: Portals can sometimes complicate CSS styling, since the rendered element is detached from its parent DOM node. You may need to use absolute positioning or other styles to ensure the element behaves as expected.

GitHub Code - useReducer Hook in React