React JS - Reducers And Context API
Table of contents
useReducer
what is useReducer
useReducer
is like a "state manager" that breaks down the state-changing process into three main parts:
State: This is the current state (or the current values/data in your app).
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.
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:
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.
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.
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 multipleuseState
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
Create the Context: You use
React.createContext()
to create a new context.Provide the Context: You use the
Provider
component to make the context available to child components.Consume the Context: You access the context data using either the
useContext
hook (for function components) or theConsumer
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
ordark
) and a function to change the theme (setTheme
).All child components of the
ThemeContext.Provider
, includingToolbar
, can now access thetheme
andsetTheme
.
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 thetheme
andsetTheme
values from theThemeContext
.The button toggles the theme between
light
anddark
.
Key Concepts
Provider: Passes data down to any components that need it.
Consumer or useContext: Allows components to subscribe to context changes.
No Prop Drilling: Context helps you avoid prop drilling, where props need to be passed down through multiple levels of components.
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
orz-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:
- 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 theid="modal-root"
. This is an element outside the typical DOM structure, like under the body tag.
- 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
- 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>
Use
ReactDOM.createPortal()
to render the component outside the normal DOM hierarchy.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:
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
.Tooltips and Popovers: Similar to modals, tooltips often need to escape overflow rules and appear over other elements.
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
Subscribe to my newsletter
Read articles from Vitthal Korvan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Vitthal Korvan
Vitthal Korvan
🚀 Hello, World! I'm Vitthal Korvan 🚀 As a passionate front-end web developer, I transform digital landscapes into captivating experiences. you'll find me exploring the intersection of technology and art, sipping on a cup of coffee, or contributing to the open-source community. Life is an adventure, and I bring that spirit to everything I do.