3 minute read

We have every tools for a single page now.

It’s time for a multi page system.

We are going to have a following file structures that categorizing

  • components : main content items that fill out a page.
  • routes : pages that contents are being placed.

source structure

For example

raacker.github.io will have the Main.js under routes folder.

raacker.github.io/about will have the About.js under routes folder.

Post.js, CodeSnippet.js, or any small widgets will be parts of components folder

Let’s define Movie component first. Data will be a json result of https://yts.mx/api/v2/list_movies.json?minimum_rating=8.8&sort_by=year

import PropTypes from "prop-types";

function Movie({ id, coverImage, title, year, summary, genres }) {
    return (
        <div key={ id }>
            <img src={ coverImage }></img>
            <h2>
                { title } ( { year } )
            </h2>
            <p>{ summary }</p>
            <ul>
                {
                    genres.map((genre) => {
                        return (<li>{ genre }</li>);
                    })
                }
            </ul>
        </div>
    );
}

Movie.propTypes = {
    id: PropTypes.number.isRequired,
    coverImage: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    year: PropTypes.string.isRequired,
    summary: PropTypes.number.isRequired,
    genres: PropTypes.array.isRequired,
}
export default Movie;

It covers

  • Define propTypes to secure expected parameters and their types
  • Return a React Component which shows some movie information briefly

Then we will have a Home page that list up all the movies of the query result.

import { useState, useEffect } from 'react';
import Movie from '../components/Movie';

function Home() {
    const [isLoading, setLoading] = useState(true);
    const [page, setPage] = useState(0);
    const [fetchResult, setFetchResult] = useState([]);
    const getMovies = async() => {
        const response = await fetch(`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.8&sort_by=year&page=${page}`);

        const json = await response.json();

        setFetchResult(json.data.movies);
        setLoading(false);
    }

    const prevPage = () => {
        setPage(curr => {
            if (curr <= 0) return 0;
            return curr - 1;
        });
    }
    const nextPage = () => {
        setPage(curr => curr + 1);
    }

    useEffect(() => {
        getMovies();
    }, [isLoading, page]);

    return (

    <div>
        <h2>Movie App</h2>
        {
            isLoading ? <b>Loading...</b> : <button onClick={ getMovies }>Refresh</button>
        }
        {
            fetchResult.length !== 0 ? <button onClick={ prevPage }>Prev Page</button> : null
        }
        {
            fetchResult.length !== 0 ? <button onClick={ nextPage }>Next Page</button> : null
        }

        {
            fetchResult.map((movie) => 
                <Movie
                    id={ movie.id }
                    coverImage={ movie.medium_cover_image }
                    title={ movie.title }
                    year={ movie.year }
                    summary={ movie.summary }
                    genres={ movie.genres }
                />
            )
        }
    </div>
    );
}

export default Home;

This is literally a barebone page without any styles.

It has

  • Use an asynchronous function to fetch API result and apply it to the React Component.
  • Use useEffect to update only when desired states get changed
  • To render Movie data, we are using Movie Component.

Let’s modify App.js to open Home.js

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

import Home from './routes/Home';

function App() {
  return (
      <Router>
        <Routes>
          <Route path="/" element={ <Home /> } />
        </Routes>
      </Router>
  );
}

export default App;

Home.js

Now, let’s add a detail page we can reroute when we click a title or a poster image.

import { useParams } from 'react-router-dom';
import { useState, useEffect } from 'react';

function Detail() {
    const { id } = useParams();
    const [ fetchResult, setFetchResult ] = useState([]);

    const getMovieDetail = async() => {
        const response = await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`);

        const json = await response.json();

        setFetchResult(json.data.movie);
    }

    useEffect(() => {
        getMovieDetail();
    }, []);

    return (
        <div>
            <h2>{ fetchResult.title }</h2>
            <img src={ fetchResult.background_image }></img>
            <t>{ fetchResult.description_full }</t>
        </div>
    );
}

export default Detail;
  • See the id in String literal url and useParams of react-router-dom. It lets you bind parameters that you pass to the Detail component.

If you add a one more line to the App.js, and a Link Component to the Movie, it’s done.

function App() {
  return (
      <Router>
        <Routes>
          <Route path="/" element={ <Home /> } />
          <Route path="/movie/:id" element={ <Detail /> } />
        </Routes>
      </Router>
  );
}
function Movie({ id, coverImage, title, year, summary, genres }) {
    return (
        <div key={ id }>
            <Link to={`/movie/${id}`}>
                <img src={ coverImage }></img>
                <h2>
                    { title } ( { year } )
                </h2>
            </Link>
            <p>{ summary }</p>
            <ul>
                {
                    genres.map((genre) => {
                        return (<li>{ genre }</li>);
                    })
                }
            </ul>
        </div>
    );
}
  • Using colons like :id is how you determine URL with parameters.
  • By using <Link>, you are rerouting URL to this component with a specific URL format which is */movie/${ id }*

Detail.js

Leave a comment