Share this article

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

In part 1 of this tutorial, we’ll be building a React frontend to present data that we’ll receive from an API, implement URL based navigation within our app using React Router, and create our interface with a bit of help from Material UI. We’ll be using N161.tech’s DummyAPI, which gives us access to a database of dummy users, posts and tags. You can find out more about the API’s available endpoints in the DummyAPI documentation.

In this tutorial:

  1. Introduction
  2. Planning & Design
  3. Setting up our React app
  4. Creating React components
  5. Fetching the API data
  6. Implementing the sidebar
  7. Adding navigation with React Router
  8. Creating the user profile pages
  9. Creating the tag pages
  10. Linking everything together with React Router

Introduction

Here’s a quick preview of what we’ll be creating in part 1:

Posts with sidebar
Profile 2
Tag page
Profile 1

This project’s source code is available on GitHub if you want to clone the repo or follow along there, there’s also a live demo available here if you just want to get hands-on.

Before we get started it’s worth noting that, because we’re using a public API, we can read from the database, but we can’t write to it. This means that we won’t be able to add actual working functionality like following/friending users, liking/disliking/sharing posts, login/registration etc., so we’ll just add placeholder visual elements to represent these actions.

Planning & Design

First of all, let’s try and visualise how our social network frontend will look. We know from existing social networking sites, like Facebook and Twitter, that the main point of interaction between our data and our users will be the posts, so let’s design our interface around that concept, and put the posts at the forefront of the app. Along with text, an image and tags, each post will have a link to the profile of the user that published it. We’ll also add a sidebar with a tag cloud, and a header with some basic navigation and a search input to help people navigate our app more easily. If we were to put together a rough wireframe diagram of this design, this is how it might look:

Social network UI design wireframe

Style-wise we’ll keep things pretty simple, and just go for a clean, minimal UI that won’t draw attention from the main focus point of our app; the posts and users. To save us some time on designing and styling individual elements, we’ll use components from Material UI.

Setting up our React app

Now that we have an idea of what we’re going to create, let’s get started. For the sake of this tutorial, I’m going to assume you’re already familiar with React & NPM, and have NPM installed on your machine. I’ll also be working on Mac, but if you’re working with Linux the process will be more or less identical and it’ll be pretty similar on Windows too.

First thing’s first, let’s use create-react-app to get our project started up. Open up a Terminal window and browse to the folder where you’d like to create your app, e.g. cd Documents/Dev. If you don’t have the create-react-app NPM module installed already, just run npm install -g create-react-app to install it globally. Now create your react app using the command create-react-app social-network-frontend.

Creating the Social Network Frontend React app

Once the process has finished, enter the new directory with the command cd social-network-frontend. We know we’re going to be using React Router and Material UI, so let’s get those installed now, by using the command npm install --save react-router-dom @material-ui/core @material-ui/icons. Once that’s finished installing, run your app with npm start.

Your new app will now fire up in a browser window (it’s usually located at http://localhost:3000/). You’ll see a basic page with some text and the React logo, which should look something like this:

Initial create react app

We’re not going to need a lot of the stuff that comes packaged with the default app, so let’s get to removing it. First, open up the App.js file with your favourite code editor (personally, I’m an Atom fan), then remove everything from within the App div. We can remove the logo import too. Your App.js file should now contain this code:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      
    </div>
  );
}

export default App;

Now open App.css with your code editor and delete all the existing styles, leaving you with an empty file. Your react app should now render as a blank white page in the browser.

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Creating React components

Now that we’ve stripped out that unnecessary code and have a blank canvas, we can start putting some of our own elements in place. Let’s start by adding a header component. Create a new directory called components within your app’s src folder. This is where we’ll store individual components of our app that we’ll import into parent components. We could just put all our components in App.js, but separating them is good practice and means that as our app grows over time, it’ll be easier to scale and maintain. Within your new components directory, create a file called Header.js, and add the following code:

import React from 'react';
import logo from '../logo.svg';

export default class Header extends React.Component {
  render(){
    return (
      <header className="Header">
        <h1 className="Branding">
          <img src={logo} alt="Social network logo" className="Logo" />
          Social Network
        </h1>
      </header>
    );
  }
}

Before the header is rendered in our app, we need to import the component into our App.js file. Add the following line to the top of your App.js file:

import Header from './components/Header';

Now add the Header component to the App function, along with a wrapper:

function App() {
  return (
    <div className="App">
      <div className="Wrapper">
        <Header/>
      </div>
    </div>
  );
}

Open up your app and check that the header component is rendering. It should look something like this:

Displaying the React logo to show that our component has imported

Good stuff! Our components are importing and we’re able to render them. Things are looking kind of ugly though, so let’s start adding some styles. Open up your App.css file and add the following:

.Wrapper {
  max-width: 900px;
  margin: auto;
  border-left: 1px solid rgba(0,0,0,0.1);
  border-right: 1px solid rgba(0,0,0,0.1);
}

.Header {
  border-bottom: 1px solid rgba(0,0,0,0.1);
  display: flex;
  justify-content: space-between;
  position: sticky;
  top: 0;
  background: #fff;
  z-index: 999;
}

.Header .Branding {
  margin: 0;
}

.Header .Branding .Logo {
  height: 40px;
  margin: 10px;
  vertical-align: middle;
}

a {
  text-decoration: none;
  color: #000;
}

Now our header component should look like this:

React frontend development tutorial - Social media network header

Now let’s create another component in our components directory called Posts.js. This is where we’ll render the posts data returned from the API. Set up the component basics with the following code:

import React from 'react';

export default class Posts extends React.Component {
  render(){
    return (
      <div className="Posts">
      
      </div>
    );
  }
}

Now let’s import the component into our App.js:

import Posts from './components/Posts';

And return the component within our App function:

function App() {
  return (
    <div className="App">
      <div className="Wrapper">
        <Header/>
        <Posts/>
      </div>
    </div>
  );
}

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Fetching the API data

If you view your app now, you won’t see any posts just yet. We need to get the data from the API before we have anything to render. We’ll do this using JavaScript’s fetch function, inside React’s ComponentDidMount() function, which will fire when the Posts component is loaded (similar to $('document').ready() if you’re familiar with or jQuery, or window.onload in vanilla JavaScript. So, let’s grab that data. First, add the following function to your Posts.js code:

import React from 'react';

export default class Posts extends React.Component {

  componentDidMount(){
    fetch('https://n161.tech/api/dummyapi/post?limit=10')
      .then(response => response.json())
      .then(posts => {
        console.log(posts);
      });
  }

  render(){
    return (
      <div className="Posts">

      </div>
    );
  }
}

View your app and open the developer console. You should see the JSON data returned from the API logged in the console:

Console log of Posts API data

So we’ve got the posts data, now we just need to render it in our app. We’ll do that by storing the data as a state within the Posts Class, then we’ll map each object within the data to a set of JSX elements. Change your Posts.js to the following:

import React from 'react';

export default class Posts extends React.Component {

  state = {
    posts: []
  }

  componentDidMount(){
    fetch('https://n161.tech//api/dummyapi/post')
      .then(response => response.json())
      .then(posts => {
        this.setState({ posts: posts.data });
        console.log(posts);
      });
  }

  render(){
    return (
      <div className="Posts">

      </div>
    );
  }
}

The posts data will now be stored as a state within the component. This means we can now map it with the following code within our render function:

render(){
  return (
    <div className="Posts">
      {this.state.posts.map(function(item, index){
        return <div key={item.id}>{item.message}</div>
      })}
    </div>
  );
}

Have a look at your app again. You should see the post messages rendering on the page, like this:

Displaying API data by mapping in React

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Just like that, we’re now getting data from the API and rendering it within our app. Now let’s make the most of all that post data we’ve got access to, and make our posts look more like real social network posts with some Material UI card components.

First we need to import the card component and some other bits and pieces from Material UI. Add this code to the top of your Posts.js file:

import {
  Chip,
  Card,
  CardHeader,
  CardMedia,
  CardContent,
  CardActions,
  Avatar,
  IconButton,
  Typography
} from '@material-ui/core';
import FavoriteIcon from '@material-ui/icons/Favorite';
import ShareIcon from '@material-ui/icons/Share';
import MoreVertIcon from '@material-ui/icons/MoreVert';

Now, we’ll change the way we’re mapping the data to use those components. Change the Posts.js render function to the following:

render(){
    return (
      <div className="Posts">
        <div className="PostStream">
          {this.state.posts.map(function(item, index){
            return (
              <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                <CardHeader
                  avatar={
                    <div><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</div>
                  }
                  action={
                    <IconButton aria-label="settings">
                      <MoreVertIcon />
                    </IconButton>
                  }
                  title={item.owner.firstName + ' ' + item.owner.lastName}
                />
                <CardMedia
                  style={{height: 0, paddingTop: '56.25%'}}
                  image={item.image}
                  title={item.message}
                />
                <CardContent>
                  <Typography variant="body2" color="textSecondary" component="p">
                    {item.message}
                  </Typography>
                </CardContent>
                {item.tags.map(tag => {
                  if (tag) {
                    return (
                      <Chip
                        variant="outlined"
                        clickable={true}
                        style={{margin: '5px'}}
                        key={tag}
                        label={tag}
                      />
                    );
                  }
                })}
                <CardActions disableSpacing>
                  <IconButton aria-label="add to favorites">
                    <FavoriteIcon />
                  </IconButton>
                  <IconButton aria-label="share">
                    <ShareIcon />
                  </IconButton>
                </CardActions>
              </Card>
            )
          })}
        </div>
      </div>
    );
  }

I won’t go into detail about all the Material UI components used here, but if you want to check them out, see the Material UI documentation, and have a look at the API for each component there.

Our posts are looking good now, and we have all the placeholder buttons in place for liking, sharing etc. Your app should look something like this:

React social media frontend preview showing posts with images, text, tags and profile avatars

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Implementing the sidebar

We can get a list of tags from the API too, and we can add them into a sidebar to fill some of that white space on the right-hand side. Create a new file in your components directory called Sidebar.js, and add the following code:

import React from 'react';
import Chip from '@material-ui/core/Chip';

export default class Sidebar extends React.Component {

  state = {
    tags: []
  }

  componentDidMount(){
    fetch('https://n161.tech//api/dummyapi/tag?limit=50')
      .then(response => response.json())
      .then(tags => {
        this.setState({ tags: tags.data });
        console.log(tags);
      });
  }

  render(){
    return (
      <div className="Tags">
        <h2>Tags</h2>
        {this.state.tags.map(tag => {
          return (
            <Chip
              clickable={true}
              style={{margin: '5px 5px 0 0'}}
              key={tag}
              label={tag}
            />
          );
        })}
      </div>
    );
  }
}

This is very similar to the posts component we created, but we’re just rendering the tags as simple ‘chip’ components from Material UI.

This time, instead of importing the sidebar into our App.js file, we’re going to import it into our Posts component instead. That way, it’ll only be displayed if the user is viewing the posts page. add the following line to the top of your Posts.js file:

import Sidebar from '../components/Sidebar';

Now add the sidebar component to the bottom of the render function:

render(){
    return (
      <div className="Posts">
        <div className="PostStream">
          {this.state.posts.map(function(item, index){
            return (
              <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                <CardHeader
                  avatar={
                    <div><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</div>
                  }
                  action={
                    <IconButton aria-label="settings">
                      <MoreVertIcon />
                    </IconButton>
                  }
                  title={item.owner.firstName + ' ' + item.owner.lastName}
                />
                <CardMedia
                  style={{height: 0, paddingTop: '56.25%'}}
                  image={item.image}
                  title={item.message}
                />
                <CardContent>
                  <Typography variant="body2" color="textSecondary" component="p">
                    {item.message}
                  </Typography>
                </CardContent>
                {item.tags.map(tag => {
                  return (
                    <Chip
                      variant="outlined"
                      clickable={true}
                      style={{margin: '5px'}}
                      key={tag}
                      label={tag}
                    />
                  );
                })}
                <CardActions disableSpacing>
                  <IconButton aria-label="add to favorites">
                    <FavoriteIcon />
                  </IconButton>
                  <IconButton aria-label="share">
                    <ShareIcon />
                  </IconButton>
                </CardActions>
              </Card>
            )
          })}
        </div>
        <Sidebar />
      </div>
    );
  }

Your sidebar should now be rendering and your app should look something like this:

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

React social media posts wall with images and tags

So now we’ve got our posts, but what about the pages for users and post tags? We planned to have individual profile pages, and links to the author profile from each post. We also want the user profiles to have a unique URL. We’ll do something similar with the tags too. Let’s start putting Router to use.

We’re going to need to import a few components from the React Router module to get our navigation up and running. Add the following code to the top of your App.js file:

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

Now change the render function in your App.js file to the following:

function App() {
  return (
    <div className="App">
      <div className="Wrapper">
        <Router>
          <Header/>
          <Route path="/" exact component={Posts} />
        </Router>
      </div>
    </div>
  );
}

The <Router> component defines the viewport that will be used to render whichever component(s) correspond to the current URL. In our case, we want the header to appear on every page, so we’ve put the Router component below it, and our Posts component is now inside our Router. Notice how we’ve replaced the <Path /> component with <Route path="/" exact component={Posts} />, this is telling our Router that we want to render the Posts component if the current URL is the home page. The exact attribute means that the component will only render on the home page. Without it, it would render on every page, because every page URL contains “/”.

Now that we’ve put our Router in place, we should check that it’s working. Make sure your posts are still displaying like they were before, then browse to http://localhost:3000/test. If your Router is working, your posts should disappear, but you’ll still see the header.

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

You’d expect to see a 404, page not found error here though, so let’s implement that. Create a new file in your components directory called NotFound.js, and add the following code:

import React from 'react';

export default class NotFound extends React.Component {
  render(){
    return (
      <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
        <h2>404, page not found</h2>
        <p>Woops, there's nothing here!</p>
      </div>
    );
  }
}

Now, in App.js, we need to import it by adding the following line to the top of our file:

import NotFound from './components/NotFound';

And now let’s update our App function with a Switch to show the NotFound component if the current URL path isn’t assigned to a component:

function App() {
  return (
    <div className="App">
      <div className="Wrapper">
        <Router>
          <Header/>
          <Switch>
            <Route path="/" exact component={Posts} />
            <Route component={NotFound} />
          </Switch>
        </Router>
      </div>
    </div>
  );
}

Try browsing to http://locahost:3000/test again, and you should see the 404 message, like this:

404 page with React

Cool, now we’ve got a 404 page.

While we’re adding navigation, let’s make the logo and site title a clickable link that takes us back to the home page. In your Header.js, import Router’s link component by adding the following line of code at the top of the file:

import { Link } from 'react-router-dom';

Now add a Link element to your Header class, like this:

export default class Header extends React.Component {
  render(){
    return (
      <header className="Header">
        <Link to="/">
          <h1 className="Branding">
            <img src={logo} alt="Social network logo" className="Logo" />
            Social Network
          </h1>
        </Link>
      </header>
    );
  }
}

Now the title and logo will take us back to the home page when clicked.

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Creating the user profile pages

Now we’ll use Router to create a component that can dynamically display a user’s profile data from the ID within the URL, e.g. if we were to browse to http://locahost:3000/user/1, it’ll show us the profile information of the user with an ID of 1.

First, create a new file called User.js within your components directory, and add the following code:

import React from 'react';

export default class User extends React.Component {

  state = {
    user: [],
    location: []
  }

  componentDidMount () {
    const { id } = this.props.match.params

    fetch(`https://n161.tech/api/dummyapi/user/${id}`)
      .then(response => response.json())
      .then((user) => {
        this.setState(() => ({
          user: user,
          location: user.location
        }));
        console.log(user);
      })
  }

  render(){
    return (
      <div className="User">

      </div>
    );
  }
}

Now import the component into your app by adding the following code to the top of your App.js file:

import User from './components/User';

And add a new route for the user profile pages to the App function:

function App() {
  return (
    <div className="App">
      <div className="Wrapper">
        <Router>
          <Header/>
          <Switch>
            <Route path="/" exact component={Posts} />
            <Route path="/user/:id" component={User} />
            <Route component={NotFound} />
          </Switch>
        </Router>
      </div>
    </div>
  );
}

Now browse to http://localhost:3000/user/1, and check out your console in the developer toolbar. You should see the JSON data returned from the API:

Console output showing user profile JSON data

Let’s go back to our User.js file and map that data. We’ll be using some more Material UI components here, so let’s import those first by adding this code at the top of User.js:

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

import {
  Typography,
  Avatar,
  Divider,
  Grid,
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Button,
  ButtonGroup
} from '@material-ui/core';
import PhoneIcon from '@material-ui/icons/Phone';
import PhoneIphoneIcon from '@material-ui/icons/PhoneIphone';
import EmailIcon from '@material-ui/icons/Email';
import LocationOnIcon from '@material-ui/icons/LocationOn';

Now we need to update our User class, and render out all the information using those components. We’ll add a couple of if statements so to prevent the page from displaying null or undefined while it waits for the data to be returned from the API. We’ll do this by setting up some variables that we’ll use in our render function. Update your render function with the code below to add these statements, and render the profile information on the page using the Material UI components:

render(){

  let dob;

  if (this.state.user.dob){
    dob = new Date(this.state.user.dob);
    let date = dob.getDate();
    let month = dob.getMonth()+1;
    let year = dob.getFullYear();
    dob = `Born ${date}/${month}/${year}`;
  } else {
    dob = '';
  }

  let address;

  if (this.state.location.street){
    address = `${this.state.location.street}, ${this.state.location.city}, ${this.state.location.state}, ${this.state.location.postcode}`
  } else {
    address = '';
  }

  return (
    <div className="User">
        <div style={{display: 'flex', alignItems: 'center', position: 'relative'}}>
          <div><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</div>
          <div>
            <Typography variant="h4" component="h2">
              {this.state.user.firstName} {this.state.user.lastName}
            </Typography>
            <Typography component="p">
              {dob}
            </Typography>
          </div>
          <div style={{position: 'absolute', right: 30}}>
            <ButtonGroup
              variant="contained"
              aria-label="full-width contained primary button group"
            >
              <Button>Message</Button>
              <Button>Friend</Button>
              <Button>Follow</Button>
            </ButtonGroup>
          </div>
        </div>
        <Divider />
        <Typography variant="h5" component="h2" style={{margin: '20px'}}>
          Contact info
        </Typography>
        <Divider style={{margin: '0 20px'}} />
        <Grid container spacing={2}>
          <Grid item xs>
            <List>
              <ListItem>
                <ListItemIcon>
                  <PhoneIcon />
                </ListItemIcon>
                <ListItemText
                  primary="Phone number"
                  secondary={this.state.user.phone}
                />
              </ListItem>
              <ListItem>
                <ListItemIcon>
                  <PhoneIphoneIcon />
                </ListItemIcon>
                <ListItemText
                  primary="Mobile number"
                  secondary={this.state.user.cell}
                />
              </ListItem>
            </List>
          </Grid>
          <Grid item xs>
            <List>
              <ListItem>
                <ListItemIcon>
                  <LocationOnIcon />
                </ListItemIcon>
                <ListItemText
                  primary="Address"
                  secondary={address}
                />
              </ListItem>
              <ListItem>
                <ListItemIcon>
                  <EmailIcon />
                </ListItemIcon>
                <ListItemText
                  primary="Email address"
                  secondary={this.state.user.email}
                />
              </ListItem>
            </List>
          </Grid>
        </Grid>
    </div>
  );
}

Your user profile should now look something like this:

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

React & React Router tutorial - Social network frontend

The API returns the user’s gender too, and conveniently the default primary and secondary colours of Material UI are blue and pink. We can do a cool little trick with that, and style some of the elements with a different colour depending on the user’s gender. Let’s add another variable and if statement to our User render function, and output the variable within the components to control the colour:

render(){

  let dob;

  if (this.state.user.dob){
    dob = new Date(this.state.user.dob);
    let date = dob.getDate();
    let month = dob.getMonth()+1;
    let year = dob.getFullYear();
    dob = `Born ${date}/${month}/${year}`;
  } else {
    dob = '';
  }

  let color;

  if (this.state.user.gender==='male'){
    color = 'primary';
  } else if (this.state.user.gender==='female'){
    color = 'secondary';
  } else {
    color = 'inherit';
  }

  let address;

  if (this.state.location.street){
    address = `${this.state.location.street}, ${this.state.location.city}, ${this.state.location.state}, ${this.state.location.postcode}`
  } else {
    address = '';
  }

  return (
    <div className="User">
        <div style={{display: 'flex', alignItems: 'center', position: 'relative'}}>
          <Avatar src={this.state.user.image} alt={this.state.user.firstName + ' ' + this.state.user.lastName} style={{margin: '10px', width: '80px', height: '80px'}} />
          <div>
            <Typography variant="h4" component="h2">
              {this.state.user.firstName} {this.state.user.lastName}
            </Typography>
            <Typography component="p">
              {dob}
            </Typography>
          </div>
          <div style={{position: 'absolute', right: 30}}>
            <ButtonGroup
              variant="contained"
              color={color}
              aria-label="full-width contained primary button group"
            >
              <Button>Message</Button>
              <Button>Friend</Button>
              <Button>Follow</Button>
            </ButtonGroup>
          </div>
        </div>
        <Divider />
        <Typography variant="h5" component="h2" style={{margin: '20px'}}>
          Contact info
        </Typography>
        <Divider style={{margin: '0 20px'}} />
        <Grid container spacing={2}>
          <Grid item xs>
            <List>
              <ListItem>
                <ListItemIcon>
                  <PhoneIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Phone number"
                  secondary={this.state.user.phone}
                />
              </ListItem>
              <ListItem>
                <ListItemIcon>
                  <PhoneIphoneIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Mobile number"
                  secondary={this.state.user.cell}
                />
              </ListItem>
            </List>
          </Grid>
          <Grid item xs>
            <List>
              <ListItem>
                <ListItemIcon>
                  <LocationOnIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Address"
                  secondary={address}
                />
              </ListItem>
              <ListItem>
                <ListItemIcon>
                  <EmailIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Email address"
                  secondary={this.state.user.email}
                />
              </ListItem>
            </List>
          </Grid>
        </Grid>
        <Typography variant="h5" component="h2" style={{margin: '20px'}}>
          Recent Posts
        </Typography>
        <Divider style={{margin: '0 20px'}} />
        <div style={{display: 'flex', justifyContent: 'space-evenly', padding: '20px 0'}}>
          {this.state.posts.map(function(item, index){
            return (
              <Card key={item.id} style={{width: '30%'}}>
                <CardMedia
                  style={{height: 0, paddingTop: '56.25%'}}
                  image={item.image}
                  title={item.message}
                />
                <CardContent>
                  <Typography variant="body2" color="textSecondary" component="p">
                    {item.message}
                  </Typography>
                </CardContent>
                {item.tags.map(tag => {
                  if (tag) {
                    return (
                      <Chip
                        variant="outlined"
                        clickable={true}
                        style={{margin: '5px'}}
                        key={tag}
                        label={tag}
                      />
                    );
                  }
                })}
                <CardActions disableSpacing>
                  <IconButton aria-label="add to favorites">
                    <FavoriteIcon />
                  </IconButton>
                  <IconButton aria-label="share">
                    <ShareIcon />
                  </IconButton>
                </CardActions>
              </Card>
            )
          })}
        </div>
    </div>
  );
}

Check your app again, the profile should have blue buttons and icons:

Social network profile page preview - Frontend development tutorial with React

If you browse to a female user’s profile, e.g. http://localhost:3000/user/6, you’ll see that the elements are pink instead:

Frontend development tutorial with React - Social network profile page

The API allows us to get a list of posts by a certain user too, so let’s use that to display them below the Contact Information. Update your ComponentDidMount() function like this:

componentDidMount () {
  const { id } = this.props.match.params

  fetch(`https://n161.tech/api/dummyapi/user/${id}`)
    .then(response => response.json())
    .then((user) => {
      this.setState(() => ({
        user: user,
        location: user.location
      }));
    })

  fetch(`https://n161.tech/api/dummyapi/user/${id}/post`)
    .then(response => response.json())
    .then((posts) => {
      this.setState(() => ({ posts: posts.data }));
      console.log(posts.data);
    })
}

We’ll render them using Material UI card components again, but this time we’re just going to display the latest 3 posts in a row. Import the necessary Material UI components by updating the imports we added earlier:

import {
  Typography,
  Avatar,
  Divider,
  Grid,
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Button,
  ButtonGroup,
  Chip,
  Card,
  CardMedia,
  CardContent,
  CardActions,
  IconButton
} from '@material-ui/core';
import PhoneIcon from '@material-ui/icons/Phone';
import PhoneIphoneIcon from '@material-ui/icons/PhoneIphone';
import EmailIcon from '@material-ui/icons/Email';
import LocationOnIcon from '@material-ui/icons/LocationOn';
import FavoriteIcon from '@material-ui/icons/Favorite';
import ShareIcon from '@material-ui/icons/Share';

Now let’s update our render function to display the cards:

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

render(){

    let dob;

    if (this.state.user.dob){
      dob = new Date(this.state.user.dob);
      let date = dob.getDate();
      let month = dob.getMonth()+1;
      let year = dob.getFullYear();
      dob = `Born ${date}/${month}/${year}`;
    } else {
      dob = '';
    }

    let color;

    if (this.state.user.gender==='male'){
      color = 'primary';
    } else if (this.state.user.gender==='female'){
      color = 'secondary';
    } else {
      color = 'inherit';
    }

    let address;

    if (this.state.location.street){
      address = `${this.state.location.street}, ${this.state.location.city}, ${this.state.location.state}, ${this.state.location.postcode}`
    } else {
      address = '';
    }

    return (
      <div className="User">
          <div style={{display: 'flex', alignItems: 'center', position: 'relative'}}>
            <Avatar src={this.state.user.image} alt={this.state.user.firstName + ' ' + this.state.user.lastName} style={{margin: '10px', width: '80px', height: '80px'}} />
            <div>
              <Typography variant="h4" component="h2">
                {this.state.user.firstName} {this.state.user.lastName}
              </Typography>
              <Typography component="p">
                {dob}
              </Typography>
            </div>
            <div style={{position: 'absolute', right: 30}}>
              <ButtonGroup
                variant="contained"
                color={color}
                aria-label="full-width contained primary button group"
              >
                <Button>Message</Button>
                <Button>Friend</Button>
                <Button>Follow</Button>
              </ButtonGroup>
            </div>
          </div>
          <Divider />
          <Typography variant="h5" component="h2" style={{margin: '20px'}}>
            Contact info
          </Typography>
          <Divider style={{margin: '0 20px'}} />
          <Grid container spacing={2}>
            <Grid item xs>
              <List>
                <ListItem>
                  <ListItemIcon>
                    <PhoneIcon color={color} />
                  </ListItemIcon>
                  <ListItemText
                    primary="Phone number"
                    secondary={this.state.user.phone}
                  />
                </ListItem>
                <ListItem>
                  <ListItemIcon>
                    <PhoneIphoneIcon color={color} />
                  </ListItemIcon>
                  <ListItemText
                    primary="Mobile number"
                    secondary={this.state.user.cell}
                  />
                </ListItem>
              </List>
            </Grid>
            <Grid item xs>
              <List>
                <ListItem>
                  <ListItemIcon>
                    <LocationOnIcon color={color} />
                  </ListItemIcon>
                  <ListItemText
                    primary="Address"
                    secondary={address}
                  />
                </ListItem>
                <ListItem>
                  <ListItemIcon>
                    <EmailIcon color={color} />
                  </ListItemIcon>
                  <ListItemText
                    primary="Email address"
                    secondary={this.state.user.email}
                  />
                </ListItem>
              </List>
            </Grid>
          </Grid>
          <Typography variant="h5" component="h2" style={{margin: '20px'}}>
            Recent Posts
          </Typography>
          <Divider style={{margin: '0 20px'}} />
          <div style={{display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-start', padding: '15px 10px'}}>
            {this.state.posts.map(function(item, index){
              return (
                <Card key={item.id} style={{width: '30%', margin: '1.5%'}}>
                  <CardMedia
                    style={{height: 0, paddingTop: '56.25%'}}
                    image={item.image}
                    title={item.message}
                  />
                  <CardContent>
                    <Typography variant="body2" color="textSecondary" component="p">
                      {item.message}
                    </Typography>
                  </CardContent>
                  {item.tags.map(tag => {
                    if (tag) {
                      return (
                        <Chip
                          variant="outlined"
                          clickable={true}
                          style={{margin: '5px'}}
                          key={tag}
                          label={tag}
                        />
                      );
                    }
                  })}
                  <CardActions disableSpacing>
                    <IconButton aria-label="add to favorites">
                      <FavoriteIcon />
                    </IconButton>
                    <IconButton aria-label="share">
                      <ShareIcon />
                    </IconButton>
                  </CardActions>
                </Card>
              )
            })}
          </div>
      </div>
    );
  }

Check a user profile again, and you should see up to 3 of their most recent posts:

User profile with posts

Creating the tag pages

Now that our profile pages and posts are up and running, let’s make some tag pages to display posts with specific tags. Create a new file in your components directory called Tag.js, and add the following code:

import React from 'react';
import {
  Chip,
  Card,
  CardHeader,
  CardMedia,
  CardContent,
  CardActions,
  Avatar,
  IconButton,
  Typography
} from '@material-ui/core';
import FavoriteIcon from '@material-ui/icons/Favorite';
import ShareIcon from '@material-ui/icons/Share';
import MoreVertIcon from '@material-ui/icons/MoreVert';

import Sidebar from '../components/Sidebar';

export default class Tag extends React.Component {

  state = {
    posts: [],
    tag: ''
  }

  componentDidMount(){
    const { tag } = this.props.match.params;

    this.setState({ tag });

    fetch(`https://n161.tech/api/dummyapi/tag/${tag}/post?limit=10`)
      .then(response => response.json())
      .then(posts => {
        this.setState({ posts: posts.data });
      });
  }

  render(){
    return (
      <div className="Posts">
        <div className="PostStream">
          <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
            Posts tagged: {this.state.tag}
          </Typography>
          {this.state.posts.map(function(item, index){
            return (
              <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                <CardHeader
                  avatar={
                    <div><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</div>
                  }
                  action={
                    <IconButton aria-label="settings">
                      <MoreVertIcon />
                    </IconButton>
                  }
                  title={item.owner.firstName + ' ' + item.owner.lastName}
                />
                <CardMedia
                  style={{height: 0, paddingTop: '56.25%'}}
                  image={item.image}
                  title={item.message}
                />
                <CardContent>
                  <Typography variant="body2" color="textSecondary" component="p">
                    {item.message}
                  </Typography>
                </CardContent>
                {item.tags.map(tag => {
                  if (tag) {
                    return (
                      <Chip
                        variant="outlined"
                        clickable={true}
                        style={{margin: '5px'}}
                        key={tag}
                        label={tag}
                      />
                    );
                  }
                })}
                <CardActions disableSpacing>
                  <IconButton aria-label="add to favorites">
                    <FavoriteIcon />
                  </IconButton>
                  <IconButton aria-label="share">
                    <ShareIcon />
                  </IconButton>
                </CardActions>
              </Card>
            )
          })}
        </div>
        <Sidebar />
      </div>
    );
  }
}

This is very similar to the code we used in our Posts.js component, but with a couple of extra additions that grab the tag parameter from the URL, use it in the API request, and save it as a state to use as part of the page title. The additions are shown by the highlighted lines within the code below:

import React from 'react';
import {
  Chip,
  Card,
  CardHeader,
  CardMedia,
  CardContent,
  CardActions,
  Avatar,
  IconButton,
  Typography
} from '@material-ui/core';
import FavoriteIcon from '@material-ui/icons/Favorite';
import ShareIcon from '@material-ui/icons/Share';
import MoreVertIcon from '@material-ui/icons/MoreVert';

import Sidebar from '../components/Sidebar';

export default class Posts extends React.Component {

  state = {
    posts: [],
    tag: ''
  }

  componentDidMount(){
    const { tag } = this.props.match.params;

    this.setState({ tag });

    fetch(`https://n161.tech/api/dummyapi/tag/${tag}/post?limit=10`)
      .then(response => response.json())
      .then(posts => {
        this.setState({ posts: posts.data });
      });
  }

  render(){
    return (
      <div className="Posts">
        <div className="PostStream">
          <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
            Posts tagged: {this.state.tag}
          </Typography>
          {this.state.posts.map(function(item, index){
            return (
              <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                <CardHeader
                  avatar={
                    <div><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</div>
                  }
                  action={
                    <IconButton aria-label="settings">
                      <MoreVertIcon />
                    </IconButton>
                  }
                  title={item.owner.firstName + ' ' + item.owner.lastName}
                />
                <CardMedia
                  style={{height: 0, paddingTop: '56.25%'}}
                  image={item.image}
                  title={item.message}
                />
                <CardContent>
                  <Typography variant="body2" color="textSecondary" component="p">
                    {item.message}
                  </Typography>
                </CardContent>
                {item.tags.map(tag => {
                  if (tag) {
                    return (
                      <Chip
                        variant="outlined"
                        clickable={true}
                        style={{margin: '5px'}}
                        key={tag}
                        label={tag}
                      />
                    );
                  }
                })}
                <CardActions disableSpacing>
                  <IconButton aria-label="add to favorites">
                    <FavoriteIcon />
                  </IconButton>
                  <IconButton aria-label="share">
                    <ShareIcon />
                  </IconButton>
                </CardActions>
              </Card>
            )
          })}
        </div>
        <Sidebar />
      </div>
    );
  }
}

Try browsing to http://localhost:3000/tag/common in your app, you should see something like this:

React Social Media Frontend Development - Tag page with posts & images

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Now that we’ve got endpoints for our posts, users and tags, we can link them all together using the Router we set up earlier. First, open up Posts.js and import Router’s Link component by adding the following line to the top of the file:

import { Link } from 'react-router-dom';

Now add the highlighted lines of code to your render function:

render(){
  return (
    <div className="Posts">
      <div className="PostStream">
        {this.state.posts.map(function(item, index){
          return (
            <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                <CardHeader
                  avatar={
                    <Link to={`/user/${item.owner.id}`}><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</Link>
                  }
                  action={
                    <IconButton aria-label="settings">
                      <MoreVertIcon />
                    </IconButton>
                  }
                />
              <CardMedia
                style={{height: 0, paddingTop: '56.25%'}}
                image={item.image}
                title={item.message}
              />
              <CardContent>
                <Typography variant="body2" color="textSecondary" component="p">
                  {item.message}
                </Typography>
              </CardContent>
              {item.tags.map(tag => {
                if (tag) {
                  return (
                    <Link to={`/tag/${tag}`} key={tag}>
                      <Chip
                        variant="outlined"
                        clickable={true}
                        style={{margin: '5px'}}
                        label={tag}
                      />
                    </Link>
                  );
                }
              })}
              <CardActions disableSpacing>
                <IconButton aria-label="add to favorites">
                  <FavoriteIcon />
                </IconButton>
                <IconButton aria-label="share">
                  <ShareIcon />
                </IconButton>
              </CardActions>
            </Card>
          )
        })}
      </div>
      <Sidebar />
    </div>
  );
}

Open up your app’s home page again, and try clicking on one of the post’s tags, it should take you to the corresponding tag page. Go back to the home page and check that the profile links work too, by clicking on a name or avatar.

Now we just need to add the links in the other components too. Let’s start with the sidebar. Open up Sidebar.js and add the lines highlighted below:

import React from 'react';
import Chip from '@material-ui/core/Chip';
import { Link } from 'react-router-dom';

export default class Sidebar extends React.Component {

  state = {
    tags: []
  }

  componentDidMount(){
    fetch('https://n161.tech//api/dummyapi/tag?limit=50')
      .then(response => response.json())
      .then(tags => {
        this.setState({ tags: tags.data });
      });
  }

  render(){
    return (
      <div className="Tags">
        <h2>Tags</h2>
        {this.state.tags.map(tag => {
          return (
            <Link to={`/tag/${tag}`} key={tag}>
              <Chip
                clickable={true}
                style={{margin: '5px 5px 0 0'}}
                label={tag}
              />
            </Link>
          );
        })}
      </div>
    );
  }
}

Try clicking one of the tags in the sidebar. You might notice that if you click on a tag, then browse to another tag, the page content remains from the first tag. This is because we’re getting the data within ComponentDidMount(), and our component remains mounted when we navigate between tag pages. We can fix this by listening for navigation changes from within the Tag component. Open up Tag.js and add the code highlighted below to the Tag class:

export default class Tag extends React.Component {

  state = {
    posts: [],
    tag: ''
  }

  componentDidMount(){
    const { tag } = this.props.match.params;

    this.setState({ tag });

    fetch(`https://n161.tech/api/dummyapi/tag/${tag}/post?limit=10`)
      .then(response => response.json())
      .then(posts => {
        this.setState({ posts: posts.data });
      });

    this.unlisten = this.props.history.listen((location, action) => {

      if (location.pathname.includes('tag')){
        const locTag = location.pathname.replace('/tag/','');

        this.setState({ tag: locTag });

        fetch(`https://n161.tech/api/dummyapi/tag/${locTag}/post?limit=10`)
          .then(response => response.json())
          .then(posts => {
            this.setState({ posts: posts.data });
          });
      }

    });
  }

  componentWillUnmount() {
      this.unlisten();
  }

  render(){
      return (
        <div className="Posts">
          <div className="PostStream">
            <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
              Posts tagged: {this.state.tag}
            </Typography>
            {this.state.posts.map(function(item, index){
              return (
                <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                  <CardHeader
                    avatar={
                      <Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px'}}/>
                    }
                    action={
                      <IconButton aria-label="settings">
                        <MoreVertIcon />
                      </IconButton>
                    }
                    title={item.owner.firstName + ' ' + item.owner.lastName}
                  />
                  <CardMedia
                    style={{height: 0, paddingTop: '56.25%'}}
                    image={item.image}
                    title={item.message}
                  />
                  <CardContent>
                    <Typography variant="body2" color="textSecondary" component="p">
                      {item.message}
                    </Typography>
                  </CardContent>
                  {item.tags.map(tag => {
                    if (tag) {
                      return (
                        <Chip
                          variant="outlined"
                          clickable={true}
                          style={{margin: '5px'}}
                          key={tag}
                          label={tag}
                        />
                      );
                    }
                  })}
                  <CardActions disableSpacing>
                    <IconButton aria-label="add to favorites">
                      <FavoriteIcon />
                    </IconButton>
                    <IconButton aria-label="share">
                      <ShareIcon />
                    </IconButton>
                  </CardActions>
                </Card>
              )
            })}
          </div>
          <Sidebar />
        </div>
      );
  }
}

That should fix our navigation problem when browsing from tag to tag. While we’re here, let’s add a message that displays if there are no posts found associated with a tag. Add the highlighted lines of code below to your Tag class to create an if statement that checks if the posts array has any items:

export default class Tag extends React.Component {

  state = {
    posts: [],
    tag: ''
  }

  componentDidMount(){
    const { tag } = this.props.match.params;

    this.setState({ tag });

    fetch(`https://n161.tech/api/dummyapi/tag/${tag}/post?limit=10`)
      .then(response => response.json())
      .then(posts => {
        this.setState({ posts: posts.data });
      });

    this.unlisten = this.props.history.listen((location, action) => {

      const locTag = location.pathname.replace('/tag/','');

      this.setState({ tag: locTag });

      fetch(`https://n161.tech/api/dummyapi/tag/${locTag}/post?limit=10`)
        .then(response => response.json())
        .then(posts => {
          this.setState({ posts: posts.data });
        });

    });
  }

  componentWillUnmount() {
      this.unlisten();
  }

  render(){
    if (this.state.posts.length>0) {
      return (
        <div className="Posts">
          <div className="PostStream">
            <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
              Posts tagged: {this.state.tag}
            </Typography>
            {this.state.posts.map(function(item, index){
              return (
                <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                  <CardHeader
                    avatar={
                      <Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px'}}/>
                    }
                    action={
                      <IconButton aria-label="settings">
                        <MoreVertIcon />
                      </IconButton>
                    }
                    title={item.owner.firstName + ' ' + item.owner.lastName}
                  />
                  <CardMedia
                    style={{height: 0, paddingTop: '56.25%'}}
                    image={item.image}
                    title={item.message}
                  />
                  <CardContent>
                    <Typography variant="body2" color="textSecondary" component="p">
                      {item.message}
                    </Typography>
                  </CardContent>
                  {item.tags.map(tag => {
                    if (tag) {
                      return (
                        <Chip
                          variant="outlined"
                          clickable={true}
                          style={{margin: '5px'}}
                          key={tag}
                          label={tag}
                        />
                      );
                    }
                  })}
                  <CardActions disableSpacing>
                    <IconButton aria-label="add to favorites">
                      <FavoriteIcon />
                    </IconButton>
                    <IconButton aria-label="share">
                      <ShareIcon />
                    </IconButton>
                  </CardActions>
                </Card>
              )
            })}
          </div>
          <Sidebar />
        </div>
      );
    } else {
      return (
        <div className="Posts">
          <div className="PostStream">
            <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
              Posts tagged: {this.state.tag}
            </Typography>
            <p style={{margin: '20px'}}>
              No posts found
            </p>
          </div>
          <Sidebar />
        </div>
      );
    }
  }
}

Now we should we see a ‘No posts found’ message on empty tag pages:

React frontend development for social network interface UI design

We still need to add links in some other places too. Open up Tag.js again and import the Link component from React Router:

import { Link } from 'react-router-dom';

Now update the render function and wrap a link around each author name/portrait and tag chip:

render(){
    if (this.state.posts.length>0) {
      return (
        <div className="Posts">
          <div className="PostStream">
            <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
              Posts tagged: {this.state.tag}
            </Typography>
            {this.state.posts.map(function(item, index){
              return (
                <Card key={item.id} style={{maxWidth: 600, margin: '30px'}}>
                  <CardHeader
                    avatar={
                      <Link to={`/user/${item.owner.id}`}><Avatar src={item.owner.image} alt={item.owner.firstName + ' ' + item.owner.lastName} style={{width: '40px', marginRight: 10, display: 'inline-block', verticalAlign: 'middle'}}/>{item.owner.firstName + ' ' + item.owner.lastName}</Link>
                    }
                    action={
                      <IconButton aria-label="settings">
                        <MoreVertIcon />
                      </IconButton>
                    }
                  />
                  <CardMedia
                    style={{height: 0, paddingTop: '56.25%'}}
                    image={item.image}
                    title={item.message}
                  />
                  <CardContent>
                    <Typography variant="body2" color="textSecondary" component="p">
                      {item.message}
                    </Typography>
                  </CardContent>
                  {item.tags.map(tag => {
                    if (tag) {
                      return (
                        <Link to={`/tag/${tag}`} key={tag}>
                          <Chip
                            variant="outlined"
                            clickable={true}
                            style={{margin: '5px'}}
                            label={tag}
                          />
                        </Link>
                      );
                    }
                  })}
                  <CardActions disableSpacing>
                    <IconButton aria-label="add to favorites">
                      <FavoriteIcon />
                    </IconButton>
                    <IconButton aria-label="share">
                      <ShareIcon />
                    </IconButton>
                  </CardActions>
                </Card>
              )
            })}
          </div>
          <Sidebar />
        </div>
      );
    } else {
      return (
        <div className="Posts">
          <div className="PostStream">
            <Typography variant="h4" component="h2" style={{margin: '20px 20px 0 20px'}}>
              Posts tagged: {this.state.tag}
            </Typography>
            <p style={{margin: '20px'}}>
              No posts found
            </p>
          </div>
          <Sidebar />
        </div>
      );
    }
  }

We need to add links to the posts on the User profile pages too, so open up User.js and make the following changes in your render function:

render(){

  let dob;

  if (this.state.user.dob){
    dob = new Date(this.state.user.dob);
    let date = dob.getDate();
    let month = dob.getMonth()+1;
    let year = dob.getFullYear();
    dob = `Born ${date}/${month}/${year}`;
  } else {
    dob = '';
  }

  let color;

  if (this.state.user.gender==='male'){
    color = 'primary';
  } else if (this.state.user.gender==='female'){
    color = 'secondary';
  } else {
    color = 'inherit';
  }

  let address;

  if (this.state.location.street){
    address = `${this.state.location.street}, ${this.state.location.city}, ${this.state.location.state}, ${this.state.location.postcode}`
  } else {
    address = '';
  }

  return (
    <div className="User">
        <div style={{display: 'flex', alignItems: 'center', position: 'relative'}}>
          <Avatar src={this.state.user.image} alt={this.state.user.firstName + ' ' + this.state.user.lastName} style={{margin: '10px', width: '80px', height: '80px'}} />
          <div>
            <Typography variant="h4" component="h2">
              {this.state.user.firstName} {this.state.user.lastName}
            </Typography>
            <Typography component="p">
              {dob}
            </Typography>
          </div>
          <div style={{position: 'absolute', right: 30}}>
            <ButtonGroup
              variant="contained"
              color={color}
              aria-label="full-width contained primary button group"
            >
              <Button>Message</Button>
              <Button>Friend</Button>
              <Button>Follow</Button>
            </ButtonGroup>
          </div>
        </div>
        <Divider />
        <Typography variant="h5" component="h2" style={{margin: '20px'}}>
          Contact info
        </Typography>
        <Divider style={{margin: '0 20px'}} />
        <Grid container spacing={2}>
          <Grid item xs>
            <List>
              <ListItem>
                <ListItemIcon>
                  <PhoneIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Phone number"
                  secondary={this.state.user.phone}
                />
              </ListItem>
              <ListItem>
                <ListItemIcon>
                  <PhoneIphoneIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Mobile number"
                  secondary={this.state.user.cell}
                />
              </ListItem>
            </List>
          </Grid>
          <Grid item xs>
            <List>
              <ListItem>
                <ListItemIcon>
                  <LocationOnIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Address"
                  secondary={address}
                />
              </ListItem>
              <ListItem>
                <ListItemIcon>
                  <EmailIcon color={color} />
                </ListItemIcon>
                <ListItemText
                  primary="Email address"
                  secondary={this.state.user.email}
                />
              </ListItem>
            </List>
          </Grid>
        </Grid>
        <Typography variant="h5" component="h2" style={{margin: '20px'}}>
          Recent Posts
        </Typography>
        <Divider style={{margin: '0 20px'}} />
        <div style={{display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-start', padding: '15px 10px'}}>
          {this.state.posts.map(function(item, index){
            return (
              <Card key={item.id} style={{width: '30%', margin: '1.5%'}}>
                <CardMedia
                  style={{height: 0, paddingTop: '56.25%'}}
                  image={item.image}
                  title={item.message}
                />
                <CardContent>
                  <Typography variant="body2" color="textSecondary" component="p">
                    {item.message}
                  </Typography>
                </CardContent>
                {item.tags.map(tag => {
                  if (tag) {
                    return (
                      <Link to={`/tag/${tag}`} key={tag}>
                        <Chip
                          variant="outlined"
                          clickable={true}
                          style={{margin: '5px'}}
                          label={tag}
                        />
                      </Link>
                    );
                  }
                })}
                <CardActions disableSpacing>
                  <IconButton aria-label="add to favorites">
                    <FavoriteIcon />
                  </IconButton>
                  <IconButton aria-label="share">
                    <ShareIcon />
                  </IconButton>
                </CardActions>
              </Card>
            )
          })}
        </div>
    </div>
  );
}

ORIGINALLY PUBLISHED BY SOUTH DEVON DIGITAL – SOUTHDEVONDIGITAL.COM

Awesome, now we can easily navigate around our app’s posts, tags and users!

That concludes part of 1 our ‘Using React, React Router & Material UI to Make a Social Network Frontend’ tutorial. In part 2 (coming soon), we’ll make our design responsive, add comments to our posts, and explore some more advanced frontend techniques to add features like endless scrolling, modal windows, a dark/light mode toggle switch, and more.


Share this article

Leave a Reply

Your email address will not be published.Required fields are marked *

email subscribe web design & development tutorial updates

Get more awesome web design & development tutorials delivered straight to your inbox as soon as they’re published!