Skip to content

ReactJS - Component API

ReactJS - Interacting with Components: State, Refs, and Re-renders

Section titled “ReactJS - Interacting with Components: State, Refs, and Re-renders”

This tutorial explores key React concepts for managing component data and behavior: updating state with the useState hook, understanding how React re-renders components, and accessing DOM elements directly using the useRef hook. We’ll focus on functional components, the modern standard in React development, which simplifies many aspects, including handling this context automatically.

The useState hook is the fundamental way to add state to functional components. It returns a pair: the current state value and a function to update it. Calling this update function will re-render the component and its children.

When updating state, especially arrays or objects, it’s crucial to treat state as immutable. This means you should create a new array or object rather than modifying the existing one directly. This helps React optimize updates and prevents subtle bugs.

import React, { useState } from 'react';
function App() {
// Initialize 'data' state as an empty array
const [data, setData] = useState([]);
// Handler to add a new item to the 'data' array
const updateStateHandler = () => {
const item = "Item added via useState!";
// Create a new array with the old items and the new item
setData(prevData => [...prevData, item]);
};
return (
<div>
<button onClick={updateStateHandler}>ADD ITEM TO STATE</button>
<h4>State Array:</h4>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default App;

We start with an empty array in data. Each time the button is clicked, updateStateHandler is called, which appends a new string to the data array using the setData function. The component then re-renders to display the updated list. If clicked three times, the list will show three items.

Triggering Re-renders (and forceUpdate considerations)

Section titled “Triggering Re-renders (and forceUpdate considerations)”

In modern React, components automatically re-render when their state or props change. Direct manual re-renders, like those invoked by forceUpdate() in class components, are generally discouraged as they often indicate a design issue or a misunderstanding of React’s reactive data flow.

If you find yourself needing to re-render a component without a direct state or prop change (e.g., when depending on an external, non-reactive data source, which is an anti-pattern), you might be tempted to look for a forceUpdate equivalent. A common workaround in functional components is to use a dummy state variable that you update to trigger a re-render. However, always try to model your component’s rendering based on its props and state first.

The original example used forceUpdate() to display a new random number. Here’s how you’d achieve a similar outcome using useState, which is the idiomatic React way:

import React, { useState } from 'react';
function App() {
const [randomNumber, setRandomNumber] = useState(Math.random());
const generateNewRandomNumber = () => {
setRandomNumber(Math.random()); // Update state, triggering a re-render
};
return (
<div>
<button onClick={generateNewRandomNumber}>NEW RANDOM NUMBER</button>
<h4>Random Number: {randomNumber}</h4>
</div>
);
}
export default App;

Each button click updates the randomNumber state, causing the component to re-render and display the new random number. This is React’s declarative way of handling UI updates.

While React abstracts away direct DOM manipulation, sometimes you need to interact with DOM elements directly (e.g., managing focus, triggering animations, or integrating with third-party DOM libraries). The useRef hook provides a way to access underlying DOM elements.

ReactDOM.findDOMNode() was used in older class components but is now discouraged and largely replaced by useRef. useRef returns a mutable ref object whose .current property is initialized to the passed argument (or null). When a ref attribute is passed to an element in JSX, React sets its .current property to the corresponding DOM element when the component mounts.

import React, { useRef } from 'react';
// For React 18, the root API is used for rendering
// import ReactDOM from 'react-dom/client';
// No direct DOM manipulation in this example, but refs can be used for it.
function App() {
const divRef = useRef(null); // Create a ref
const changeDivColor = () => {
if (divRef.current) {
// Accessing the DOM element directly
divRef.current.style.color = 'green';
divRef.current.textContent = 'NODE (Color Changed!)';
}
};
return (
<div>
<button onClick={changeDivColor}>CHANGE NODE COLOR</button>
{/* Attach the ref to the div element */}
<div ref={divRef} id="myDiv">NODE</div>
</div>
);
}
export default App;

When the button is clicked, changeDivColor directly manipulates the DOM node referenced by divRef.current to change its text color to green and update its content. While this is possible, remember that declarative updates via state are generally preferred for managing styles and content that change based on application logic.

In older class components, you often had to manually .bind(this) for event handlers or use arrow functions to ensure the correct this context. With functional components and Hooks, this is no longer a concern. Event handlers defined within functional components naturally capture the variables (including state setters from useState) from their lexical scope.

React has significantly evolved, with functional components and Hooks becoming the standard. This approach often leads to more concise, readable, and maintainable code. While class components are still supported, new development typically favors Hooks for state management, side effects, context, and refs.