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.
Forkthis repository.Cloneyour fork.cdinto the project directory.- create a new
submissionbranchgit 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 apiin 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
axiosto the project usingnpm install --save axios. - Open
./src/components/App.js. - Import
axiosinto the component. - Use
axiosand the API documentation to fetchpostsin thecomponentDidMountmethod.- Set the
postsarray returned from the API ontopostsin thestateobject.
- Set the
- Import the
Postcomponent. - Underneath the
Composecomponent, map overpostsonstateand render aPostcomponent 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
rendermethod, update themapto send the followingpropsintoPost:text- Should equal thepost'stext.date- Should equal thepost'sdate.
- Open
./src/components/Post/Post.js. - Update
POST DATE GOES HEREto equal the value ofdateonprops. - Update
POST TEXT GOES HEREto equal the value oftextonprops.
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
updatePostmethod, useaxiosto send aPUTrequest 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
postsonstate.
- Remember that this method will need to work for any
- Pass the
updatePostmethod down as apropcalledupdatePostFnin thepostsmap. - Add a new
propto themapcalledidthat 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
divwe either render theEditcomponent or thepost'stext.
- In this
- Add a new
propto theEditcomponent calledupdatePostFnthat equalsupdatePostFnoff ofprops. - Add a new
propto theEditcomponent calledidthat equalsidoff ofprops. - Update the
textpropto equaltextoff ofprops. - Open
./src/components/Post/Edit/Edit.js. - Update the
updatePostmethod to:- Call
updatePostFnoff ofpropswith the correctidandtextarguments. - Call
hideEditoff ofpropsafterupdatePostFn.hideEditis reponsible for toggling theEditcomponent off after pressing theupdatebutton.
- 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
deletePostmethod, useaxiosto send aDELETErequest 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
postsonstate.
- Remember that this method will need to work for any
- Pass the
deletePostmethod down as apropcalleddeletePostFnin thepostsmap. - Open
./src/components/Post/Post.js. - Update the
deletespanelement to use anonClickevent that callsdeletePostFnoff 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
createPostmethod, useaxiosto send aPOSTrequest 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
postsonstate.
- Remember that this method will need to work for any
- Pass the
createPostmethod down aspropcalledcreatePostFnon theComposecomponent. - Open
./src/components/Compose/Compose.js. - Update the
createPostmethod to:- Call the
createPostFnoff ofpropswith the correcttextargument. - Use
setStateto reset the value oftextonstateto 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