COMPLETE ADVANCED REACT
UNCONTROLLED FORMS
INTRO TO REACT
Uncontrolled Forms
We are now going to do a contact page for our
site. As part of that, we will accept a name,
email, and message from our users and
submit it our backend. Like all good backends,
ours will log it to the console and then ignore
it.
We are going to see two new concepts here:
how to do a post with TanStack Query (which
they call a mutation) and how to do
uncontrolled forms with React. Let's start with
our API query. In src/api, create a file called
postContact.js and put this in there.
export default async function postConta
const response = await fetch("/api/co
method: "POST",
headers: {
"Content-Type": "application/json
},
body: JSON.stringify({ name, email,
});
if (!response.ok) {
throw new Error("Network response w
}
return response.json();
}
Create a new link to our page in index.lazy.jsx
// after past orders
<li>
<Link to="/contact">Contact</Link>
</li>
Create a new route called
contact.lazy.jsx in src/routes
import { createLazyFileRoute } from "@t
import { useMutation } from "@tanstack/
import postContact from "../api/postCon
export const Route = createLazyFileRout
component: ContactRoute,
});
function ContactRoute() {
const mutation = useMutation({
mutationFn: function (e) {
e.preventDefault();
const formData = new FormData(e.t
return postContact(
formData.get("name"),
formData.get("email"),
formData.get("message")
);
},
});
return (
<div className="contact">
<h2>Contact</h2>
{mutation.isSuccess ? (
<h3>Submitted!</h3>
) : (
<form onSubmit={mutation.mutate
<input name="name" placeholde
<input type="email" name="ema
<textarea placeholder="Messag
<button>Submit</button>
</form>
)}
</div>
);
}
We are using a mutation from TanStack
React Query. It works very similar to
normal gets except this one sends
information to the API instead of gets it.
Notice we're using isSuccess to show
or hide the form. Once the mutation has
successfully been submitted, we just
want to show them that and then hide
the form.
Notice we're not using state or useState
at all here. We're using an "uncontrolled"
form which just means we're letting the
DOM manage the state. Only on a submit
event are we reading from the DOM
using the FormData API. We use that to
submit to our API the contact data.
There's also isError and isIdle, we just
didn't use them from TanStack Query.
Also notice in the dev tools for TanStack
Query the mutations (it's in another tab.)
Try it! You'll notice in the logs of where-ever
your API is running that it logs out the contact
info.
Click here to see the state of the
project up until now: 13-uncontrolled-
forms
← Previous Next →
Content
Licensed
Under
CC-BY-
NC-4.0
Code
Samples
and
Exercises
Licensed
Under
Apache
2.0
Site
Designed
by Alex
Danielson
COMPLETE INTRO ADVANCED REACT
ERROR BOUNDARIES
TO REACT
Error Boundaries
Frequently there are errors with APIs with
malformed or otherwise weird data. Let's be
defensive about this because we still want to
use this API but we can't control when we get
errors. We're going to use a feature called
componentDidCatch to handle this. This is
something you can't do with hooks so if you
needed this sort of functionality you'd have to
use a class component.
This will also catch 404s on our API if someone
give it an invalid ID!
A component can only catch errors in its
children, so that's important to keep in mind. It
cannot catch its own errors. Let's go make a
wrapper to use on PastOrdersRoute. Make a
new file called ErrorBoundary.jsx
This is also the first time I'm going to be
showing you what was the old way of writing
components, called class components. All
React code used to be written this way but
while it's not deprecated it is explicitly not
recommended by the React team. Previous to
this version of the Complete Intro to React I
still taught class components as part of the
course. In the past year, I kept track and I
wrote class components zero times. And as
such it is time to retire teaching them as part
of an intro course. However, with error
boundaries it is the only way to write them, so
we will do a brief intro to them so we can learn
them. In reality, I would likely just use react-
error-boundary and never have to write a class
component at all!
A class component is really similar to a
function component (which is what we have
been writing) but have a few key di!erences.
You cannot use any hooks with class
components (useState, useE!ect, etc.)
Every class component has a
render() function. It works mostly the
same as a function component.
Instead of the useState hook you'll use
this.state which is an object that
contains all state for that component.
To change state, you'll use a
this.setState({ key: "value"
}) function. This allows you to change
state like your setKey hook would
have.
Instead of useE!ect, you use lifecycle
methods. componentDidMount,
componentDidUpdate,
componentWillUnmount, etc. To read
more about them, go here.
Instead of a props parameter being
passed into the function, you'll use
this.props .
This is enough here. You shouldn't be
authoring class components anymore
except for error boundaries. But you may
encounter them in legacy React
codebases.
import { Component } from "react";
import { Link } from "@tanstack/react-r
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error("ErrorBoundary caught
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary"
<h2>Uh oh!</h2>
<p>
There was an error with thi
to back to the home page.
</p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Now anything that is a child of this
component will have errors caught here.
Think of this like a catch block from
try/catch.
A static method is one that can be called
on the constructor. You'd call this
method like this:
ErrorBoundary.getDerivedStateFromEr
This method must be static.
If you want to call an error logging
service, componentDidCatch would
be an amazing place to do that. I can
recommend Sentry and TrackJS.
this.props.children is any child
component inside of the component. e.g.
if someone renders <MyComponent>
<h1>hi</h1></MyComponent> , the
<h1>hi</h1> is consider the the
"children". This works in function
components too.
Because we just return children if there's
no error, the ErrorBoundary component
doesn't render anything itself if there are
no errors.
Let's go make PastOrderRoute use it in case
the API throws an error.
// add import
import ErrorBoundary from "../ErrorBoun
// replace Route export
export const Route = createLazyFileRout
component: ErrorBoundaryWrappedPastOr
});
// beneath Route export
function ErrorBoundaryWrappedPastOrderR
return (
<ErrorBoundary>
<PastOrdersRoute />
</ErrorBoundary>
);
}
Now this is totally self contained. No one
rendering PastOrderRoute has to know
that it has its own error boundary. I'll let
you decide if you like this pattern or if
you would have preferred doing this in
App.js at the Router level. Di!ering
opinions exist.
We totally could have made
ErrorBoundary a bit more flexible and
made it able to accept a component to
COMPLETE INTRO TO ADVANCED REACT
PORTALS
REACT
Portals
What if you are rendering a page and you want
to render something in another part of the
page at the same time? Think like you have a
contextual right navigation and you have a
page that wants to render into the contextual
nav. We could use something like context to
do that and that would be totally acceptable.
But there's another cool way called a portal to
do this as well. It even lets us render outside
our app totally, like we're about to do.
We're going to do a modal or a "popover". I
think these are bad user experiences but it's a
perfect use case for a portal so we're going to
do it! In our case, because we want the modal
to render in front of everything, we need the
div that the modal renders into to be first in
the DOM. So let's go make a div that does that.
Open your index.html file and put this in there.
// above #root
<div id="modal"></div>
By default this will have nothing in it, but once
we render our modal it render inside this div
instead of root. Make a new file in src called
Modal.jsx
// basically stolen from the React docs
import { useEffect, useRef } from "reac
import { createPortal } from "react-dom
const Modal = ({ children }) => {
const elRef = useRef(null);
if (!elRef.current) {
elRef.current = document.createElem
}
useEffect(() => {
const modalRoot = document.getEleme
modalRoot.appendChild(elRef.current
return () => modalRoot.removeChild(
}, []);
return createPortal(<div>{children}</
};
export default Modal;
This uses a ref. A ref is a reference to
something that need to be exactly the
same between renders. A hook would get
regenerated / recreated so we need a ref
because it'll create a div and then it hand
back the same div every render. It's
important that it's the same div because
it'll be the one we use to render the
portal.
We're using createPortal to render into
this new modal div we're chosen for our
modal. But this could be a contextual nav
or any other DOM div we want.
We use the returned function on the
e!ect to clean up when this Modal is
unmounted from the DOM. Otherwise
we'd leak memory.
Before we use our Modal, we'll need to make
another API call function get a past order. In
our example here, we're going to make it so
you can click on one of the rows in our past
orders page to see what was in the order. In
src/api , add a file called
getPastOrder.js (no s ) and add this.
export default async function getPastOr
const response = await fetch(`/api/pa
const data = await response.json();
return data;
}
Then render the modal if we have a
focusedOrder :
// import at top
import getPastOrder from "../api/getPas
import Modal from "../Modal";
// NOTE: In the course, Brian makes thi
const intl = new Intl.NumberFormat("en-
style: "currency",
currency: "USD",
});
// top of the render function
const [focusedOrder, setFocusedOrder] =
const { isLoading: isLoadingPastOrder,
queryKey: ["past-order", focusedOrder
queryFn: () => getPastOrder(focusedOr
enabled: !!focusedOrder,
staleTime: 24 * 60 * 60 * 1000, // on
});
// last thing before closing div
{
focusedOrder ? (
<Modal>
<h2>Order #{focusedOrder}</h2>
{!isLoadingPastOrder ? (
<table>
<thead>
<tr>
<td>Image</td>
<td>Name</td>
<td>Size</td>
<td>Quantity</td>
<td>Price</td>
<td>Total</td>
</tr>
</thead>
<tbody>
{pastOrderData.orderItems.m
<tr key={`${pizza.pizzaTy
<td>
<img src={pizza.image
</td>
<td>{pizza.name}</td>
<td>{pizza.size}</td>
<td>{pizza.quantity}</t
<td>{intl.format(pizza.
<td>{intl.format(pizza.
</tr>
))}
</tbody>
</table>
) : (
<p>Loading …</p>
)}
<button onClick={() => setFocused
</Modal>
) : null
}
We're using React Query again here.
We're using the enabled key to only
make this request when the user has set
a focusedOrder. If there's no
focusedOrder, then it won't make the
request. !! is the same as "not not". It
makes a number like 5 be true and
a number like 0 or undefined be
false .
We set the staleTime to day. These
orders shouldn't change very frequently.
If there's no focusedOrder, we don't
render the Modal, which means the
portal is unused.
If there is a focusedOrder, we render the
modal and show a loading indicator that
we're loading the rest of the info.
When a user clicks Close, we set the
focusedOrder to be undefined again
which causes the Modal to unrender.
Finally, we need a way to open the modal.
Open past.lazy.jsx . Make the
order.order_id value a button and add
an onClick event so clicking it will open the
Modal:
<td>
<button onClick={() => setFocusedOrde
{order.order_id}
</button>
</td>
<td>{order.date}</td>
<td>{order.time}</td>
That's it!
Click here to see the state of the
project up until now: 11-modals
← Previous Next →
Content
Licensed
Under
CC-BY-
NC-4.0
Code
Samples
and
Exercises
Licensed
Under
Apache
2.0
Site
Designed
by Alex
Danielson