2 - Fullstack Part2 - Forms
2 - Fullstack Part2 - Forms
b Forms
Let's continue expanding our application by allowing users to add new notes. You can find the code
for our current application here .
To get our page to update when new notes are added it's best to store the notes in the App
component's state. Let's import the useState function and use it to define a piece of state that gets
initialized with the initial notes array passed in the props.
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<Note key={note.id} note={note} />
)}
</ul>
</div>
)
}
https://fullstackopen.com/en/part2/forms 1/17
2/20/24, 2:33 PM Fullstack part2 | Forms
The component uses the useState function to initialize the piece of state stored in notes with
the array of notes passed in the props:
// ...
}
We can also use React Developer Tools to see that this really happens:
If we wanted to start with an empty list of notes, we would set the initial value as an empty array, and
since the props would not be used, we could omit the props parameter from the function
definition:
// ...
}
Let's stick with the initial value passed in the props for the time being.
Next, let's add an HTML form to the component that will be used for adding new notes.
https://fullstackopen.com/en/part2/forms 2/17
2/20/24, 2:33 PM Fullstack part2 | Forms
}
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<Note key={note.id} note={note} />
)}
</ul>
<form onSubmit={addNote}>
<input />
<button type="submit">save</button>
</form>
</div>
)
}
We have added the addNote function as an event handler to the form element that will be called
when the form is submitted, by clicking the submit button.
We use the method discussed in part 1 for defining our event handler:
The event parameter is the event that triggers the call to the event handler function:
The event handler immediately calls the event.preventDefault() method, which prevents the
default action of submitting a form. The default action would, among other things , cause the page to
reload.
The target in this case is the form that we have defined in our component.
https://fullstackopen.com/en/part2/forms 3/17
2/20/24, 2:33 PM Fullstack part2 | Forms
Controlled component
There are many ways to accomplish this; the first method we will take a look at is through the use of
so-called controlled components .
Let's add a new piece of state called newNote for storing the user-submitted input and let's set it
as the input element's value attribute:
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<Note key={note.id} note={note} />
)}
</ul>
<form onSubmit={addNote}>
<input value={newNote} />
<button type="submit">save</button>
</form>
</div>
)
}
The placeholder text stored as the initial value of the newNote state appears in the input element,
but the input text can't be edited. The console displays a warning that gives us a clue as to what
might be wrong:
Since we assigned a piece of the App component's state as the value attribute of the input element,
the App component now controls the behavior of the input element.
https://fullstackopen.com/en/part2/forms 4/17
2/20/24, 2:33 PM Fullstack part2 | Forms
To enable editing of the input element, we have to register an event handler that synchronizes the
changes made to the input with the component's state:
// ...
return (
<div>
<h1>Notes</h1>
<ul>
{notes.map(note =>
<Note key={note.id} note={note} />
)}
</ul>
<form onSubmit={addNote}>
<input
value={newNote}
onChange={handleNoteChange}
/>
<button type="submit">save</button>
</form>
</div>
)
}
We have now registered an event handler to the onChange attribute of the form's input element:
<input copy
value={newNote}
onChange={handleNoteChange}
/>
The event handler is called every time a change occurs in the input element. The event handler
function receives the event object as its event parameter:
https://fullstackopen.com/en/part2/forms 5/17
2/20/24, 2:33 PM Fullstack part2 | Forms
The target property of the event object now corresponds to the controlled input element, and
event.target.value refers to the input value of that element.
Note that we did not need to call the event.preventDefault() method like we did in the
onSubmit event handler. This is because no default action occurs on an input change, unlike a form
submission.
You can follow along in the console to see how the event handler is called:
You did remember to install React devtools , right? Good. You can directly view how the state
changes from the React Devtools tab:
Now the App component's newNote state reflects the current value of the input, which means that
we can complete the addNote function for creating new notes:
https://fullstackopen.com/en/part2/forms 6/17
2/20/24, 2:33 PM Fullstack part2 | Forms
setNotes(notes.concat(noteObject))
setNewNote('')
}
First, we create a new object for the note called noteObject that will receive its content from the
component's newNote state. The unique identifier id is generated based on the total number of
notes. This method works for our application since notes are never deleted. With the help of the
Math.random() function, our note has a 50% chance of being marked as important.
The new note is added to the list of notes using the concat array method, introduced in part 1 :
setNotes(notes.concat(noteObject)) copy
The method does not mutate the original notes array, but rather creates a new copy of the array
with the new item added to the end. This is important since we must never mutate state directly in
React!
The event handler also resets the value of the controlled input element by calling the setNewNote
function of the newNote state:
setNewNote('') copy
You can find the code for our current application in its entirety in the part2-2 branch of this GitHub
repository .
Let's add some new functionality to our application that allows us to only view the important notes.
Let's add a piece of state to the App component that keeps track of which notes should be displayed:
https://fullstackopen.com/en/part2/forms 7/17
2/20/24, 2:33 PM Fullstack part2 | Forms
// ...
}
Let's change the component so that it stores a list of all the notes to be displayed in the
notesToShow variable. The items on the list depend on the state of the component:
// ...
return (
<div>
<h1>Notes</h1>
<ul>
{notesToShow.map(note =>
<Note key={note.id} note={note} />
)}
</ul>
// ...
</div>
)
}
The definition uses the conditional operator also found in many other programming languages.
the result variable will be set to the value of val1 if condition is true. If condition is
false, the result variable will be set to the value of val2 .
https://fullstackopen.com/en/part2/forms 8/17
2/20/24, 2:33 PM Fullstack part2 | Forms
If the value of showAll is false, the notesToShow variable will be assigned to a list that only
contains notes that have the important property set to true. Filtering is done with the help of the
array filter method:
The comparison operator is redundant, since the value of note.important is either true or false,
which means that we can simply write:
We showed the comparison operator first to emphasize an important detail: in JavaScript val1 ==
val2 does not always work as expected. When performing comparisons, it's therefore safer to
exclusively use val1 === val2 . You can read more about the topic here .
You can test out the filtering functionality by changing the initial value of the showAll state.
Next, let's add functionality that enables users to toggle the showAll state of the application from
the user interface.
// ...
return (
<div>
<h1>Notes</h1>
<div>
<button onClick={() => setShowAll(!showAll)}>
show {showAll ? 'important' : 'all' }
</button>
</div>
<ul>
{notesToShow.map(note =>
<Note key={note.id} note={note} />
)}
</ul>
// ...
</div>
)
}
https://fullstackopen.com/en/part2/forms 9/17
2/20/24, 2:33 PM Fullstack part2 | Forms
The displayed notes (all versus important) are controlled with a button. The event handler for the
button is so simple that it has been defined directly in the attribute of the button element. The event
handler switches the value of showAll from true to false and vice versa:
The text of the button depends on the value of the showAll state:
You can find the code for our current application in its entirety in the part2-3 branch of this GitHub
repository .
Exercises 2.6.-2.10.
In the first exercise, we will start working on an application that will be further developed in the later
exercises. In related sets of exercises, it is sufficient to return the final version of your application.
You may also make a separate commit after you have finished each part of the exercise set, but doing
so is not required.
Let's create a simple phonebook. In this part, we will only be adding names to the phonebook.
You can use the code below as a starting point for the App component of your application:
return (
<div>
<h2>Phonebook</h2>
<form>
<div>
name: <input />
</div>
<div>
<button type="submit">add</button>
https://fullstackopen.com/en/part2/forms 10/17
2/20/24, 2:33 PM Fullstack part2 | Forms
</div>
</form>
<h2>Numbers</h2>
...
</div>
)
}
The newName state is meant for controlling the form input element.
Sometimes it can be useful to render state and other variables as text for debugging purposes. You
can temporarily add the following element to the rendered component:
It's also important to put what we learned in the debugging React applications chapter of part one
into good use. The React developer tools extension is incredibly useful for tracking changes that
occur in the application's state.
After finishing this exercise your application should look something like this:
Note the use of the React developer tools extension in the picture above!
NB:
you can use the person's name as a value of the key property
https://fullstackopen.com/en/part2/forms 11/17
2/20/24, 2:33 PM Fullstack part2 | Forms
2.7: The Phonebook Step 2
Prevent the user from being able to add names that already exist in the phonebook. JavaScript arrays
have numerous suitable methods for accomplishing this task. Keep in mind how object equality
works in Javascript.
Issue a warning with the alert command when such an action is attempted:
Hint: when you are forming strings that contain values from variables, it is recommended to use a
template string :
If the newName variable holds the value Arto Hellas, the template string expression returns the
string
The same could be done in a more Java-like fashion by using the plus operator:
Using template strings is the more idiomatic option and the sign of a true JavaScript professional.
Expand your application by allowing users to add phone numbers to the phone book. You will need to
add a second input element to the form (along with its own event handler):
<form> copy
<div>name: <input /></div>
<div>number: <input /></div>
https://fullstackopen.com/en/part2/forms 12/17
2/20/24, 2:33 PM Fullstack part2 | Forms
<div><button type="submit">add</button></div>
</form>
At this point, the application could look something like this. The image also displays the application's
state with the help of React developer tools :
Implement a search field that can be used to filter the list of people by name:
You can implement the search field as an input element that is placed outside the HTML form. The
filtering logic shown in the image is case insensitive, meaning that the search term arto also returns
results that contain Arto with an uppercase A.
NB: When you are working on new functionality, it's often useful to "hardcode" some dummy data
into your application, e.g.
https://fullstackopen.com/en/part2/forms 13/17
2/20/24, 2:33 PM Fullstack part2 | Forms
// ...
}
This saves you from having to manually input data into your application for testing out your new
functionality.
If you have implemented your application in a single component, refactor it by extracting suitable
parts into new components. Maintain the application's state and all event handlers in the App root
component.
It is sufficient to extract three components from the application. Good candidates for separate
components are, for example, the search filter, the form for adding new people to the phonebook, a
component that renders all people from the phonebook, and a component that renders a single
person's details.
The application's root component could look similar to this after the refactoring. The refactored root
component below only renders titles and lets the extracted components take care of the rest.
return (
<div>
<h2>Phonebook</h2>
<h3>Add a new</h3>
<PersonForm
...
/>
<h3>Numbers</h3>
https://fullstackopen.com/en/part2/forms 14/17
2/20/24, 2:33 PM Fullstack part2 | Forms
NB: You might run into problems in this exercise if you define your components "in the wrong place".
Now would be a good time to rehearse the chapter do not define a component in another
component from the last part.
Part 2a Part 2c
Previous part Next part
About course
Course contents
FAQ
Partners
Challenge
https://fullstackopen.com/en/part2/forms 15/17
2/20/24, 2:33 PM Fullstack part2 | Forms
https://fullstackopen.com/en/part2/forms 16/17
2/20/24, 2:33 PM Fullstack part2 | Forms
https://fullstackopen.com/en/part2/forms 17/17