- Check out the What Is React Markdown for an introduction to the library
- This tutorial will strictly cover React.js concepts but there is another markdown that summarizes the other technologies in this app.
- Please review the Companion Technologies Markdown
- This tutorial is also written in
ES6
/ES2015
. If you're not familiar with the differences in syntax please review the ES6 Syntax Markdown
- In this tutorial we'll be building a web app that will search for movies using the OMDB API.
- The user will search for a movie by a title.
- All movies that relate to that title will be returned in JSON format.
- The list of movies will be rendered on the page using React.
- If a user clicks on a specific movie they will be shown the details of that movie.
- There is an extension to this tutorial that will focus on expanding this app to include the react-router.
- You can find this tutorial here https://github.com/Jingo88/Just_Another_React_Tutorial_Extended
- Let's start by seeing what the final product will look like.
- Clone this repo
- Change directory into the project
Just_Another_React_Tutorial
and run the following command in your terminal
npm install
- This command is a node command that will download all the node modules listed in the
package.json
file - If you don't have npm then... you should probably download npm
- Now let's run the server
npm start
- We only have to type
npm start
because in ourpackage.json
file, the start keyword is hooked up to running thewebpack-dev-server
- Now open up your browser and visit
localhost:8080
- This is our end goal. Or as far as this tutorial will take you. Hopefully you'll learn/understand the concepts in this tutorial enough to build your own apps and incorporate newer concepts. I'll suggest some later on.
- Now we're going to start from scratch.
- Let's make a new Git Branch
git checkout -b your_branch_name
- Go ahead and delete the
containers
components
andhelpers
directories and theindex.js
file in theapp
directory.- You can always refer back to the Github tutorial, or checkout to the master branch
package.json
- This file is used to list the dependencies of the application. We used it to install the correct node modulesapp/.babelrc
- To use babel to transform our items we have to pass in "react" and "es2015"index.html
- has some cdns for jQuery, Materialize CSS. Also has only a single element with an id ofapp
. We're going to target this in our JS file
import React from 'react';
import {render} from 'react-dom';
function HomeComponent(props){
return(
<div>
<h1>Hello World!!!</h1>
<h3>You're using REACT!!!!</h3>
</div>
)
}
class HomeContainer extends React.Component{
render(){
return(
<HomeComponent/>
)
}
}
render(
<HomeContainer/>,
document.getElementById('app')
)
- the imports are ES6 syntax. We are grabbing what we want from the node modules we installed earlier (remember when we ran that
npm install
command?)
NOTES
- Some tutorials may have their containers written with the syntax below
const HomeContainer = React.createClass({
render(){
return(
<HomeComponent/>
)
}
})
- With the updates to the React Library it is recommended to create your containers by extending the React.Component
class HomeContainer extends React.Component{
render(){
return(
<HomeComponent/>
)
}
}
const
- an ES6 syntax that we are using to declare an object instead of usingvar
. Const also tells us that this object isimmutable
render(){}
- ES6 syntax. This just meansfunction render(){}
<HomeComponent/>
-JSX syntax
inside of thereturn / render
of Container- All const/containers must have a render function with a return statement, returning an object that represents the virtual DOM
- The last render is coming from the
react-dom
node module.- The first argument is the main React component we want to be rendered
- The second argument is the location in the HTML file that will render it. In this example we target the
div
element with an id ofapp
- Many tutorials will use some of the following technologies
Containers vs Components
Smart and Dumb Components
Containers vs Stateless Presentational Components
- We'll stick with the first one
- Our Containers will be JavaScript objects that hold all the
state
and thelogic
for that piece of the Virtual DOM - Our Components will be logicless and stateless. Their only goal is to present the element to the page. You can apply your styling in each component file. We'll do that in the following branches
Alright, time for the next step 02_separation_of_concerns
- In this branch we're going to separate all of our code for organizational purposes.
- We learned about Containers and Components but now we're going to give them their own folders.
- In a larger app you might even have sub directories inside of these folders
- Since these are all new files we'll have to export and import them into each other
- Here's an example of importing and exporting in our
HomeContainer
file
import React from 'react';
import HomeComponent from '../components/HomeComponent';
class HomeContainer extends React.Component{
render(){
return(
<HomeComponent/>
)
}
}
export default HomeContainer;
- Import the HomeComponent for use
'../components/HomeComponent'
We can use relative pathing to grab what we want
- Export the const object so the
index.js
file can use it - Below is how we export our
HomeComponent
function HomeComponent(props){
return(
<div className="container" style={styles.centering}>
<h1>This Is Your Movie App!</h1>
<div className="row">
<form className="col s10 offset-s2 m4 offset-m4">
<input
placeholder="Enter Movie or TV Show Title"
type="text"
className="validate"/>
<input
type="submit"
hidden/>
</form>
</div>
</div>
)
}
export default HomeComponent;
- We are exporting our
Stateless Presentational Component
so the HomeContainer can use it - REMEMBER this is a component that will NOT HOLD any state or logic. Hence the name
Stateless Presentational Component
className
- JSX way of identifying a classconst styles
- we created styling for this component that will be applied inside the return- The classes applied to these elements are class names from
Materialize CSS
index.js
Now our Index.js File looks like this!!!
import React from 'react';
import {render} from 'react-dom';
import HomeContainer from './containers/HomeContainer';
render(
<HomeContainer/>,
document.getElementById('app')
)
- Now you can start seeing the organizational piece of React.
- Each component is just a JavaScript object that we can import/export and drop any where we want in our site.
- Utilizing components in this manner lets us keep them small and also makes them reusable
- Think about having to build only one
loading animation component
and being able to re-use that in multiple parts/pages on your site.
- Think about having to build only one
export default
- is ES6 syntax that will allow us to export the one main function of the file.- This is how we are allowed to import the
HomeComponent
inside of theHomeContainer
file
- This is how we are allowed to import the
- STYLING
- There are arguments for and against declaring
styles
as a JS object to add to your JSX. - It may look more organized because a React project can break up into many components.
- However, you will not get the full CSS functionality inside a JS file as you would a normal CSS/SASS file
- There are arguments for and against declaring
Forward onto step 03_state_and_props
- If we keep our logic in one place and the styling in another place how do the two talk to each other?
State and Props
- These are the meat and potatoes of React, or if you're vegetarian, lettuce and tomatoes.- Your
State
is similar to props but acts as a private object that is only accessible by that component.
- Inside our container we will use a built in function called
getInitialState
getInitialState()
- This is the ES6 way of writing
function getInitialState()
- This is a built in React function that must ALWAYS return a Object
- This object is immutable
- If
search
was a key in your state you cannot change it in the following mannerthis.search = blah
- we'll cover how to change the state object in following branches
- If
- You can pass this whole object or just specific parts to the component being called in components
render()
function
- This is the ES6 way of writing
handleUserSubmit()
- This is our own function built to handle an event
- This event listener will be passed down to the component
- Now when we render our
HomeComponent
we'll use JSX syntax to pass in the props. - Feel free to stick console logs inside of the components functions any time to make sure your data is correct
- check out the console.log for "this.state"
- Inside our HomeComponent, along with the styling, we can use the props that were passed in to show on the page.
<h2>The name from our props is {props.name}</h2>
- props.name was accessible because inside our
HomeContainer
we rendered theHomeComponent
like this
render(){
console.log(this.state);
return(
<HomeComponent
data = {this.state}
name = {this.state.yourName}
onUserSubmit = {this.handleUserSubmit}/>
)
}
Props
- This represents the data/functions that can be sent to a component. The component can use this data to populate it's JSX Virtual DOM Elements or add event listeners to the Virtual DOM ElementsState
- An immutable object of information that represents the data for that container and what is available to be passed down to the sub components- To change state use the
this.setState
function. We will see and example of this in the following branches - When to use the word
handle
- When creating events the best practice is to use
"handle"
when creating the event listener and"on"
when passing it as a prop - This helps us, and other programmers to understand where the event listener is being created.
- When creating events the best practice is to use
event.preventDefault()
is used to prevent the form from reloading
Enough meat and potatoes, lets go to step 04_ajax_and_axios
- Well we didn't come here to just log things in the console
- We learn to grab the user input with our
handleUserSubmit
function and pass it down as a prop calledOnUserSubmit
to ourHomeComponent
. - Now we're going to start sending requests to the OMDB API
- Again, we want to organize our code into small files with a specific kind of structure
- For these AJAX calls we're going to use a node module called
Axios
- These calls will be in the
helpers
file of thehelpers
directory - We're going to import and export them the same way we do with our containers and components
import axios from 'axios';
var singleTitle = "http://www.omdbapi.com/?t="
var multiTitle = "http://www.omdbapi.com/?s="
function singleSearch(title, year){
let endpoint = singleTitle + title + "&y=" + year;
return axios.get(endpoint)
.then(function(response){
return response
})
.catch(function(err){
console.warn("Error will your Single Search Helper");
})
}
function multiSearch(title){
let endpoint = multiTitle + title
return axios.get(endpoint)
.then(function(response){
return response.data.Search
})
.catch(function(err){
console.warn("Error with our multi search helper call" + err)
})
}
module.exports = {
singleSearch : singleSearch,
multiSearch : multiSearch
}
- Import the Axios library
- Set up our
endpoints
for the APIendpoints
are the addresses for the data we need to grab from OMDB
- Build the function so that the proper url will be created.
- For this well use string concatenation to combine the title, and year with the proper url
- OMDB will send data back to us in JSON format
- We will return that data to the container it is called.
- At the bottom of the file we export all our helper fuctions.
NOTE:
we continue to break up our code to be small and reusable. Now if this were a larger app we could drop these different helper functions anywhere we want for use
- Make sure to import the helper functions at the top along with your other imports
import {multiSearch} from '../helpers/helpers'
- Now lets look at the actual user submit function
handleUserSubmit(event){
event.preventDefault();
console.log(event.target)
console.log($(event.target).find("input:text").val())
let movieTitle = $(event.target).find("input:text").val();
multiSearch(movieTitle)
.then((data) => {
console.log(data)
})
}
- We're keeping the console logs for now to make sure we have the users input
- We're setting the user input to a variable that will be passed into the
multiSearch
helper function - The data that is returned from
multiSearch
will be passed in asdata
inside of the.then
promise.
Find out how to use that data in the next step 05_render_movies
- Take a look at what we've done with the handleUserSubmit function
- We know the
data
we get back inside.then
is an array of objects - We are going to use Reacts
this.setState
function to set thatdata
returned from our axios call to the keymoviesFound
inside our state.
handleUserSubmit(event){
event.preventDefault();
console.log(event.target)
console.log($(event.target).find("input:text").val())
multiSearch(movieTitle)
.then((data) => {
this.setState({
search : true,
movieTitle : movieTitle,
moviesFound : data
})
})
},
render(){
return(
<HomeComponent
data = {this.state}
onUserSubmit = {this.handleUserSubmit}/>
)
}
- Our render function stays the same.
- However, since the state change the
data={this.state}
will reflect that change and pass it down as props to ourHomeComponent
function HomeComponent(props){
const {moviesFound, loading, search} = props.data;
return(
<div className="container" style={styles.centering}>
<h1>This Is Your Movie App!</h1>
<div className="row">
<form
className="col s10 offset-s2 m4 offset-m4"
onSubmit = {props.onUserSubmit}>
<input
placeholder="Enter Movie or TV Show Title"
type="text"
className="validate"/>
<input
type="submit"
hidden/>
</form>
</div>
{
search === true ?
<MovieListComponent
data={moviesFound}
loading={loading}/>
:
<h4>Let us help you find what are you looking for?</h4>
}
</div>
)
}
- The only thing that has changed here is the ternary operator
- What the hell is the ternary operator saying?
- Assess if props.data.search is true
- If it is true render the
MovieListComponent
and pass it the props ofmoviesFound
(the array of movie objects) - If it is false we'll render a sub header for the user to see
- Lets take a look at the MovieListComponent first.
- Inside the return function we are using
props.data.map
to loop through the array of objects. - It will apply the object at EACH ITERATION as props to the
MovieUI
component - In turn each
MovieUI
component will call theMoviePoster
function to see if we have an image or not to show the user.- If there is an image return that in an image tage
- If there is not an image return an image tag with the default image
function MoviePoster(props){
return props.data === "N/A"
? <img style={styles.posterImage} className="activator" src="http://www.nyctransitforums.com/forums/fcontent/default.png" />
: <img style={styles.posterImage} className="activator" src={props.data} />
}
function MovieUI(props){
return(
<div className="col s12 m4">
<div className="card large">
<div className="card-image waves-effect waves-block waves-light">
<MoviePoster
data = {props.data.Poster}/>
</div>
<div className="card-content">
<span className="card-title activator" style={styles.mTitle}>
{props.data.Title}
<i className="material-icons right">more_vert</i>
</span>
</div>
<div className="card-action" style={styles.details}>
<a href="#">Add to favorites</a>
<a href="#">Share</a>
</div>
</div>
</div>
)
}
function MovieListComponent(props){
return (
<div className="row">
{props.data.map(function(movie){
return <MovieUI
data={movie}/>
})}
</div>
)
}
export default MovieListComponent;
this.setState
runs asynchronouslythis.setState
will automatically re-render the component, passing the new state down as props- Please make sure you check out the ES6 section in this markdown to understand how the arrow function works and why we no longer need to write
bind(this)
at the end of our function - Why bother with a Movie Component? Can't we just stick those functions inside the same file as our Home Component?
- Yes it would definitely work the same if we put everything in the Home Component
- BUT REMEMBER, the purpose of breaking these up is to
keep our files small, our code organized, and our components reusable
- If a component is getting too big think about how to break it up into smaller files.
Imperative vs Declarative programming!!!
- We have to use props.data.map inside our MovieComponent return. We cannot use a for loop inside of the JSX.
Time to get more movie details in step 06_adding_details
- So far we built a new component called
MovieListComponent
- The goal of this component was to render a template showing all the movies on the page.
- This component uses classes from the
Materialize CSS
framework to format the movies into cards - Notice that they aren't clickable and we don't have any detailed movie information
- What about the Synopsis? Release Date? Rating? Directors? Actors and Actresses?
- Now we're going to grab all that information using the other helper function
singleSearch
- Inside the
MovieListComponent
the functionMovieUI
now calls a new object by the name ofMovieDetailsContainer
and passes "props.data" to it.
- Import the singleSearch helper function
- This container will initialize its own state
- Use a lifecycle method called
componentDidMount
- This is a method built into react that says "Once this component is mounted run this code block"
- That code block being the invoking of
this.getSingleMovie
and passing in the props as the arguments - We won't cover lifecycle methods in depth in this tutorial but there are many articles/documentation on how to use them
These next steps should sound familiar
getSingleMovie
will take the title and year as arguments and send it to the helper function- Axios hits the OMDB API and will return back that data in JSON format
- That data is used to update the state of our
MovieDetailsContainer
- Now that the state has been updated we can send that data as
props
to aMovieDetailsComponent
- Uses a ternary operator to assess if
props.loading
is true. - If it is true render some loading images from
Materialize CSS
- If it is false take the data from
movieInfo
and populate them on the page.
- Wait we called a container
MovieDetailsContainer
from a componentMovieListComponent
? - Yes we did. Remember all of these are just JavaScript Objects
- The reason I made details into another container/component instead of putting it all in one file is the seperation of the axios logic and the presentation of listing out all the data returned
- CONGRATS!!!
- You got through all 6 branches.
- You may notice there is an extra component file in the master branch that is not in branch 6
- I made a
LoadingComponent
to show as an example of something small and reusable - It can be imported and dropped anywhere throughout the app
- We can use it as a default loading screen where ever the user might have to wait for data.
- In the example of the master branch I imported the
LoadingComponent
into theMovieListComponent
- Check out the ternary operator in the return function there
function MovieListComponent(props){
return (
<div className="row">
{props.loading === false ?
<Loading/>
:
props.data.map(function(movie){
return <MovieUI
data={movie}/>
})
}
</div>
)
}
- If the movies are rendering too fast and you want to see the loading sign in action feel free to go into the
MovieListContainer
and setloading
to false inside of thethis.setState
- You can find an extension of this tutorial in my new repo here. https://github.com/Jingo88/Just_Another_React_Tutorial_Extended
- In this repo I will go about refactoring this projects code to include the React Router.
- Lifecycle methods help us to control when specific code runs with regards to component
- In our
MovieDetailsComponent
we used a lifecycle method to tell React when to run our Axios helper function - Check out the docs for more lifecycle methods and how to use them.
- https://facebook.github.io/react/docs/react-component.html
- React Router is a routing library for React.
- It will help us to jump between multiple React components while syncing with the url
- Check out the docs for more information on how to utilize this awesome library
- https://github.com/ReactTraining/react-router
- ReactJS is great but things might start to get hectic if you have too many containers all with their own
state
- That's where Redux saves the day. It is like the missing limb of React.
- It allows us to put all our state into a
store.js
file that can be accessible by all components - Check out the docs for more info
- http://redux.js.org/