Table of Contents
Can you believe it? We’ve reached the end of our React app-building journey, and we’ve covered a lot of ground along the way.
From setting up our development environment to building reusable components, adding styles, managing state and handling events, and working with conditional logic, we’ve explored the fundamental concepts of React and built a fully functional web application š„²
In this final post of our series, we’re going to wrap things up by exploring some advanced React topics: fragments, portals, and refs.
These tools might seem complex at first glance, but once you understand their use cases, you’ll wonder how you ever lived without them.
Okay, that might be somewhat overstated, but theyāre super helpful š
Fragments allow us to group a list of children without adding extra nodes to the DOM.
Portals provide a way to render children into a different part of the DOM hierarchy, outside the current parent component.
And refs allow us to reference and interact with DOM elements directly, providing even more control over components.
So, get ready to dive into the world of fragments, portals, and refs, and discover how these tools can make your React code more robust and semantically sound.
Let’s end our React app-building journey on a high note and take our skills to the next level!
š« See full project in action and interact with it on this link.
React Fragments
JSX has limitations, one of them being that you canāt return, or store in a variable, more than one root JSX element.
There are two workarounds:
1) Wrap adjacent elements in a wrapper container div.
If youāve been following along, this is whatās currently going on in Form.js š
return (
<div className={styles.form_wrapper}>
{err && <Modal error={errMsg} onError={errHandler} />}
<form className={`${styles.form} ${errMsg && styles.form__err}`} onSubmit={formSubmitHandler}>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name"
value={name}
onChange={nameHandler}
/>
<label htmlFor="age">Age</label>
<input
type="number"
id="age"
name="age"
max="100"
min="1"
value={age}
onChange={ageHandler}
/>
<Button type="submit">Add User</Button>
</form>
</div>
);
ļ»æ
Weāre rendering two adjacent componentsāa modal and formāthat are wrapped in a container div
.
This method, however, can lead to what my instructor called a ādiv soupā due to the rendering of unnecessary elements.

Its impact is more pronounced in bigger, more nuanced applications.
2) Place adjacent components in an array since React knows how to work with an array of JSX elements.
In such a case, youāll need to provide key
attributes which is a rather cumbersome method.
Neither option is great, so what can we do?
A solution that goes beyond these workarounds is to create a wrapper component that returns props.children
. This is an empty component that only renders children.
Tip: Donāt confuse this with wrapper containers like the Card component. In that case, weāre rendering anything wrapped in specific tags!
This wrapper component gets rid of the rendered wrapper div
s in the DOM. The end result is just the modal (if thereās an error) and the form – no div
or other wrapper encasing them.
Hold on, it gets much better!
You donāt actually need to build such a component since it comes with React as a Fragment
š
There are two ways of implementing the rendering of adjacent components:
- If supported by the project set-up, you can use
<> ... </>
(aka ānamelessā tags) - Otherwise, import
React
(if you havenāt already done so) into the file and either use<React.Fragment>
or destructureFragment
from the react module and use<Fragment>
.
Now, letās put all this into action!
How to use React Fragments
In Form.js, import Fragment
and replace the wrapper div
. Remove the className
property because we no longer have a wrapper in this component.
Note the class on the div
simply added some padding which we are going to keep by transferring the style over to the Card wrapper on App.js.
// App.js
<Card className={styles.form_wrapper}>
<Form newUser={newUserDataHandler} />
</Card>
ļ»æ
Since we pre-defined the Card component to account for any instances of additional styles, weāre all set!
Hint: Copy and paste the associated style to App.module.css! Remove it from Form.module.css as itās no longer necessary on that component.
Fantastic! The app remains intact but boasts a cleaner base.

React Portals
A Portal renders HTML in a different location than its respective JSX placement.
The biggest advantage of Portals is to maintain the semantic HTML organization of a project.
For example, take into consideration components that are separate from, and sometimes lie on top, other components. A modal comes to mind here.
Our current project contains a nested modal that is problematic. Donāt get me wrong, it works, but itās a bad implementation with poor accessibility.
The modal with its overlay is supposed to lie on top of the root wrapper when it appears. Instead, itās nested inside the root wrapper, within App, and even further inside the Card wrapper along the rest of the form elements.

Though not too nested this time around, what if the modal was even deeper in the component tree?
See where Iām going šāāļø
How to use React Portals
A Portal takes two parameters specifying the what and where of the port.
To use a Portal in our project:
- Add a ārootā in index.js where project overlays can go in. I call this
div
āoverlay-rootā.
Tip š«
Adding a ārootā isnāt necessary as you can port to the body element directly, but ārootādiv
s offer a good way to keep the code base organized.
- Import
ReactDOM
from the react-dom module to access thecreatePortal
method. Likewise, destructurecreatePortal
directly from the module to skip the extra syntax.
The createPortal
method takes (a) the JSX component to port and (b) a pointer to the DOM element (using a Vanilla JS selector).
- The JSX component is the what weāre porting
- The pointer to the DOM element is the where to place the JSX component
Tip: Store JSX components in variables to make the code clean and reduce possible errors!
// Modal.js
import Button from "./UI/Button";
import Card from "./UI/Card";
import { createPortal } from "react-dom";
import styles from "./Modal.module.css";
const ModalOverlay = (props) => {
return (
<div className={styles.modal_overlay}>
<Card className={styles.modal_wrapper}>
<div className={styles.modal_header}>
<h3>Invalid input</h3>
</div>
<div className={styles.modal_body}>
<p>{props.error}</p>
</div>
<div className={styles.modal_action}>
<Button
type="button"
className={styles.modal_btn}
onClick={props.onError}
>
Close
</Button>
</div>
</Card>
</div>
);
};
const Modal = (props) => {
return createPortal(
<ModalOverlay error={props.error} onError={props.onError} />,
document.querySelector("#overlay-root")
);
};
export default Modal;
ļ»æ
Iām still exporting a Modal component, but itās now way different.
In fact, there are two components in the Modal file with ModalOverlay holding the main code of the modal.
Modal now acts as the porting agent that makes sure that anytime the Modal component is used anywhere in the project, the ModalOverlay component will render inside of the root div
.
Note: I pass the
error
andonError
properties to ModalOverlay when porting in order to access the props values on the component properly!
To sum things up:
- Store the modal code in a secondary component. We canāt simply store it in a variable because
createPortal
requires a JSX variable. - Port the secondary component with the necessary properties to the desired DOM location. Said location must exist in index.html (if not, like in our case, create it).

React Refs
Refs allow us to access DOM elements so we can work with them by creating a connection between a JSX element and the JS code.
Breaking down the use of refs in three steps:
- Import the
useRef
hook in the same fashion we importeduseState
- Call
useRef()
inside of the component, again likeuseState
- Add
ref
prop with a pointer to the constant storing the hook initiation
Much like its name implies, a ref
creates a reference to a DOM element.
In fact, it returns a DOM element as an object with a current
property whose value (aka the ref
) is a DOM node.
By using the value
property of the return object, you can read data without logging every keystroke (as is the case, so far, with useState
).
In this way, a ref
enables us to get rid of states. Thereās, also, no longer a need to bind by feeding a state back into the input.
Since weāre no longer managing state, a ref
component becomes uncontrolled. Its state no longer depends on React because weāre directly manipulating the value of the DOM element.
Tip: Avoid directly manipulating the DOM as its Reactās job! Make use of
ref
when reading values such as those of inputs in forms.
As an example, letās read the values of the form inputs using ref
instead of state.
Form.js
Start by importing the useRef
hook: import { useState, Fragment, useRef } from "react"
Replace state initiations and handlers with ref
s:
const Form = (props) => {
// let [name, setName] = useState("");
// let [age, setAge] = useState("");
let nameRef = useRef(),
ageRef = useRef();
let [err, setErr] = useState(false);
let [errMsg, setErrMsg] = useState();
// const nameHandler = (e) => {
// setName(e.target.value);
// };
// const ageHandler = (e) => {
// setAge(e.target.value);
// };
...
ļ»æ
At this point, Iām initializing ref
s for the name and age inputs respectively.
Follow by creating a connection between the initialized ref
and the DOM element:
...
<input
type="text"
id="name"
name="name"
// value={name}
// onChange={nameHandler}
ref={nameRef}
/>
<label htmlFor="age">Age</label>
<input
type="number"
id="age"
name="age"
max="100"
min="1"
// value={age}
// onChange={ageHandler}
ref={ageRef}
/>
...
ļ»æ
Notice how we no longer need the value
and onChange
properties for the inputs.
Now, when submitting the form, access the values of the inputs and check if thereās an error.
const formSubmitHandler = (e) => {
e.preventDefault();
let name = nameRef.current.value,
age = ageRef.current.value;
// Check if input has error
if (name.length === 0 || age.length === 0) {
setErr(true);
setErrMsg("Please enter a valid name and age (between 1-100).");
return;
} else {
setErrMsg();
}
...
ļ»æ
By storing the ref values in the name
and age
variables, I avoid having to replace the rest of the error logic.
However, when resetting the values of the inputs after forwarding data to the parent, you canāt simply set name
and age
to empty strings.
This action will not work and youāll get an error!
Instead, to reset the input values using refs, directly set the ref
values to empty strings.
...
// Reset input values
// setName("");
// setAge("");
nameRef.current.value = '';
ageRef.current.value = '';
};
ļ»æ
See how everything is working wonderfully?
And, yes, I absolutely took the liberty of making myself young again š
Grab the complete project source code on GitHub.
āTill the next one, be cool š