FAQ: The State Hook - Lesson Review

This community-built FAQ covers the “Lesson Review” exercise from the lesson “The State Hook”.

Paths and Courses
This exercise can be found in the following Codecademy content:

Learn React

FAQs on the exercise Lesson Review

There are currently no frequently asked questions associated with this exercise – that’s where you come in! You can contribute to this section by offering your own questions, answers, or clarifications on this exercise. Ask or answer a question by clicking reply (reply) below.

If you’ve had an “aha” moment about the concepts, formatting, syntax, or anything else with this exercise, consider sharing those insights! Teaching others and answering their questions is one of the best ways to learn and stay sharp.

Join the Discussion. Help a fellow learner on their journey.

Ask or answer a question about this exercise by clicking reply (reply) below!
You can also find further discussion and get answers to your questions over in #get-help.

Agree with a comment or answer? Like (like) to up-vote the contribution!

Need broader help or resources? Head to #get-help and #community:tips-and-resources. If you are wanting feedback or inspiration for a project, check out #project.

Looking for motivation to keep learning? Join our wider discussions in #community

Learn more about how to use this guide.

Found a bug? Report it online, or post in #community:Codecademy-Bug-Reporting

Have a question about your account or billing? Reach out to our customer support team!

None of the above? Find out where to ask other questions here!

Hi,

That was a great lesson. I have a question. On line 10 of AppFunction.js (in handleChange handler), why should we use spread syntax like this:

setNewTask((prev) => ({ ...prev, id: Date.now(), [name]: value }));

instead of simply update the state based on user input:

setNewTask(() => ({ id: Date.now(), [name]: value }));

I’m asking this since I think we only need user input here and we are not acting based on previous state, aren’t we?

Thank you

4 Likes

Hi, @imangm.
The component NewTask has two input fields internally: one for title, and one for detail. If you were to omit ...prev , each time you were to switch to a different input field, your form will be reset. To prevent that from happening, you must use ...prev to preserve previously typed input.

2 Likes

actaully can you tell me how this works

handleChange({ target }){

const { name, value } = target;

this.setState((prevState) => ({

  ...prevState,

  newTask: {

    ...prevState.newTask,

    [name]: value,

    id: Date.now()

  }

}));

Hi, @dev6573490445.

First of all, check the contents of NewTask.js .(You can find it by clicking the files icon on the left side and navigating to files > Presentational > NewTask.js)
In that file, you’ll see (on line 10 and 18) that both the input tag and the textarea tag use the same event handler handleChange. So, a task has a Title field (which makes use of an input tag and is mandatory) and a Details field (which makes use of a textarea tag and is optional).

So, coming back to the code,
const { name, value } = target;
evaluates to:

const name = event.target.name;
const value = event.target.value;

Now, when the state object is set the first time(i.e when a user types into the title input field, say, “Hello”),
...prevState is empty.
Next is the newTask property which again has ...prevState.newTask . This will be empty for the same reason as mentioned above. Then,
[name]: value
will evaluate to
title:"Hello"
So, to recap, after the user types “Hello”, this is what the state looks like:

state: {
newTask:{
title:"Hello",
id:1604945933064
  }
}

Now, if the user decides to type in the description(Detail) for the task in the textarea field, the state will be updated as follows:
...prevState will copy the contents of the state as it is.
Within the property newTask, ...prevState.newTask will copy the contents of newTask from previous state and then add a new property
description:“description text”
to it and overwrite the id property. If you choose to skip ...prevState.newTask, the title typed by the user will be erased and the task will be reset (the description box is hidden every time the user input is blank)

So, in the end, after the user has typed the description, the state will finally look like:

state: {
newTask: { 
    title: "Hello",
    description:"description text",
    id:167636345411
},
allTasks:[]
}

Hope that helps you.

4 Likes

thank you so much, it took me long time to understand this.
why codecademy provided class components while the world is moving with hooks

I’m working on the Web Development path but didn’t quite understand React, so I figured I’d do the Learn React course for more in-depth understanding.

It’s very educational so far. But I don’t think I’m quite understanding hooks yet. I had little idea what to do for the last assignment of the lesson, to convert AppClass. Is there a good place on the web where I can practice/play around with this concept, to get more of a feel for it?
Or is this something that will develop as I continue with the Learn React course?

Hi I am finding the last exercise in The State Hook course very very difficult and don’t really understand a lot of the code. The previous exercises had pretty clear explanations of what was going on, but there are several lines of code in this exercise that previously have not appeared are not explained at all in the course. Would anyone be willing to walk me through what is going on in the code? Please help I’m very confused

Could you copy/paste the lines of code you find confusing?

const handleSubmit = (event) => {
  event.preventDefault();
  if (!newTask.title) return;
  setAllTasks((prevState) => [newTask, ...prevState];
  setNewTask({});
}

So basically most of this function is very confusing to me and it would be helpful to hear an explanation of whats going on in each line.

if (!newTask.title) return;
I don’t get what condition this if statement is checking for.

I also don’t get why:
setNewTask({});
is happening at the bottom of this function ?!

Kind of crazy that we’re expected to write this code without guidance for this last exercise.

event.preventDefault() I’ve never seen that in this course before and I don’t understand what that does! How am I supposed to figure out how to write these functions without looking at the solution?! Feeling pretty exasperated and frustrated after several attempts at this exercise and starting the course all the way over from the beginning trying to make sure that there wasn’t something I overlooked or missed.

also this function i’ve written:

const handleDelete = (taskIdToRemove) => {
  setAllTasks((prevState) => {
    return prev.filter((item,index) => index !==taskIdToRemove)
  });
};

deletes everything for some reason. it doesn’t delete just the task you click the x on it delete the input area and the header. whyyyyyyy???

Don’t let the exercise dishearten you. It was a difficult exercise considering there were no steps or checkpoints to follow. We had to translate a class component into an equivalent function component in one giant step. One misplaced line or bracket and the function component won’t work and identifying the mistake is not straightforward either. So, don’t feel bad if you had to compare your code to the solution code. That being said, I think you would be able to get most of the code right based on what we have been taught in the previous lessons. But figuring out everything in one single step is a tough task and one is bound to make many/some mistakes. As long as you are able to dissect and understand what you did right and what you did wrong, you should feel optimistic that you have made useful progress in your learning.

First, let us get the general gist of what is happening. In index.js, we are rendering the App component making use of the import statements. Those import statements lead us to either AppClass.js or AppFunction.js. Within those files, we render NewTask and TasksList components. So, if you want to figure out the basics, then you should inspect the basic building blocks. If you click on the folder icon in the top left corner of the middle window, it will bring up the directory. From there, you should navigate and click on Presentational/NewTask.js (and if you want AllTasks.js as well) and then close the directory tree.

Looking at NewTask.js, we get a better sense of the skeleton of our App. We can see that we have an html Form with an input field. Additionally, if the input field is not empty, then an optional description textarea as well as the submit button (with a value of “Add Task”) also become visible. You can read about the input element here. Specifically, the input element has been given a name of “title” and the textarea element has been given a name of “description”. Giving a name allows us to select a specific element via javascript or css or whatever. If we had multiple input elements in a single form and we didn’t want to target all of the input elements but only a specific input element, then the name attribute allows us to give the element a unique identifier. So, we have chosen a name of “title” for our input field. We have also set the attributes for placeholder, value and onChange (you can read about the attributes in the linked page above). We have set the value attribute to be value={newTask.title || ""}. This basically says that initially the value of the input field should be set to newTask.title (where is this coming from? more on that later!) and if that doesn’t exist, then the empty string "" should be the initial value. If you look at the parameters of the NewTask component, you will see that it expects to be passed an object which has properties called newTask, handleChange and handleSubmit. newTask is supposed to be an object while handleChange and handleSubmit are expected to hold references to event handling functions. It isn’t the job of NewTask to provide these details; it is the responsibility of the parent which is doing the rendering to provide the arguments for these parameters. Indeed, we can see in AppFunction.js how this pans out:

<NewTask
          newTask={newTask} 
          handleChange={handleChange} 
          handleSubmit={handleSubmit}
        />

The newTask object and the event handling functions are defined in the parent AppFunction.js and then they are passed as props to NewTask. The props are packaged as properties of a single object and then in NewTask.js, we use destructuring assignment (read more here: scroll to Object destructuring) to extract the values for our parameters.

In AppFunction.js, we are using useState to initialize newTask as an empty object. The interesting thing to note is that the handleChange function is being passed as the event handler to the NewTask component and in NewTask.js, this function is being used for both the input and the textarea elements. Now, initially newTask is an empty object. So, the newTask.title property is nonexistent. Hence the input field defaults to an empty string as specified in value={newTask.title || ""}.

When we type or click on an element in our page, an html event is generated. An object containing details of the event is created (read more here) and can be used by the event handling functions. In the handleChange function, we extract the name and value of the element on which the interaction has happened.

const handleChange = ({ target }) => {
    const { name, value } = target;
    setNewTask((prev) => ({
      ...prev,
      [name]: value,
      id: Date.now()
    }));
  };

The property key of an object can’t be a string, but we were taught in the earlier lesson “Objects in State” that we can convert a string into a key by the use of square brackets (read more here). Suppose we type in “abc” into the input field, then the element name “title” and value “abc” will be stored in the name and value variables. The [name]: value allows us to create a property title: "abc". Note the key is title and non “title” thanks to our square brackets approach. We also create a property called id which stores the time the current time. The …prev is necessary so that any other existing properties of newTask aren’t discarded. If we type in say “def” in the textarea, then the same handleChange function updates our newTask object to be something like {title: "abc", description: "def", id: \\theCurrentTime}.
I hope by now you understand the basic structure of what is happening as well as from where newTask.title is coming. Now, we can better understand the snippets posted by you.

Thank you so much for your reply and explanation - and apologies for sounding frustrated in my initial post. I get the general gist of everything and when I spend time reading the code can get what’s going on. It’s still beyond my skill level to be able to write these functions on my own … but hope to get there!

One other question I still have is why are these two lines of code needed in the handleSubmit function

event.preventDefault();
if (!newTask.title) return;

lines 14 and 15?

  • When we click the submit button “Add Task” of the form, an event happens. This is passed in as the argument to our handleSubmit function. If you click on a url, your browser takes to you to the new page. That is the default action of clicking a url. If we submit a form, then the default action is that the information contained in the form is sent from the client to server. But, we want to prevent this default action and customize how we want to handle the submission of form. This is what event.preventDefault(); does. You are correct we haven’t seen this earlier in the course, so we weren’t expected to know about this beforehand. But, this statement was present in the AppClass.js handleSubmit event handler, so we know that we should also include it in our AppFunction.js handleSubmit function.

  • The if (!newTask.title) return; statement says that if newTask either doesn’t have a title property OR it does have the property but its value is the empty string newTask = {..., title: "", ...}, then we should exit our function immediately because we don’t want to add tasks with no titles to our tasklist. Now, it may appear that this conditional is unnecessary because the submit button doesn’t appear until something has been typed into the input field. But, if you click on the input field and press enter/return on your keyboard, that also counts as submitting the form even though you didn’t click on the submit button. If you click on the input field and press enter/return without typing anything, you will see that nothing happens. This is because of our if statement and precisely what we want. Now comment out this statement and do the same experiment again. You will see that when you press enter/return on your keyboard, an empty task with no title is added to out tasks list. This is not what we want. We don’t want blank tasks in our list.

  • setAllTasks((prevState) => [newTask, ...prevState]; If indeed something is present in the input field and the submit button has been clicked, then we want to add this to our tasks list. allTasks is a list of objects where each object is a task that we added to our list. If we omit the …prevState, then our previously added tasks will be discarded. So, we take the previous list contained in prevState and create a new list in which the newTask object has been added and the previous tasks have also been preserved.

  • setNewTask({}); Once we have added a task to our list, we need to reset the newTask object. We do so with this statement. Suppose we didn’t do this reset. What would happen? When we click the submit button, our task will be added to the list as intended. That’s great! But whatever we typed in the input field and the description textarea will still remain visible on the screen. So, when we want to add a new task, we will have to edit/delete the values visible in the fields. It is much more desirable that once we have added a task, the input field is completely blank. So, we reset our newTask object to an empty object {}

You have the right idea, but you have made a couple of mistakes.

  • Firstly in your state setter, you have given the previous state the name prevState. That’s fine, but then you are applying the filter to something called prev. That’s not right. You need to be consistent. You should apply the filter as prevState.filter((

  • Also the condition index !== taskIdToRemove is problematic. First, you need to see what the parameter taskIdToRemove represents. If you look at the class component version, you will see that for every object in our array/list, we are checking if the object’s id property matches the taskIdToRemove. Recall allTasks is an array/list of objects with the structure [ {title: "abc", description: "def", id: //time task was added} , {title: "xyz", description: "dfgsd", id: //time task was added} , ...]. Two or more tasks can have the same title and description but the id is unique because each task has been added at a distinct time. In TasksList.js, you can see that the li elements have keys equal to these ids. When the delete button is clicked the call handleDelete(id) is made. That’s how taskIdToRemove receives its value.
    So, if you edit your code to

const handleDelete = (taskIdToRemove) => {
  setAllTasks((prevState) => {
    return prevState.filter((item) => item.id !==taskIdToRemove)
  });
};

it will work. I have removed index from your code because we don’t actually make use of the index. You can leave it in if you like.
If you find something confusing, share your thoughts.

1 Like

I’m not sure why setNewTask({}) that attempting set the input area empty doesn’t work when I develop locally.

What happens when you click on the “Add Task” button? Do you see the task being added to the tasks list?