In this project, we'll complete the functionality of a social media platform by
using axios
in a react
project. Majority of the application has already been
built out for you. Take some time before starting to familarize yourself with
the component structure. The platform we'll be building out is a scaled down
version of twitter and we'll focus on the functionality for fetching all the
Boom.Camp posts, creating new posts, editing existing posts, and deleting
existing posts.
Fork
this repository.Clone
your fork.cd
into the project directory.- create a new
submission
branchgit checkout -b submission
- Run
npm install
. - Run
npm start
.
Before continuing to step 1, take a minute to start the api server and learn how to interact with it.
- To Start API server run
npm run api
in a new terminal window. - You should see some output as a simple JSON server starts up.
This is a very simple API that only interacts with one resource posts
. The
following HTTP methods are available for the posts
endpoint
Base URL is: https://localhost:9090
GET /posts - get all posts
GET /posts/:id - get one post by id
POST /posts - create new post
PUT /posts/:id - replace post by id
PATCH /posts/:id - modify post by id
DELETE /posts/:id - delete post by id
In this step, we'll use axios
to fetch posts
from the API and render them to
the DOM
.
- Add
axios
to the project usingnpm install --save axios
. - Open
./src/components/App.js
. - Import
axios
into the component. - Use
axios
and the API documentation to fetchposts
in thecomponentDidMount
method.- Set the
posts
array returned from the API ontoposts
in thestate
object.
- Set the
- Import the
Post
component. - Underneath the
Compose
component, map overposts
onstate
and render aPost
component for eachpost
.- Remember that React requires a unique key property when using a
map
.
- Remember that React requires a unique key property when using a
Detailed Instructions
Let's begin by opening a terminal and cd
into the root of the project. We'll
need to add axios
to our project in order to make API requests. We can do so
by running npm install --save axios
.
Now let's open ./src/components/App.js
and import axios
into the component.
Near the top of the file with the other import statements, add the following
code:
import axios from 'axios';
Let's setup some default HTTP headers for axios, so we send the correct headers with our requests.
axios.defaults.headers.common['Content-Type'] = 'application/json';
Now that we have axios
imported, we can go into the componentDidMount
method
and make a GET
request to the API. Using the API documentation we can see that
we need to make a GET
request to http://localhost:9090/posts
. The API
documentation also explains that a GET
request returns an array of post
objects. We'll need to capture this returned array and set it onto posts
on
App
's state
.
componentDidMount() {
axios
.get('http://localhost:9090/posts')
.then(response => this.setState({ posts: response.data }));
}
Now when the App
component mounts it will fetch the array of posts
. The last
thing we'll need to do is map
over the posts
and render them onto the DOM
.
We'll need to import the Post
component into ./src/components/App.js
. Once
it has been imported, we can go into the render
method and render a Post
component for every post
in the posts
array on state
. Remember when using
a map
in the render
method, the element needs a unique key
property. In
this case, we can use the id
of the post
.
render() {
const { posts } = this.state;
return (
<div className="App__parent">
<Header />
<section className="App__content">
<Compose />
{posts.map(post => (
<Post key={post.id} />
))}
</section>
</div>
);
}
}
./src/components/App.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
import Header from './Header/Header';
import Compose from './Compose/Compose';
import Post from './Post/Post';
axios.defaults.headers.common['Content-Type'] = 'application/json';
class App extends Component {
constructor() {
super();
this.state = {
posts: [],
};
this.updatePost = this.updatePost.bind(this);
this.deletePost = this.deletePost.bind(this);
this.createPost = this.createPost.bind(this);
}
componentDidMount() {
axios
.get('https://practiceapi.devmountain.com/api/posts')
.then(response => {
this.setState({ posts: response.data });
});
}
updatePost() {}
deletePost() {}
createPost() {}
render() {
const { posts } = this.state;
return (
<div className="App__parent">
<Header />
<section className="App__content">
<Compose />
{posts.map(post => (
<Post key={post.id} />
))}
</section>
</div>
);
}
}
export default App;
In this step, we'll pass props
down into the Post
component in order to see
the text
and date
for each post
.
- Open
./src/components/App.js
. - In the
render
method, update themap
to send the followingprops
intoPost
:text
- Should equal thepost
'stext
.date
- Should equal thepost
'sdate
.
- Open
./src/components/Post/Post.js
. - Update
POST DATE GOES HERE
to equal the value ofdate
onprops
. - Update
POST TEXT GOES HERE
to equal the value oftext
onprops
.
Detailed Instructions
Let's begin by opening ./src/components/App.js
. In the render
method, we'll
need to update the map
to include two new props on the Post
component. The
props
we'll need are: text
and date
. If we take a look at the API
documentation we can see that a post
object has an id
, text
, and date
property. We can access the properties we need by using post.text
and
post.date
.
{
posts.map(post => <Post key={post.id} text={post.text} date={post.date} />);
}
Now that our Post
component is receiving the text
and date
, we can
render
them in the Post
component. Let's open
./src/components/Post/Post.js
. I'm going to destructure text
and date
off
of props
at the top of the render
method for easier referencing.
const { text, date } = this.props;
We can then update POST DATE GOES HERE
and POST TEXT GOES HERE
to use these
destructured values.
<span className="Post__date">- { date }</span>
<span className="Post__text">{ text }</span>
./src/components/App.js ( posts map )
{
posts.map(post => <Post key={post.id} text={post.text} date={post.date} />);
}
./src/components/Post/Post.js ( render method )
render() {
const { editing, showMasterMenu } = this.state;
const { text, date } = this.props;
return (
<section className="Post__parent" onClick={this.hideMasterMenu}>
{/* Three dots in top right corner */}
<div className="Post__master-controls">
<MdMoreVert onClick={this.toggleMasterMenu} />
<div
className="Post__master-menu"
style={{ display: showMasterMenu ? 'flex' : 'none' }}
>
<span onClick={this.showEdit}>Edit</span>
<span>Delete</span>
</div>
</div>
<div className="Post__meta-data">
<div className="Post__profile-picture">
<MdPersonOutline />
</div>
<span className="Post__name">Boom.Camp</span>
<span className="Post__handle">@boom.camp</span>
<span className="Post__date">- {date}</span>
</div>
<div className="Post__content">
{
editing ? (
<Edit text="" hideEdit={this.hideEdit} />
) : (
<span className="Post__text">{text}</span>
)}
</div>
<div className="Post__user-controls">
<MdChatBubbleOutline className="Post__control-icon" />
<MdFavoriteBorder className="Post__control-icon" />
<MdMailOutline className="Post__control-icon" />
</div>
</section>
);
}
In this step, we'll create the updatePost
method to use axios
to update a
post
's text
.
- Open
./src/components/App.js
. - In the
updatePost
method, useaxios
to send aPUT
request to the correct endpoint.- Remember that this method will need to work for any
post
. Hint:function parameters
. - Use the returned data from the request to update
posts
onstate
.
- Remember that this method will need to work for any
- Pass the
updatePost
method down as aprop
calledupdatePostFn
in theposts
map
. - Add a new
prop
to themap
calledid
that equals thepost
'sid
.
Detailed Instructions
Let's begin by opening ./src/components/App.js
. In the updatePost
method,
we'll need to use axios
to make a PUT
request to the API. Using the API
documentation, we can see that when editing a post the API is expecting a PUT
request at http://localhost:9090/posts/:id
. We can also see that the endpoint
uses the request path to determine the post
id
and uses the request body to
determine the post
text
. Because the id
and text
of the post will be
different every time the method is called we should use an id
and text
parameter for the method.
updatePost(id, text) {
}
We can then use these parameters to construct our axios
request and use the
returned data to update posts
on state
. When using axios.put()
the second
argument is the request
body.
updatePost( id, text ) {
axios
.put(`https://localhost:9090/posts/${id}`, { text })
.then(response => {
this.setState({ posts: response.data });
});
}
Now that our method is constructed, all we need to do is pass it down into the
component
that needs it. In the render
method, let's update the map
to
include a new prop
called updatePostFn
that equals the updatePost
method.
We'll also add a new prop
called id
that equals the post
's id
.
{
posts.map(post => (
<Post
key={post.id}
id={post.id}
text={post.text}
date={post.date}
updatePostFn={this.updatePost}
/>
));
}
./src/components/App.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
import Header from './Header/Header';
import Compose from './Compose/Compose';
import Post from './Post/Post';
axios.defaults.headers.common['Content-Type'] = 'application/json';
class App extends Component {
constructor() {
super();
this.state = {
posts: [],
};
this.updatePost = this.updatePost.bind(this);
this.deletePost = this.deletePost.bind(this);
this.createPost = this.createPost.bind(this);
}
componentDidMount() {
axios.get('https://localhost:9090/posts').then(response => {
this.setState({ posts: response.data });
});
}
updatePost(id, text) {
axios.put(`http://localhost:9090/posts/${id}`, { text }).then(response => {
const updatedPost = response.data;
// remember we CANNOT! directly mutate the properties on this.state
// We use map here to make a new copy of the posts but with the updated
// post that was edited.
const updatedPosts = this.state.posts.map(post => {
if (post.id === updatedPost.id) {
return { post, ...updatedPost };
} else {
return post;
}
});
this.setState({ posts: updatedPosts });
});
}
deletePost() {}
createPost() {}
render() {
const { posts } = this.state;
return (
<div className="App__parent">
<Header />
<section className="App__content">
<Compose />
{posts.map(post => (
<Post
key={post.id}
text={post.text}
date={post.date}
id={post.id}
updatePostFn={this.updatePost}
/>
))}
</section>
</div>
);
}
}
export default App;
In this step, we'll make the Edit
button functional by using the
updatePostFn
.
- Open
./src/components/Post/Post.js
. - Locate the
<div>
element with the class ofPost__content
.- In this
div
we either render theEdit
component or thepost
'stext
.
- In this
- Add a new
prop
to theEdit
component calledupdatePostFn
that equalsupdatePostFn
off ofprops
. - Add a new
prop
to theEdit
component calledid
that equalsid
off ofprops
. - Update the
text
prop
to equaltext
off ofprops
. - Open
./src/components/Post/Edit/Edit.js
. - Update the
updatePost
method to:- Call
updatePostFn
off ofprops
with the correctid
andtext
arguments. - Call
hideEdit
off ofprops
afterupdatePostFn
.hideEdit
is reponsible for toggling theEdit
component off after pressing theupdate
button.
- Call
Detailed Instructions
Let's begin by opening ./src/components/Post/Post.js
. In the render
method,
there is a div
with the class of Post__content
that either renders a
post
's text
or an Edit
component. The Edit
component is responsible for
capturing a user's new text to assign to a post
. In order for the Edit
component to function, we'll need to pass the updatePostFn
prop
from
Post.js
as a prop
into Edit.js
. We'll also need to pass down the post
's
text
and id
so they can be used as the arguments for the updatePostFn
.
You'll notice that a prop
called hideEdit
already exists, that method is
responsible for hiding the Edit
component after updating a post
's text
.
<div className="Post__content">
{editing ? (
<Edit
text={text}
id={id}
hideEdit={this.hideEdit}
updatePostFn={updatePostFn}
/>
) : (
<span className="Post__text">{text}</span>
)}
</div>
Now that we're passing down all the necessary props
into the Edit
component,
let's open ./src/components/Post/Edit/Edit.js
and get the Edit
button
functional. We'll need to update the updatePost
method. This method gets
called when a user clicks on update
. The component keeps track of the new text
value on its state
. Knowing this, we can call updatePostFn
with id
off of
props
and text
off of state
. After we call updatePostFn
we should also
call hideEdit
off of props
to hide the Edit
component.
updatePost() {
const { text } = this.state;
const { id, updatePostFn, hideEdit } = this.props;
updatePostFn( id, text );
hideEdit();
}
./src/components/Post/Post.js ( Post__content )
<div className="Post__content">
{editing ? (
<Edit
text={text}
id={id} // Remember to destructure id off of props or use this.props.id
hideEdit={this.hideEdit}
updatePostFn={updatePostFn}
/>
) : (
<span className="Post__text">{text}</span>
)}
</div>
./src/components/Post/Edit/Edit.js ( updatePost method )
updatePost() {
const { text } = this.state;
const { id, updatePostFn, hideEdit } = this.props;
updatePostFn( id, text );
hideEdit();
}
In this step, we'll create the deletePost
method to use axios
to delete a
post
and also make the Delete
button functional.
- Open
./src/components/App.js
. - In the
deletePost
method, useaxios
to send aDELETE
request to the correct endpoint.- Remember that this method will need to work for any
post
. Hint:function parameters
. - Use the returned data from the request to update
posts
onstate
.
- Remember that this method will need to work for any
- Pass the
deletePost
method down as aprop
calleddeletePostFn
in theposts
map
. - Open
./src/components/Post/Post.js
. - Update the
delete
span
element to use anonClick
event that callsdeletePostFn
off ofprops
.
Detailed Instructions
Let's begin by opening ./src/components/App.js
. We'll need to update the
deletePost
method to make a DELETE
request using axios
. If we take a look
at the API documentation we can see that the API expects a DELETE
request at
https://practiceapi.devmountain.com/api/posts
. We can also see that the
endpoint uses the request query to determine the id
of the post
to delete.
Because the id
of the post
will be different every time we should use an
id
parameter for this method.
deletePost(id) {
}
We can then use the parameter to construct our axios
request and use the
returned data to update posts
on state
.
deletePost( id ) {
axios
.delete(`https://localhost:9090/posts/${id}`)
.then(response => {
this.setState({
posts: this.state.posts.filter(post => post.id !== id)
});
});
}
Now that our method is constructed, let's pass it down in the Post
component
in our map
.
{posts.map(post => (
<Post
key={post.id}
id={post.id}
text={post.text}
date={post.date}
updatePostFn={this.updatePost}
deletePostFn={this.deletePost}
/>
))}
We can then call this method and pass in the post
's id
using an onClick
event on the Delete
span
.
<span onClick={() => deletePostFn(id)}>Delete</span>
./src/components/App.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
import Header from './Header/Header';
import Compose from './Compose/Compose';
import Post from './Post/Post';
axios.defaults.headers.common['Content-Type'] = 'application/json';
class App extends Component {
constructor() {
super();
this.state = {
posts: [],
};
this.updatePost = this.updatePost.bind(this);
this.deletePost = this.deletePost.bind(this);
this.createPost = this.createPost.bind(this);
}
componentDidMount() {
axios.get('https://practiceapi.devmountain.com/api/posts').then(results => {
this.setState({ posts: results.data });
});
}
updatePost(id, text) {
axios
.put(`https://practiceapi.devmountain.com/api/posts?id=${id}`, { text })
.then(results => {
this.setState({ posts: results.data });
});
}
deletePost(id) {
axios.delete(`http://localhost:9090/posts/${id}`).then(response => {
this.setState({
posts: this.state.posts.filter(post => post.id !== id),
});
});
}
createPost() {}
render() {
const { posts } = this.state;
return (
<div className="App__parent">
<Header />
<section className="App__content">
<Compose />
{posts.map(post => (
<Post
key={post.id}
id={post.id}
text={post.text}
date={post.date}
updatePostFn={this.updatePost}
deletePostFn={this.deletePost}
/>
))}
</section>
</div>
);
}
}
export default App;
./src/components/Post/Post.js ( Post__master-menu )
<div
className="Post__master-menu"
style={{ display: showMasterMenu ? 'flex' : 'none' }}
>
<span onClick={this.showEdit}>Edit</span>
<span onClick={() => deletePostFn(id)}>Delete</span> {/* Remember to destructure deletePostFn off of props or use this.props.deletePostFn */}
</div>
In this step, we'll create the createPost
method to use axios
to create a
new post
and also make the Compose
button functional.
- Open
./src/components/App.js
. - In the
createPost
method, useaxios
to send aPOST
request to the correct endpoint.- Remember that this method will need to work for any
post
. Hint:function parameters
. - Use the returned data from the request to update
posts
onstate
.
- Remember that this method will need to work for any
- Pass the
createPost
method down asprop
calledcreatePostFn
on theCompose
component. - Open
./src/components/Compose/Compose.js
. - Update the
createPost
method to:- Call the
createPostFn
off ofprops
with the correcttext
argument. - Use
setState
to reset the value oftext
onstate
to an empty string.
- Call the
Detailed Instructions
Let's begin by opening ./src/components/App.js
. We'll need to update the
createPost
method to make a POST
request using axios
. If we take a look at
the API documentation we can see that the API expects a POST
request at
https://practiceapi.devmountain.com/api/posts
. We can also see that the
endpoint uses the request body to determine the text
of the post
to create.
Because the text
of the post
will be different every time, we should use a
text
parameter for this method.
createPost( text ) {
}
We can then use the parameter to construct our axios
request and use the
returned data to update posts
on state
.
createPost( text ) {
axios.post('https://practiceapi.devmountain.com/api/posts', { text }).then( results => {
this.setState({ posts: results.data });
});
}
Now that our method is constructed, let's pass it down in the Compose
component.
<Compose createPostFn={this.createPost} />
We can then update the createPost
method in
./src/components/Compose/Compose.js
to call createPostFn
off of props
with
the value of text
on state
. After calling createPostFn
we should also
reset the value of text
on state
using setState
.
createPost() {
const { text } = this.state;
const { createPostFn } = this.props;
createPostFn( text );
this.setState({ text: '' });
}
./src/components/App.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
import Header from './Header/Header';
import Compose from './Compose/Compose';
import Post from './Post/Post';
axios.defaults.headers.common['Content-Type'] = 'application/json';
class App extends Component {
constructor() {
super();
this.state = {
posts: [],
};
this.updatePost = this.updatePost.bind(this);
this.deletePost = this.deletePost.bind(this);
this.createPost = this.createPost.bind(this);
}
componentDidMount() {
axios.get('https://practiceapi.devmountain.com/api/posts').then(results => {
this.setState({ posts: results.data });
});
}
updatePost(id, text) {
axios
.put(`https://practiceapi.devmountain.com/api/posts?id=${id}`, { text })
.then(results => {
this.setState({ posts: results.data });
});
}
deletePost(id) {
axios
.delete(`https://practiceapi.devmountain.com/api/posts?id=${id}`)
.then(results => {
this.setState({ posts: results.data });
});
}
createPost(text) {
axios
.post('https://practiceapi.devmountain.com/api/posts', { text })
.then(results => {
this.setState({ posts: results.data });
});
}
render() {
const { posts } = this.state;
return (
<div className="App__parent">
<Header />
<section className="App__content">
<Compose createPostFn={this.createPost} />
{posts.map(post => (
<Post
key={post.id}
id={post.id}
text={post.text}
date={post.date}
updatePostFn={this.updatePost}
deletePostFn={this.deletePost}
/>
))}
</section>
</div>
);
}
}
export default App;
./src/components/Compose/Compose.js ( createPost method )
createPost() {
const { text } = this.state;
const { createPostFn } = this.props;
createPostFn( text );
this.setState({ text: '' });
}
Update the project to allow the Search
bar to find posts
that contain a
certain string. Example: If a user searchs for blue, the Feed of posts should
only display posts that contain the word blue. To filter by multiple words be
sure to use the encodeURI
JavaScript function.
See this link for how to implement search through the API: https://github.com/typicode/json-server#full-text-search
Axios
Props
Create a pull request back to this project