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 divs 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 destructureFragmentfrom 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ādivs offer a good way to keep the code base organized.
- Import
ReactDOMfrom the react-dom module to access thecreatePortalmethod. Likewise, destructurecreatePortaldirectly 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
errorandonErrorproperties 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
createPortalrequires 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
useRefhook in the same fashion we importeduseState - Call
useRef()inside of the component, again likeuseState - Add
refprop 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
refwhen 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 refs:
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 refs 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 š