React actually isn't as full-featured as some front-end frameworks like AngularJS or BackboneJS.
React relies on third party libraries to fetch data.
There are a lot of choices for which AJAX tools to use. Options include:
-
Axios, a promise-based HTTP client for the browser and node.
-
fetch, a new vanilla JavaScript web API available through most browsers
-
XML HTTP Requests, the old vanilla JavaScript web API for HTTP requests.
-
jQuery, which includes a
$.ajax
method for AJAX requests.
Since we're already familiar with jQuery, we'll use it here.
- Install
jquery
and create asrc/models
directory, which will manage AJAX requests for data. Also, make aTodo.js
model file:
$ npm install --save jquery
$ mkdir src/models
$ touch src/models/Todo.js
-
The super-crud API endpoints for
todos
will provide this app's data. Take a look at the raw json at https://super-crud.herokuapp.com/todos. -
In
src/models/Todo.js
, add the following code:
// src/models/Todo.js
import $ from 'jquery'
class TodoModel {
static all(){
let request = $.ajax({
url: "https://super-crud.herokuapp.com/todos",
method: 'GET'
})
return request
}
}
export default TodoModel
-
Think critically about the steps in this sprint so far. Are you creating a new database model? Or is this a client-side "model" as in "MVC" or "MVVM"?
-
Think critically about the
TodoModel
class. What is a "static" method in a JavaScript class?
In the
TodoModel
class,all()
is a static method. What does this mean? A static method can be called without an instance of the class. This will allow us to callall()
in the following way (without instantiating the class with new):let todos = TodoModel.all()
. More on Static Methods in JavaScript
- Think critically about the
TodoModel.all
static method. What is therequest
variable's value (that is, what does$.ajax
return)?
When we use the
all
method on ourTodoModel
, it will make aGET
request to the super-crud url for all todos. jQuery's documentation tells us that$.ajax
returns a promise. TheTodoModel.all
method saves this promise asrequest
and returns it for use elsewhere in the code. In the next few steps, we'll see a reminder of how to tell our code what to do when the promise is resolved.
- This file isn't connected to anything yet. It must be
import
ed into the application in order to test it. Since theTodosContainer
component will manage most of the app's logic for todos, the most logical place to import this code will be in there.
For now, make a call to TodoModel.all
inside the TodosContainer
render()
method for testing purposes.
In containers/TodosContainer.js
:
// src/containers/TodosContainer.js
import React, {Component} from 'react'
import TodoModel from '../models/Todo'
class TodosContainer extends Component {
render(){
TodoModel.all().then( (res) => {
console.log(res);
})
return (
<div className='todosContainer'>
<h2>This is the todos container</h2>
</div>
)
}
}
export default TodosContainer
- Check the browser console for the response from the super-crud database. There should be an object logged in the console.
Now that the data is on the front end, the next series of steps focuses on how it will be presented in the view.
- To follow FIRST, it makes sense for each todo to be rendered as its own component. Create
src/components/Todo.js
and add the following code inside it:
// src/components/Todo.js
import React, {Component} from 'react'
class Todo extends Component {
render(){
return(
<p data-todos-index={this.props.todo._id}>
<span>{this.props.todo.body}</span>
</p>
)
}
}
export default Todo
- Think critically about the code above. How do you think the
todo
prop is related to the data that came from super-crud? What is another field that could be displayed by theTodo
component (other thanbody
and_id
)?
click for an idea
From the component, it's clear thatthis.props.todo
is an object with keys body
and _id
. It matches the super-crud data! Other fields the super-crud todos have that could be displayed include priority
and completed
.
-
Think critically about the code above. How will the component receive the
todo
inprops
? -
As a test, add a
Todo
component as part of what theTodosContainer
component renders. Remember toimport
Todo
, and use any data you'd like for the body of the todo.
// src/containers/TodosContainer.js
import React, {Component} from 'react'
import TodoModel from '../models/Todo'
import Todo from '../components/Todo'
class TodosContainer extends Component {
render(){
// check that TodoModel AJAX call gets todo data
TodoModel.all().then( (res) => {
console.log(res);
})
let todo = { _id: "58e3c74c93075e0011489f04",
body: "Take a walk outside" }
return (
<div className='todosContainer'>
<h2>This is the todos container</h2>
<Todo
key={todo._id}
todo={todo}/>
</div>
)
}
}
export default TodosContainer
- Check the
/todos
page in the browser. The todo you wrote should be displayed on the page. Also, thep
element around it should havedata-todos-index
equal to the_id
you used -- check by inspecting the element!
- To keep components organized, create a todo list component in
src/components/TodoList.js
. Add the following code to the new component file:
// src/components/TodoList.js
import React, {Component} from 'react'
import Todo from './Todo'
class TodoList extends Component {
render(){
let todoArray = this.props.todos.map( (todo) => {
return (
<Todo
key={todo._id}
todo={todo}/>
)
})
return(
<div className="todos">
{todoArray}
</div>
)
}
}
export default TodoList
- Think critically about the code above. The
map
method iterates through an array of todos and createstodoArray
. What is inside thetodoArray
?
click for an explanation
ThetodoArray
is an array of Todo
components, with one component for each todo object in this.props.todos
.
- Remove the test todo and the
import Todo
line from theTodoContainer
component. You can also remove the testTodoModel.all
call if you'd like.
- The
TodosContainer
will contain a wholeTodoList
, not just a singleTodo
. Modify therender
method inTodosContainer
to render a testTodoList
, and remove the testTodo
component.
// src/containers/TodosContainer.js
import React, {Component} from 'react'
import TodoModel from '../models/Todo'
import TodoList from '../components/TodoList'
class TodosContainer extends Component {
render(){
// check that TodoModel AJAX call gets todo data
TodoModel.all().then( (res) => {
console.log(res);
})
return (
<div className='todosContainer'>
<h2>This is the todos container</h2>
<TodoList
todos={
[
{"_id":"58e3c74c93075e0011489f02","body":"Wash the dishes"},
{"_id":"58e3c74c93075e0011489f07","body":"Update resume with new skills"},
{"_id":"58e3c74c93075e0011489f05","body":"Buy nutritious groceries for the week"}
]
} />
</div>
)
}
}
export default TodosContainer
- Think critically about the code above. Todo data won't be hard-coded in our JavaScript. Where will todo data actually come from?
In this series of steps, you'll add the remainder of the code necessary to display todos from the super-crud API. After adding the code, you'll examine each piece.
-
Remove the
TodoModel.all
call from theTodosContainer
component'srender
function. -
Update
src/containers/TodosContainer.js
with the following code:
// src/containers/TodosContainer.js
import React, {Component} from 'react'
import TodoModel from '../models/Todo'
import TodoList from '../components/TodoList'
class TodosContainer extends Component {
constructor(){
super()
this.state = {
todos: []
}
}
componentDidMount(){
this.fetchData()
}
fetchData(){
TodoModel.all().then( (res) => {
this.setState ({
todos: res.todos
})
})
}
render(){
return (
<div className='todosContainer'>
<h2>This is the todos container</h2>
<TodoList
todos={this.state.todos} />
</div>
)
}
}
export default TodosContainer
-
Check the
/todos
route in the browser. The todos from the super-crud JSON data should be rendered on the page. At this point, you can remove the<h2>This is the todos container</h2>
element from theTodosContainer
component. -
Examine the following code snippet:
constructor(){
super()
this.state = {
todos: []
}
}
The constructor()
for a JavaScript class is a function that is called whenever an instance of the class is created. This constructor function will be called whenever a new TodosContainer
component is created.
The call to super()
means this class will run the constructor
function React defines for the Component
class (you can tell super
is the Component
class because the todos container class extends
Component
.
The final piece of code sets this.state
to be an empty array.
- What will
this.state
be when aTodosContainer
component is first created?
click for answer
[]
- How can the component's state be changed later?
click for answer
The state can be set at any other time usingthis.setState()
5. Examine the following code snippet:
fetchData(){
TodoModel.all().then( (res) => {
console.log('TodoModel.all response:', res)
this.setState ({
todos: res.todos
})
})
}
- Explain the
fetchData
function's role in your own words.
click for an idea
This function usesTodoModel
to retrieve todos from the super-crud API. After that request completes, then
the function changes the state of the container component. The new state has the value of todos
be part of the data from the AJAX response.Note: Any time
setState
is invoked, the component re-renders.
- Examine the following code snippet:
componentDidMount(){
this.fetchData()
}
Every component in React undergoes a component lifecycle. There are several "hooks" throughout this lifecycle, and componentDidMount
is a built-in hook that happens after a component renders.
There are many hooks. A great blog post goes into detail on the lifecycle of a component.
- Think critically about the last code snippet above. Why send a
GET
request after the TodoContainer component (and components inside it) have already been rendered? For ideas, look for thecomponentDidMount
section in React's Component documentation.
click for related resources
Other devs have wondered about this too. AJAX calls are asynchronous, and we'll always need to re-render after an AJAX call completes. Here's the Facebook recommendation saying to make initial AJAX calls inside componentDidMount
.
The state of the TodosContainer
is simple. It has an array of todos
.
- Examine your app's components critically. Briefly consider the following questions:
- How does the
TodoList
component know what data to render? - How does each individual
Todo
component know about the individual todo data it needs to render?
- Take a look at the
props
being passed from one component to the next, and trace the flow of how information is passed from larger components to the smaller components they contain, throughprops
.
In src/containers/TodosContainer.js
:
<TodoList
todos={this.state.todos} />
In src/components/TodoList.js
:
let todoArray = this.props.todos.map( (todo) => {
return (
<Todo
key={todo._id}
todo={todo}
/>
)
})
In src/components/Todo.js
:
<p data-todos-index={this.props.todo._id}>
<span>{this.props.todo.body}</span>
</p>
It is a pattern in React to send parts of a parent component's state
into its child component(s) as props
.
Most of the API's developers have access to are read-only. At this point, you know how to use React to display that data!