State and events, a solution

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>

You can see a solution here.