Exercise: Dog Facts
Let's pull everything together. We're going to wire up forms and events with some "asynchronous" data.
We'll start with this sandbox. Alternatively, you can use projects/dog-facts
in the project repository.
Your Mission
When the user hits submit on the form, we want to fetch however many facts about dogs as they requested.
Solution
Warning: Spoilers below. Proceed at your own risk.
import * as React from "react";
const Form = () => {
return (
<form
onSubmit={(event) => {
event.preventDefault();
}}
>
<div className="fact-input">
<label htmlFor="number-of-facts">Number of Dog Facts</label>
<input type="number" value="3" id="number-of-facts" />
</div>
<input type="submit" value="Fetch Dog Facts" />
</form>
);
};
const Fact = ({ fact }: { fact: string }) => {
return (
<article className="dog-fact">
<h3>Dog Fact</h3>
<p>{fact}</p>
</article>
);
};
const Application = () => {
return (
<main>
<Form />
<section></section>
</main>
);
};
export default Application;
Getting the Form Working
The form is going to need some state of its own. It needs to know the following:
const [value, setValue] = React.useState(1);
That can be plugged into the form.
<input
type="number"
value={value}
min="1"
max="10"
onChange={(event) => setValue(+event.target.value)}
id="number-of-facts"
/>
We'll also need a way to hold onto whatever facts that we fetch in state. In our Application
add the following:
const [facts, setFacts] = React.useState([]);
There are a few ways that we can handle fetching the dog facts from the API, but let's start with this approach in Application
.
const handleSubmit = (n: number) => {
fetchDogFacts(n).then((facts) => {
setFacts(facts);
});
};
We'll pass that handler into the Form
component.
<Form onSubmit={handleSubmit} />
This will, of course, make TypeScript upset. (In fairness, this is the first in a number of ways that we're going to make TypeScript upset, but let's go with it.)
Let's update the props for the Form
component to get TypeScript off of our back.
type FormProps = {
onSubmit: (n: number) => void;
};
const Form = ({ onSubmit }: FormProps) => {
// …
};
Then we'll call our handler when the form is submitted.
<form
onSubmit={(event) => {
event.preventDefault();
onSubmit(value);
}}
></form>
Next, let's render our facts in the Application
component.
{
facts.map((fact) => <Fact key={fact.id} fact={fact.fact} />);
}
TypeScript is not happy with us again. We've been blissfully ignoring the fact that it thinks that facts
is of the type never
.
We didn't give TypeScript enough information about what we were going to put in that array, so it didn't know what to do. It thought it should just be a perpectually empty array.
Let's give TypeScript a hint about what we're expecting.
const [facts, setFacts] = React.useState<DogFactType[]>([]);
Now it's happier. It knows what to expect from our API and can allow us to move forward safety. Like I keep saying, TypeScript is just trying to protect us from ourselves.
The end result looks something like this:
import * as React from "react";
import { fetchDogFacts, DogFactType } from "./dog-facts";
type FormProps = {
onSubmit: (n: number) => void;
};
const Form = ({ onSubmit }: FormProps) => {
const [value, setValue] = React.useState(1);
return (
<form
onSubmit={(event) => {
event.preventDefault();
onSubmit(value);
}}
>
<div className="fact-input">
<label htmlFor="number-of-facts">Number of Dog Facts</label>
<input
type="number"
value={value}
min="1"
max="10"
onChange={(event) => setValue(+event.target.value)}
id="number-of-facts"
/>
</div>
<input type="submit" value="Fetch Dog Facts" />
</form>
);
};
const Fact = ({ fact }: { fact: string }) => {
return (
<article className="dog-fact">
<h3>Dog Fact</h3>
<p>{fact}</p>
</article>
);
};
const Application = () => {
const [facts, setFacts] = React.useState<DogFactType[]>([]);
const handleSubmit = (n: number) => {
fetchDogFacts(n).then((facts) => {
setFacts(facts);
});
};
return (
<main>
<Form onSubmit={handleSubmit} />
<section>
{facts.map((fact, index) => (
<Fact key={index} fact={fact.fact} />
))}
</section>
</main>
);
};
export default Application;
A Quick Refactor
You might have decided to create a function to handle the form submission.
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
onSubmit(value);
};
Where Are We Now?
A completed version can be found in:
projects/dog-facts
on thedog-facts/complete
branchexamples17-dog-facts-complete
- CodeSandbox