October 4, 2020 ~ 8 min read

Learn React Hooks and find something to eat - Recipe App


React Hooks tutorial to get you started

In this tutorial, we will learn the basics of React hooks by building a recipe finder application.

The premise is straightforward, a user can type an ingredient and get 10 recipes that use the ingredient. Nice and simple.

What’s a hook?

If you are rolling your eyes 🙄, skip to the next section! 😛

According to the official docs:

A Hook is a special function that lets you “hook into” React features.

So in this post, we will learn useState, useEffect and how to create our own custom hooks. We will also cover how to fetch data from an API and some HTML-form management using hooks.

But as for now, let me hook you up with some new React features.

What will you need?

Maybe a tiny bit of JavaScript and some React knowledge about props, state and event handlers.

If you are completely new to React, that’s no problem, I can wait for 5 minutes while you’re reading this great 5-minute intro to React.

The setup

Not a huge fan of complicated setups. There are as many ways to set up React apps as there are React developers, so if you have a favourite setup, feel free to use it.

For this tutorial, here’s an HTML file which imports React and ReactDOM libraries via CDN using <script> tags (see this gist for an example).

Alternatively, you can also experiment with the code in this React Scrimba playground

Our Hooks Application

We are going to build a very simplified recipe finding app that we can use to start learning hooks. It will consist of a form with an input field and a submit button. We will fetch some recipes over Recipe Puppy API and display the results in an unordered list.

Find dinner ideas with Recipe Puppy API

To get some flavoursome ideas and to find something tasty, we will use Recipe Puppy API. Inside the <script> tag we have provided getData() helper function to fetch the recipes from the API.

For this tutorial, it’s best to think of it, like a slightly improved fetch() function and we will use it in the same way.

We didn’t really want to distract you from learning hooks, so we created this helper function do all the heavy lifting.

Read from an input field with useState hook

Let’s create a barebones layout. So far, an emoji for fun and a console.log as a tradition. Nothing overly complicated.

function App() {
    return (
        <div className="App">
            <h1>Amazing Recipes</h1>
            <input placeholder="Favourite food" onChange={(e) => console.log(e.target.value)} value="" />
            <button type="submit">
                <span>Find something tasty</span>
                <span role="img" aria-label="avocado">
                    🥑
                </span>
            </button>
        </div>
    );
}

Now we would like to store the input value. If it were a class component, we would store data in this.state. Well, with hooks, we simply useState().

useState accepts initial state and always returns a pair of values: the current state, and a function that updates it.

We can access the returned pair using array destructuring in the very beginning of our function body, like so:

function App() {
    const [ingredients, setIngredients] = React.useState('');

    return (
        <div className="App">
            <h1>Amazing Recipes</h1>
            <input placeholder="type ingredients here" onChange={(e) => console.log(e.target.value)} value="" />
            <button type="submit">
                <span>Find something tasty</span>
                <span role="img" aria-label="avocado">
                    🥑
                </span>
            </button>
        </div>
    );
}

In the snippet above, ingredients is initial value, we can use it as a value to display to our users.

setIngredients is a state update function for ingredients and can be added to events, in our case that’s onChange.

We pass an empty string "" as the initial value to useState(""), as if we wanted to simply say ingredients = ""

function App() {
    const [ingredients, setIngredients] = React.useState('');

    return (
        <div className="App">
            <h1>Amazing Recipes</h1>
            <input
                placeholder="type ingredients here"
                onChange={(e) => setIngredients(e.target.value)}
                value={ingredients}
            />
            <button type="submit">
                <span>Find something tasty</span>
                <span role="img" aria-label="avocado">
                    🥑
                </span>
            </button>
        </div>
    );
}

So on the first render of the app, it would look as if nothing changed.

But if we type something into the input field we can see that our input gets updated as we type.

Fetch data from an API with useEffect

useEffect hook tells React that the component needs to do something after render. In our case, we want to get some recipes. To call the API, we will call getData() helper function and for now, we will pass an empty string "" to it.

We will also use another useState hook, to store our recipes.

    const [ingredients, setIngredients] = React.useState("");
    const [recipes, setRecipes] = React.useState([]);

    React.useEffect(async () => {
      const results = await getData("");
      setRecipes(results);
    }, []); *// <-- what's that? More on [] below*

    return(
        //...same JSX...
    );

Oops, we get a warning.

Luckily, the warning contains the solution and a helpful link to learn more.

useEffect(() => {
    const fetchRecipes = async () => {
        const results = await getData('');
        setRecipes(json.results);
    };
    fetchRecipes();
}, []);

You might have noticed an empty array [] as a second argument to useEffect. Why do we use it? useEffect runs after every render. If we pass some value into the array, we will ask useEffect to check if the value changed and apply the effect only if that value changed. We will do so when we pass [] we effectively say ‘Run useEffect on every render.’

Now, with the error gone, we can render out the recipes.

return (
    <div className="App">
        <h1>Amazing Recipes</h1>
        <input
            placeholder="type ingredients here"
            onChange={(e) => setIngredients(e.target.value)}
            value={ingredients}
        />
        <button type="submit">
            <span>Find something tasty</span>
            <span role="img" aria-label="avocado">
                🥑
            </span>
        </button>
        <ul>
            {recipes.map((recipe) => (
                <li key={recipe.title}>
                    <img alt={recipe.title} src={recipe.thumbnail} />
                    <a href={recipe.href} target="_blank" rel="noopener noreferrer">
                        {recipe.title}
                    </a>
                </li>
            ))}
        </ul>
    </div>
);

// more on target="_blank" rel="noopener noreferrer"
// can be found here: [https://mathiasbynens.github.io/rel-noopener/](https://mathiasbynens.github.io/rel-noopener/)

We can use a ternary expression to render a default image if there is no thumbnail image provided by the API.

<ul>
    {recipes.map((recipe) => (
        <li key={recipe.title}>
            {recipe.thumbnail ? (
                <img alt={recipe.title} src={recipe.thumbnail} />
            ) : (
                <img alt="default-meal" src="[http://i65.tinypic.com/maateu.png](http://i65.tinypic.com/maateu.png)" />
            )}

            <a href={recipe.href} target="_blank" rel="noopener noreferrer">
                <span>{recipe.title}</span>
            </a>
        </li>
    ))}
</ul>

Trigger a hook manually to fetch data

A good way of triggering a manual fetch would be with a form element. A form also makes it possible to trigger the button with “Enter” on the keyboard, which is a nice bonus.

Let’s write doFetch(). It will receive search parameters that getData() requires to call RecipePuppy API.

    const [ingredients, setIngredients] = React.useState("");
    const [recipes, setRecipes] = React.useState([]);
    const [search, setSearch] = React.useState("");

    useEffect(() => {
        const results = await getData(search);
        setRecipes(json.results);
      };
      fetchRecipes();
    }, [search]);

    const doFetch = query => {
      setSearch(query);
    };

Let’s now wrap our input and button in <form> and pass to onSubmit() event our doFetch() function, passing ingredients to it.

<form
    onSubmit={(e) => {
        doFetch(ingredients);
        // To prevent browser reloads when clicking the submit button
        e.preventDefault();
    }}
>
    <input placeholder="type ingredients here" onChange={(e) => setIngredients(e.target.value)} value={ingredients} />
    <button type="submit">Find something tasty</button>
</form>

Great, now it all works!

That’s the app done and let’s have a small refactor.

Create a custom hook

We can create our own hooks, by combining hooks that React gives us.

Let’s create our own hook by extracting search and recipes state hooks and doFetch(). We can also specify what a custom hook returns, by returning an object with variables and functions.

const useRecipePuppyApi = () => {
    const [recipes, setRecipes] = React.useState([]);
    const [search, setSearch] = React.useState('');

    useEffect(() => {
        const fetchRecipes = async () => {
            const results = await getData(search);
            setRecipes(json.results);
        };
        fetchRecipes();
    }, [search]);

    const doFetch = (query) => {
        setSearch(query);
    };

    return { recipes, doFetch };
};

Inside of our App component we do not need to change any JSX, as all that code needs are just recipes and doFetch.

    const useRecipePuppyApi = () => {
      // ...custom hook logic...
    };

    function App() {
      const [ingredients, setIngredients] = React.useState("");
      const { recipes, doFetch } = useRecipePuppyApi();

    return (
       // ...JSX is the same...
      );
    }

Now, this component is so nice and simple to read. It’s two hooks and JSX.

Congratulations. You now know the very fundamental hooks and even more importantly you also know how to create your own!

Full code

Github gist with ALL the code

Continue learning React

I hope you’re hooked (yep, of course, there must be a pun), and you want to learn more, be sure to check out free React course on Scrimba. as I learnt most of this from there.


Maxi Ferreira

Hey! Thanks for reading! Follow me on Twitter @alanmynah to let me know what you thought, keep in touch or ask questions. Would be super happy to have a chat!