Stop any project you currently have running; let's go back to the film application that you've started. You can run the app with npm start
.
Your goal today is to add some events to your app. You'll keep the events simple for now - each event will simply print a message to the console.
- In the future, however, you're going to be able to add films to your list of favorites, filter the films to see only your favorites, and see the details for a specific film. Your work today will make that possible.
Create a new component called Fave
that will eventually handle whether a movie is a user's favorite. The Fave
component's render
method should return the following:
<div className="film-row-fave add_to_queue">
<p className="material-icons">add_to_queue</p>
</div>
In the FilmRow
component, underneath the film-summary
div
, render the Fave
component.
In your browser, the icon should appear at the bottom right corner of each film row.
Inside the Fave
component, define a function called handleClick
. The function should accept an event (e
) as an argument. Simply log out a message like "handling Fave click!"
for now.
Since you aren't using this anywhere yet, nothing should change.
Now that you have a function that handles the user clicking a movie, connect it to the UI. In the div
of Fave
's render
function, add a parameter of onClick={this.handleClick}
.
In your browser's JavaScript console, you should see the message handleClick
prints out when the div
is clicked.
That's all! Your click is not yet adding favorites, but it is working. Later, you will modify your app so that when the Fave icon is clicked, your app adds or removes the selected movie from the user's favorites array.
Eventually, you'll want an "ALL" heading and a "FAVES" heading that are clickable links - when the user clicks "ALL", the left sidebar will show all movies; when the user clicks "FAVES", the left sidebar will show only their favorite movies. Now, you'll make the basis of that.
First set up the function that will determine what movies are shown in the list. You'll need to be able to tell if you are showing the user all of the movies or if you are filtering down to show the user just some of the movies.
In FilmListing
, create a handleFilterClick
function that takes a string filter
as an argument. For now, just print a message that says Setting filter to
and the filter
argument.
This new function isn't connected to a button in the UI yet, so nothing should change.
Add some markup to the FilmListing
component so that you can have something worth clicking. You'll keep the Films heading; underneath it, you'll add two categories of "ALL" and "FAVES". You're also setting up displaying the film's length, which you aren't using yet.
Change the FilmListing
component to render this:
<div className="film-list">
<h1 className="section-title">FILMS</h1>
<div className="film-list-filters">
<div className="film-list-filter">
ALL
<span className="section-count">{this.props.films.length}</span>
</div>
<div className="film-list-filter">
FAVES
<span className="section-count">0</span>
</div>
</div>
{allFilms}
</div>
If you check your browser, these subheadings should appear in the left column.
Now you have an ALL section and a FAVES section - you can hook that filtering function you just created up to it.
Add an onClick
inside FilmListing
so that when "FAVES" is clicked, it calls the handleFilterClick
method you created with the parameter 'faves'
.
Hint
This will look like this:onClick={() => this.handleFilterClick('faves')}
Try clicking FAVES - does it print to the console?
Now FAVES is clickable, so the next step is to make ALL clickable as well.
Add an onClick
inside FilmListing
so that when "ALL" is clicked, it calls the handleFilterClick
method with argument 'all'
.
Now, you should see a message in the console when you click either option. Later, instead of viewing a message, clicking either option will display the correct list of movies to the user - but now you've assured the options are clickable, which is an important first step.
You aren't yet going to be creating the large detailed view of the film that will be displayed on the right column, but you're going to start setting up for it.
Inside FilmRow
, define a function called handleDetailsClick
. The function should accept a film
as an argument. Print out Fetching details for
and the film title to the console.
Since this function isn't connected to the UI yet, nothing will change.
Now, connect handleDetailsClick
. Add an onClick
to FilmRow
so that your message gets printed whenever you click on a film row - don't forget to pass the argument.
You should be able to check this in your console by clicking any film row.
Hold up! Notice that you are now seeing two messages every time you click on the Fave icon/button. This is tricky, but the reason is because the event is propagating upward to the FilmRow
. To make it so only one message appears, you'll need to stop the event propagation.
To do this, inside the Fave
component's handleClick
function, add the line e.stopPropagation()
.
Try clicking the Fave icon/button - there's only one message now.
You have now triggered the events you'll need to update your app. Next, you'll add states to your app - data in your React app that will change (like if the user adds a film to the Faves list)
The first state you'll add will be whether a currently selected film is a user's favorite.
Add a constructor method to the Fave
component. Remember that any time you add a constructor to a class-based component, you have to call the super
method (and pass it props
).
By default, a film is not a user's favorite.
Back to the Fave
component, set this.state
to an object with the key isFave
and the value false
. This will set up the initial state of the component.
When the user clicks the Fave icon/button to add or remove a film from their favorites list, the app should change the film's isFave
state to reflect that.
Inside of the handleClick
method on the Fave
component, use this.setState
to toggle the value of isFave
. "Toggle" means you always want to set the new value to the opposite of the current value.
Hint - one way to do this could be:
isFave: !this.state.isFave
You now want the className
attribute on the div
to dynamically update when the state is changed. Currently, the className
on the div
is add_to_queue
. However, if the film is already favorited, then the film is already in the queue. Therefore, when isFave: true
, the className
should instead be remove_from_queue
.
You need to make this happen:
- When
isFave: true
, you want to give thediv
the classremove_from_queue
. WhenisFave: false
you want to give thediv
the classadd_to_queue
. - You also want to change the text that's rendered in the
p
to be the same text as the class -remove_from_queue
oradd_to_queue
.
Note: It will be easier to read if you determine which class to set first, store that value in a variable, then insert that variable into the className
attribute.
Hint - a more advanced and succinct way to write this function could be:
const isFave = (this.state.isFave) ? 'remove_from_queue' : 'add_to_queue'
You can drop this in the render
method. This checks the current isFave
state for true or false.
If it's true, it sets the const
variable isFave
to remove_from_queue
; when it's false, it sets the const
variable isFave
to add_to_queue
.
Once you have this, clicking the "Add" icon in each row should change the icon displayed.
Currently, you have the ALL and FAVE headings, but all films are always shown. Next, you'll add a state so that you can track if the user is currently filtering to view ALL movies or only their FAVE movies.
By default, a user will be viewing the entire list of movies.
In the FilmListing
component, set this.state
to an object with the key filter
and the value all
. This will set up the initial state of the component.
The handleFilterClick
method is the one that's called when a user clicks ALL or FAVES, so it's where you'll change the filter.
Inside of the handleFilterClick
method on the FilmListing
component, use this.setState
to set the value of filter
to the value passed to the event handler.
To give the user a clue as to where they are, the ALL and FAVES div
s should change color depending on which is active. In the CSS, we've already set the colors using a class; you'll need to dynamically change which class each div
has.
You now want the className
attribute on each .film-list-filter
div
to dynamically update when the state is changed. When filter: all
, you want to give the div
the class is-active
to the ALL
filter. When filter: faves
, you want to give the div
the class is-active
to the FAVES
.
Hint - one way to do this could be by adding a line similar to this (different for each div
) into the className
parameter:
This line in particular checks if the filter
state is currently "all"; if it is, it sets the value to is-active
. If it isn't, it does nothing. {this.state.filter === 'all' ? 'is-active' : ''}
Still stuck? The overall div
for "ALL" will look like this:
<div className={`film-list-filter ${this.state.filter === 'all' ? 'is-active' : ''}`} onClick={() => this.handleFilterClick('all')}>
(Don't forget to change it for the other div
).
Check in your browser that everything works.