Simple Instagram app with Rails 6 API and React. Part 2
The React Part
Find the first part here
For this app, we would be styling mobile-first. For this second part in the series, we would only be focusing on working with the index and update methods in our controller. We would also only be focusing on implementing the ‘Liking’ functionality.
We would be using the create-react-app configuration containing the heroku:buildpack to get us up and running.
Follow the instructions in the repo to get started or see below.
npx create-react-app instabox-frontend
cd instabox-frontend
I have taken out the heroku parts as we will be using Netlify for deployment.
Git is also initialized for us out of the box. Let’s create our development branch, make a commit there, then checkout to a new branch called index.
Head to the src folder. Delete every file except App.css/App.js/index.css/index.js.
Inside the src folder, create a components folder. Name it ‘Pictures.jsx’.
Create another folder called helpers inside the src folder. Inside the helpers folder, create a file called apiHelper.js. This file will hold the link to our API so that we don’t have to keep manually typing out http://localhost:3001 each time we make a request to the server. In the future when we deploy our backend to the cloud, we would only have to change the URL in one place.
Add the following code to the file.
> src > components > helpers > apiHelpers.jsconst API_URL = "http://localhost:3001";export default API_URL;
Exit that file and return to our Pictures.jsx file.
We will create a new React class component called Pictures. This component will grab our data from the server using a package called axios. It would then hold this data in an array in our React app.
First things first, get axios. Make sure you are in the instabox-frontend folder. In your terminal, type
npm i axios --save
Add the following code to your Pictures.jsx file. I will try to add comments for each line in the code
import React, { Component } from "react";
import axios from "axios";
import API_URL from "./helpers/apiHelper";export default class Pictures extends Component {
constructor(props) {
super(props);// we create a stateful array to hold our pictures and likes
this.state = {
pictures: [],};
}// the first time our page loads, we grab all our pictures from the server
componentDidMount() {
axios
.get(`${API_URL}/pictures`)
.then(response => {
this.setState({ providers: response.data });// setstate is used to update data that we expect to change
})
.catch(error => console.log(error));
}
// this code grabs all the pictures and passes them into the providers array then logs the response in the console.render() {
return <div></div>;
}
}
We need to import our Pictures component inside our App component so it can be rendered in the DOM.
import React from "react";
import "./App.css";
import Pictures from "./components/Pictures"; // import picturesfunction App() {
return <Pictures />; // call pictures component here
}export default App;
Run
npm start
Open another instance of your vscode. Navigate to the instabox-api directory and run your rails server on port 3001.
rails s -p 3001
If you’ve made it this far, you should see that your console includes stale data from our Postman POST requests. Let’s take that data out as we used dummy values.
Head back into your rails folder in the seperate vscode instance you created.
Head into the rails console with rails c. Find the user you created.Mine has an id of 2.
Picture.find(2).destroy
This would remove that dummy picture from our DB. You can confirm it was deleted by running Picture.all.
Back to React.
We need to install a few packages to help us. First, fontawesome for all our icons.
npm i --save @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome # icon librarynpm i normalize # this gives us a clean CSS resetnpm i react-timeago # this allows us show how long since a picture was posted
Now, let’s build the menu.
import React, { Component } from "react";
import axios from "axios";
import API_URL from "./helpers/apiHelper";
import "../styles/Pictures.css";
import {
faCamera,
faChevronCircleRight,
faPaperPlane
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";export default class Pictures extends Component {
constructor(props) {
super(props);
this.state = {
pictures: []
};
}componentDidMount() {
axios
.get(`${API_URL}/pictures`)
.then(response => {
this.setState({ providers: response.data });
console.log(this.state.providers);
})
.catch(error => console.log(error));
}render() {
return (
<div>
<main className="PicturesWrapper">
<div className="MenuBar">
<div className="LeftMenu">
<div className="CameraIcon">
<FontAwesomeIcon
className="CamIcon"
id="CamIcon"
icon={faCamera}
size="2x"
/>
</div>
<div className="LogoIconDiv">
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Instagram_logo.svg/1000px-Instagram_logo.svg.png"
alt="igLogo"
className="LogoIcon"
/>
</div>
</div>
<div className="SendIcon">
<FontAwesomeIcon size="2x" icon={faPaperPlane} />
</div>
</div>
</main>
</div>
);
}
}
And here’s the CSS
@media screen and (min-device-width: 350px) and (max-device-width: 575px) {
.MenuBar {
display: flex;
border-bottom: 1px solid black;
}.LogoIcon {
width: 100px;
}.LogoIconDiv {
width: 260px;
margin-left: 0.5em;
}.LeftMenu {
display: flex;
margin-top: 1em;
margin-left: 1em;
}.SendIcon {
margin-top: 1em;
}
}
Next is the card for each Picture. It’ll contain the picture, caption text, created_by and any other information that belongs to a single picture.
Let’s call it PictureCard component. Create a PictureCard.jsx file inside the components folder. This time around we will use a functional component since we aren’t updating any data and we’re just displaying information.
To avoid this article being filled with CSS code, here’s a link to all the CSS here.
Here’s what our PictureCard component looks like in the end
import React from "react";
import "../styles/PictureCard.css";
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
import {
faHeart,
faComment,
faPaperPlane,
faBookmark
} from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import TimeAgo from "react-timeago";export default function PictureCard({# define the props we will be using
displayPicture,
uName,
picture,
caption,
dateCreated,
likes,
increaseLikes,
pid
}) {
return (
<div className="PictureCardWrapper">
<div className="UserBar">
<div className="UserAv">
<img src={displayPicture} alt="UserAvatar" className="UserAvatar" />
</div>
<div className="UserName">{uName}</div>
<div className="Dots">
<FontAwesomeIcon icon={faEllipsisV} color="grey" />
</div>
</div>
<div className="MainPictureDiv">
<img src={picture} alt="mainPicture" className="MainPicture" />
</div>
<div className="PictureIcons">
<div>
# call our fontawesome icons
<FontAwesomeIcon
icon={faHeart}
size="2x"
# pass in the pid to identify each heart icon with
the picture it belongs to
className={`PictureIconsIcon HeartIcon-${pid}`}
onClick={increaseLikes}
# when the heart icon is clicked, run the increaseLikes function
pid={pid}
/>
<FontAwesomeIcon
icon={faComment}
size="2x"
className="PictureIconsIcon"
/>
<FontAwesomeIcon
icon={faPaperPlane}
size="2x"
className="PictureIconsIcon"
/>
</div>
<div className="BookMarkIcon">
<FontAwesomeIcon icon={faBookmark} size="2x" />
</div>
</div>
<div className="caption">
<p className="CaptionP">
<strong>{uName}</strong>
<span className="captionSp">{caption}</span>
</p>
<div className="timeSincePost">
<div>
<TimeAgo date={dateCreated} />
</div>
<div>
<span className="Likes">{likes} likes</span>
</div>
</div>
</div>
</div>
);
}
After styling your page, you should have something like below.
Back to our Pictures.jsx component. Let’s make some changes that allows us increase the likes after each click of the heart button.
But first let’s extract our Likes and TimeAgo parts into a seperate Likes component. Create a ‘Likes’ component in your src folder and a Likes.css in your styles folder and add the following code to the Likes component. The Likes.CSS code below.
import React, { Component } from "react";
import TimeAgo from "react-timeago";
import "../styles/Likes.css";export default class Likes extends Component {
render() {
const { dateCreated, likes } = this.props;
return (
<div>
<div className="timeSincePost">
<div>
<TimeAgo date={dateCreated} />
</div>
<div>
<span className="Likes">{likes} likes</span>
</div>
</div>
</div>
);
}
}
Back in our Pictures component, update the code for our displayAllPictures to include our new Likes component. Don’t forget to import Likes at the top.
const displayAllPictures = pictures.map(picture => (
<div>
<PictureCard
key={picture.id}
uName={picture.created_by.toLowerCase()}
displayPicture={picture.img_link}
picture={picture.img_link}
caption={picture.caption}
increaseLikes={this.increaseLikes}
pid={picture.id}
/>
<Likes dateCreated={picture.created_at} likes={picture.likes} />
</div>
));
Finally, let’s update our Picture component to include our new methods to increase our likes.
constructor(props) {
super(props);
this.state = {
pictures: [],
likes: 0
};
this.increaseLikes = this.increaseLikes.bind(this);
}componentDidMount() {
axios.get(`${API_URL}/pictures/`)
.then(response => {
this.setState({ pictures: response.data });
console.log(response.data);
})
.catch(error => console.log(error));
}increaseLikes(e) {
const pid = e.currentTarget.getAttribute("pid");
const heart = document.querySelector(`.HeartIcon-${pid}`);
heart.setAttribute("color", "red");
this.getPictureClicked(pid);
}getPictureClicked(pid) {
axios
.get(`${API_URL}/pictures/${pid}`)
.then(response => {
this.setState({ likes: response.data.likes });
this.increase(pid);
})
.catch(error => console.log(error));
}increase(pid) {
const { likes } = this.state;
const body = {
likes: likes + 1
};axios.put(`${API_URL}/pictures/${pid}`, body).then(response => {
console.log(response.data);
this.setState({
pictures: response.data
});
});
}
After adding this methods. Go into your rails console to see all our pictures and you’ll see that the likes have also been updated in or db. Nice!
In the next part, we’ll add more functionality to our app. We will be able to show a picture clicked on a single page, add comments and allow a user to only have like a picture once.
Now is a good time to make a commit.