Solutions
Let's start by getting rid of the hard-coded data and replacing it with some state.
const [quotes, setQuotes] = useState([]);
You'll see have a bit of an issue here. Using an empty array should work in JavaScript, but TypeScript doesn't have enought information to do it's job and it assigns quotes
to the following type:
const quotes: never[];
A similar problem with arrays
The code won't compile and we'll get the following error:
Property 'content' does not exist on type 'never'.
This kind of makes sense if we follow along with the rational that we've been establishing, right? Consider theses lines:
const [quote, setQuote] = useState();
const [quotes, useQuotes] = useState([]);
With the array, it has no idea what it could possibly contain. It just assumes that it'll be a forever empty array.
Then we move on to this line:
if (!quote) return <Loading />;
Okay, quote is falsy, which undefined
is, then show the <Loading />
component. Well, if quote
is undefined and we handled the case where quote
is undefined
, then as we move along in the code, quote
can't be undefined
anymore and we're out of options. So, TypeScript assigns it a special type: never
.
never
is the lowest common demoninator and certainly doesn't have a property like content
or source
on in—mostly, because it doesn't have anything.
Luckily, we know how to handle this.
const [quotes, setQuotes] = useState<Quote[]>([]);
We'll also add some state for the count. This doesn't require anything special on our part and should just work. Pick a number that makes you happy.
const [quotes, setQuotes] = useState<Quote[]>([]);
const [count, setCount] = useState(10);
Passing in the count and loading quotes
Now that we have a value that represents the number of quotes that we want to load, we can pass that into the <Quotes />
component.
<Quotes count={count}>/* … */</Quotes>
Changing the count
Our inclination would be to try something like this:
<Quotes count={count} onChange={(e) => setCount(+e.target.value)}>
And that's a good inclination, but we haven't done the requisite work on the types inside of that component just yet.
import { ChangeEventHandler, FormEventHandler, PropsWithChildren } from "react";
type QuotesProps = {
count: number;
onChange?: ChangeEventHandler<HTMLInputElement>;
onSubmit: FormEventHandler<HTMLFormElement>;
};
const Quotes = ({
children,
count,
onSubmit,
onChange,
}: PropsWithChildren<QuotesProps>) => {
// …
};
We also need to add the onSubmit
event handler before it will compile, but that seems like a totally reasonable request, right?
Submitting the form
Hmm, we clearly need to break out the ability to fetch posts out of the useEvent
hook.
useEffect(() => {
fetchQuotes(count).then(setQuotes);
}, [count]);
We might feel fancy and try to do something like this to get that initial count.
useEffect(() => fetchPosts(count), []);
Quick side note: You'll notice that the code will not compile if you omit the curly braces.
const fetchPosts = (count: number) =>
fetch(`/api/quotes?limit=${count}`)
.then((res) => res.json())
.then(({ quotes }) => setQuotes(quotes));
This won't work because useEffect
expects thst either nothing is returned or a function that it will call when the component it can call when it unmounts. We need to make sure that the function returns void
(e.g. undefined
) instead.
<Quotes
count={count}
onChange={(e) => setCount(+e.target.value)}
onSubmit={() => fetchPosts(count)}
></Quotes>