skip to Main Content

WordFlix – Single Page App tutorial: Step 5 – Styling the Routes

Post Series: WordFlix – Super Powered Single Page App Tutorial

Step 5 – Starting Point

If you followed the previous steps of the tutorial, you should be able to continue here, but if you’re just jumping in or got lost in the last steps or just want to skip ahead, you can either check-out this branch of the Github repo or change directories to the “4” theme (wp-content/themes/4)

Move the Components to their own files

Our “index.js” file is getting crowded again, so let’s move our <MoviePage> and <MoviesPage> components to their own files, like we did earlier for the <HomePage> component.

Create new MoviesPage file

Let’s create a new file at “/app/src/components/MoviesPage.js”

And let’s move the <MoviesPage> component to this new file.

We need to make sure to include any imports that we make use of in this file, and we need to make sure to export the component for use by other files.

The final file should look like this:

import React, { Component } from 'react';
import { Link } from 'react-router';

class MoviesPage extends Component {
  render() {
    return(
      <div>
        <h1>This is the movies list page</h1>
        <Link to="/">Home</Link>
        <br/>
        <Link to="/movies/some-movie-id">Movie Details Page</Link>
      </div>
    );
  }
}

export default MoviesPage;

Create new MoviePage file

Let’s do the same thing for the <MoviePage> component, creating a new file at “/app/src/components/MoviePage.js” and moving the component, set up the proper imports, and export the component. It should look like this:

import React, { Component } from 'react';
import { Link } from 'react-router';

class MoviePage extends Component {
  render() {
    return(
      <div>
        <h1>This is the movie detail page</h1>
        <Link to="/">Home</Link>
        <br/>
        <Link to="/movies">Movies List Page</Link>
      </div>
    );
  }
}

export default MoviePage;

 Clean up the index.js file

Now that we’ve moved our components to their own file, we can clean up our index.js file.

We can remove “Link” from the imports we’re importing from “react-router.”

Then, we’ll want to import our new components:

import MoviePage from './components/MoviePage';
import MoviesPage from './components/MoviesPage';

Then we can remove the <MoviePage> and <MoviesPage> components completely from the file, as we’re now pulling them in through the imports.

The diff for this can be seen here.

Style the MoviesPage component

Now that the MoviesPage component is in it’s own file, let’s add some styles and layout to the component.

Mock up our MoviesPage layout

The goal for this page is to add a MenuBar with some links to Routes, and have a Grid of movies that display the Movie Poster image and the Movie Title, and link to the MovieDetail page.

Our end goal is to look something like this:

So, inside our MoviesPage component, let’s start by writing the structure we’re going to end with, then we’ll follow up by creating the components needed for this structure.

Update the <MoviesPage> component to look like this:

class MoviesPage extends Component {
  render() {
    return(
      <Wrapper>
        <MenuBar>
          <Link to="/">Home</Link>
          <Link to="/movies">Browse Movies</Link>
        </MenuBar>
        <Content>
          <PageTitle>Movies</PageTitle>
          <MoviesWrapper>
            <Row gutter={40} type="flex" justify="space-between" align="center">
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
              <Col xs={24} sm={12} md={8} lg={6}>
                <MovieCard />
              </Col>
            </Row>
          </MoviesWrapper>
        </Content>
      </Wrapper>
    );
  }
}

What we’re doing here is adding a Wrapper component, then inside that we have a MenuBar component with some Links, then a Content component which has a PageTitle, then a MoviesWrapper component which contains a Row, with a repeating list of Columns, each column containing a MovieCard.

So, we just introduced the following components to render that don’t exist yet:

  • Wrapper
  • MenuBar
  • Content
  • PageTitle
  • MoviesWrapper
  • Row
  • Col
  • MovieCard

Some of these we need to create, and some we can import from Ant Design. Let’s start with the imports.

Add the imports

Let’s import some components from Ant Design, which is a FANTASTIC React component library.

At the top of the “/components/MoviesPage.js” file, with the other imports, add:

import { Row, Col, Card, Layout } from 'antd';

const { Content } = Layout;

This takes care of the following components we added to our MoviesPage component:

  • Content
  • Row
  • Col

So we still need to create:

  • Wrapper
  • MenuBar
  • PageTitle
  • MoviesWrapper
  • MovieCard

Create some Styled Components

 

Let’s add styled to our imports:

import styled from 'styled-components';

Then, let’s create some styled components.

const MenuBar = styled.section`
  background: #ad0000;
  padding: 10px;
  height:auto;
  position:fixed;
  top:0px;
  left:0px;
  width:100%;
  z-index:100;
  >a {
     font-size: 18px;
     line-height: 40px;
     color: white;
     margin: 0 10px;
     &:hover {
        color: black;
     }
  }
`;

/**
 * This creates a div that will be used as the wrapper for the contents of the page.
 * This uses a prop passed to it to generate the background image...say what?
 */
const Wrapper = styled.div`
  background:#000;
  min-height:100vh;
  display:flex;
  justify-content: center;
  align-items:center;
  padding-top:60px;
`;

/**
 * Create a styled H1 component
 */
const PageTitle = styled.h1`
  font-family: avengeance_mightiest_avengeRg;
  font-size: 48px;
  line-height: 50px;
  text-align:center;
  padding:20px;
  margin-top:40px;
  color:white;
`;

/**
 * Create a styled div to serve as the wrapper for the movies list
 */
const MoviesWrapper = styled.div`
  max-width: 80%;
  padding:40px;
  display:flex;
  justify-content:center;
  align-items:center;
  flex-direction:column;
  margin:auto;
  margin-bottom: 50px;
`;

/**
 * Create a styles h2 to use as the movie title for individual movies
 */
const MovieTitle = styled.h2`
  color: black;
  font-size: 16px;
  line-height: 24px;
  font-family:avengeance_mightiest_avengeRg;
  padding:10px;
`;

This leaves us with just one more component to create, <MovieCard>.

Create the <MovieCard> Component

This is a bit more complicated than a simple Styled Component, so we’ll create a new MovieCard Component with it’s own “render” method.

Since the only place we’re using this is inside the MoviesPage, we’ll go ahead and create this component inside the MoviesPage.js file, and we can leave it there. But, like we did with some of our other components, this could easily live inside it’s own file and be imported for use wherever it’s needed (same goes for styled components – they can live in their own files and be imported and re-used wherever you want!)

Here’s the <MovieCard> component we’ll use:

class MovieCard extends Component {

  /**
   * Render a Card component with the Movie poster and title
   */
  render() {
    return(
      <Card style={{marginBottom: '40px', flex:1}} bodyStyle={{ padding: 0 }}>
        <Link to="movies/movieId" >
          <div>
            <img src="http://placehold.it/236x332" alt="poster for movie title"/>
            <MovieTitle>Movie Title</MovieTitle>
          </div>
        </Link>
      </Card>
    );
  }
}

This component makes use of the <Card> component from Ant Design

Then it wraps the <img> and <MovieTitle> within a <Link>, making the contents of the card link to the MovieDetail page when clicked.

At this point, we should have a MoviesPage looking like what we set out to create in the screenshot above.

Let’s do the same thing for our MoviePage.

Mock up our MoviePage layout

The goal for this page is to add a MenuBar with some links to Routes, and then a box that displays details of our Movie, such as the Poster, Title, Release Date and Description.

Our end goal is to look something like this:

So, let’s rewrite our MovePage component to have the structure we want, then we’ll come back and create and import the components we need to get there.

class MoviePage extends Component {
  render() {
    return(
      <Wrapper>
        <MenuBar>
          <Link to="/">Home</Link>
          <Link to="/movies">Browse Movies</Link>
        </MenuBar>
        <MovieDetails>
          <Poster>
            <img alt={'poster for Movie Title'} src="http://placehold.it/341x512" />
          </Poster>
          <MovieContent>
            <h2>Movie Title</h2>
            <h3>Released: realease date</h3><br/>
            <div>Movie description</div>
          </MovieContent>
        </MovieDetails>
      </Wrapper>
    );
  }
}

What we’re doing here is adding a Wrapper component, and inside the Wrapper is a MenuBar with some Links and a MovieDetails component which contains a Poster with an <img>, and MovieContent which contains the MovieTitle, Released Date and the Movie Description.

So, that introduces the following Components we need to either import or create:

  • Wrapper
  • MenuBar
  • MovieDetails
  • Poster
  • MovieContent

Create some Styled Components

Let’s add styled to our imports:

import styled from 'styled-components';

Then, let’s create some styled components.

const MenuBar = styled.section`
  background: #ad0000;
  padding: 10px;
  height:auto;
  position:fixed;
  top:0px;
  left:0px;
  width:100%;
  z-index:100;
  >a {
     font-size: 18px;
     line-height: 40px;
     color: white;
     margin: 0 10px;
     &:hover {
        color: black;
     }
  }
`;

/**
 * This creates a div that will be used as the wrapper for the contents of the page.
 * This uses a prop passed to it to generate the background image...say what?
 */
const Wrapper = styled.div`
  background:#000;
  min-height:100vh;
  display:flex;
  justify-content: center;
  align-items:center;
  padding-top:60px;
`;

/**
 * This is the div that contains the Details about the movie (poster, title, description, etc)
 */
const MovieDetails = styled.div`
  background:rgba( 255,255,255,0.8 );
  max-width: 80%;
  display:flex;
  justify-content: center;
  align-items:center;
  flex-direction:row;
  padding:25px 50px;
  color: #222;
  >div{
    flex:1;
  }
`;

/**
 * This is the div that contains the poster
 */
const Poster = styled.div`
  max-width:35%;
  min-width:200px;
  background:white;
  padding:10px;
  >img{
    max-width:100%;
  }
`;

/**
 * This is the div that contains the MovieContent (title, description)
 */
const MovieContent = styled.div`
  padding: 50px;
  font-size: 18px;
  line-height: 24px;
  >h2{
    margin-bottom:15px;
  }
`;

This should get us to the layout and styles we were looking to get to in the Screenshot.

DRY up our code

DRY is an acronym for “Don’t Repeat Yourself” which is common in development. If you’re doing the same thing several times, it’s best to create a way to do it well once, and re-use that when possible.

You may have noticed that we created some duplicate components in the MoviePage and MoviesPage components, so let’s DRY it up.

In both pages we had a Wrapper, and MenuBar and then the content of the page.

So, let’s create a new PageLayout component at “/components/PageLayout.js” and we’ll use this to wrap our pages.

The final PageLayout component should look like this:

import React, { Component } from 'react';
import { Link } from 'react-router';
import { Layout } from 'antd';
import styled from 'styled-components';
const { Content } = Layout;

const MenuBar = styled.section`
  background: #ad0000;
  padding: 10px;
  height:auto;
  position:fixed;
  top:0px;
  left:0px;
  width:100%;
  z-index:100;
  >a {
     font-size: 18px;
     line-height: 40px;
     color: white;
     margin: 0 10px;
     &:hover {
        color: black;
     }
  }
`;

/**
 * This creates a div that will be used as the wrapper for the contents of the page.
 * This uses a prop passed to it to generate the background image...say what?
 */
const Wrapper = styled.div`
  background:#000;
  min-height:100vh;
  display:flex;
  justify-content: center;
  align-items:center;
  padding-top:60px;
`;

class PageLayout extends Component {
  render() {
    return(
      <Wrapper>
        <MenuBar>
          <Link to="/">Home</Link>
          <Link to="/movies">Browse Movies</Link>
        </MenuBar>
        <Content>
          {this.props.children}
        </Content>
      </Wrapper>
    )
  }
}

export default PageLayout;

Then we can clean up our MoviesPage and MoviePage files to remove the pieces that will now be handled by our PageLayout wrapper component.

Clean up the MoviesPage

You can see the diff for this here.

In the MoviesPage, we can remove the Layout import from antd, and remove the MenuBar and Wrapper styled components. Then update the MoviesPage to just render the content like so:

class MoviesPage extends Component {
  render() {
    return(
      <div>
        <PageTitle>Movies</PageTitle>
        <MoviesWrapper>
          <Row gutter={40} type="flex" justify="space-between" align="center">
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
            <Col xs={24} sm={12} md={8} lg={6}>
              <MovieCard />
            </Col>
          </Row>
        </MoviesWrapper>
      </div>
    );
  }
}

Clean up the MoviePage

Let’s do the same thing for our MoviePage.

You can see the diff here.

We can remove the Link import, and remove the MenuBar and Wrapper Styled Components. Then we can adjust our MoviePage component to render just the page content, like so:

class MoviePage extends Component {
  render() {
    return(
      <MovieDetails>
        <Poster>
          <img alt={'poster for Movie Title'} src="http://placehold.it/341x512" />
        </Poster>
        <MovieContent>
          <h2>Movie Title</h2>
          <h3>Released: realease date</h3><br/>
          <div>Movie description</div>
        </MovieContent>
      </MovieDetails>
    );
  }
}

We also add:

margin:auto;

To our MovieDetails styled component to help with the layout.

Wrap the Pages with the PageLayout

There are different ways we could accomplish this, but I’ll show you one way. Our goal is to have our PageLayout component wrap our MoviePage and MoviesPage component.

In our “index.js” file, we can nest our page routes within a Route that contains the PageLayout component, and effectively passes the MoviesPage and MoviePage components to the PageLayout component as it’s children.

So let’s update our Router to look like this:

class App extends Component {
  render() {
    return(
      <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
        <Route component={PageLayout} >
          <Route path="/movies" component={MoviesPage} />
          <Route path="/movies/:movieId" component={MoviePage} />
        </Route>
        <Route path="*" component={HomePage} />
      </Router>
    )
  }
}

Leave a Reply

Share This
Back To Top