How to Use the React useState Hook

Though we've been able to create a custom fetch() function to help us make the GraphQL requests to query listings or delete a certain listing, we've only seen the results of our requests in our browser console. In this lesson, we'll introduce and use React's useState Hook to track listings state value in our function component with which we'll be able to use and display in our UI.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

Table of Contents

This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL with a single-time purchase.

Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL
  • [00:00 - 00:14] The useState hook is a special hook that allows us to add React state to functional components. We're going to use the useState hook here to keep track of the listings data from our query as part of the component state.

    [00:15 - 00:31] We'll import the useState hook from the React library. Hooks should always be called at the top level of components, so we'll specify the useState hook at the top of the listings component, and specify two values it will return.

    [00:32 - 00:44] Listings and setListings. Since we don't have an idea of what the initial data of our state object is going to be, we'll initialize it with no.

    [00:45 - 00:55] The useState hook allows us to destructure two values. The first value is the state variable we want to create, in this case we're calling it listings.

    [00:56 - 01:03] The second value is a function used to update the state value. We're calling it setListings.

    [01:04 - 01:16] The only argument that the useState hook accepts is the initial value of the state variable being created. In this case we're initializing the listings variable with no.

    [01:17 - 01:27] We are destructuring the array useState gives us into two values. Keep in mind the useState hook is just a function that returns an array of two values.

    [01:28 - 01:50] What we've written is equivalent to doing useStateNo and then creating two separate variables by accessing the first and second index of the value being returned from the hook. Instead of writing this in three lines, we're able to group it into a single line with array destructuring, a feature introduced in ES6.

    [01:51 - 02:05] The useState hook often does a good job in inferring the type of the state data depending on the initial value provided. If we hover over listings at this moment in time we can see that it's recognized to be of type no.

    [02:06 - 02:27] The issue here is that oftentimes when we initialize our state values we often initialize them with either empty or null or undefined values. If we intend on using this value later in our component, TypeScripts will emit a warning if we attempt to access a property from it or map through it, since it assumes it is of type no, even if it might get populated later on.

    [02:28 - 02:43] This is why the useState hook accepts a type argument that allows us to specify the type of the value being created. In this case we want to specify the type of listings is the array of the listing type we've created in the types file.

    [02:44 - 03:05] So we'll go to that types file, export the listing interface, import it in our listings component file, and pass in an array of type listing as the type variable of our hook. We get an error here because we're assigning null to this particular type.

    [03:06 - 03:23] As a result we'll just use a union type here and basically say that the type of listings is either the array of listing or null. We want to update the listings state value the moment our query is complete.

    [03:24 - 03:39] So in our fetchListings function we'll remove the console log and use the set Listings function from our hook to update the listings state value. We'll pass in the listings property retrieved from data.

    [03:40 - 04:02] With the listings state value to be updated when the query is complete, we can now attempt to display the data we have when it's available in our UI. We know listings will be an array of objects, so let's use the map function to render a simple list item for every data object in listings when available.

    [04:03 - 04:20] We'll assign this list to a constant variable we'll call listings list. Right away our editor complains and rightfully so since listings might be null, and it is when the component actually gets rendered for the first time.

    [04:21 - 04:45] So as a result we should only map through the values when listings is available and we'll use a ternary statement to achieve this. Here we attempt to render a list of titles in which every list item will just be the title of that listing and we're using the listing ID as the unique identifier for each key for each rendered list item.

    [04:46 - 05:11] We'll wrap the entire map within an unordered list just to reference that the listings list is the single element that contains a series of list items. Let's place this listings list constant variable which is to contain a JSX element right after the title in our return statement.

    [05:12 - 05:26] When our UI is refreshed we can see that nothing's being shown because at the very beginning no listings data exists. When we actually click the button to query listings we're now presented with a list of listing titles.

    [05:27 - 05:53] Amazing. Since we're able to display a list of listings in our UI we can now try to attach the delete listing function to every render list item node and allow the user to select which listing they'd want to delete. We'll first declare that the delete listing function should accept an ID argument of type string and pass it as the ID variable for our server fetch function.

    [05:54 - 06:09] We'll also remove the console log message at the end of our function. Since we don't need data for now we'll remove the destruction portion as well and we'll just only call the server fetch function.

    [06:10 - 06:31] For every rendered list item we'll introduce a button that has a click event listener that'll trigger the delete listing function and pass in the ID of the list item node. We'll also remove the additional button that we had before geared to deleting a hard code listing.

    [06:32 - 06:46] When we now delete a listing our mutation should be successful and we'd expect our UI to update and show the change but that doesn't happen. We're only able to see the updated UI when we query the listings again.

    [06:47 - 07:10] This makes sense since when the mutation is complete our listings state value remains unchanged and it keeps its original state value. We could directly manipulate the listings data value after the mutation is complete but another maybe simpler approach is to simply refresh the query once again and have our listings state object be updated once the list is updated.

    [07:11 - 07:33] We can do this by simply calling the fetch listings function we already have right at the end of the delete listing function. Now when we delete a listing in our list the listings query is refetched and our UI is re-rendered to show the new state of our app.

    [07:34 - 07:46] Amazing. At any moment in time we're able to run the seed script in our server application if we need to introduce more mock listing objects into our listings collection.