Monday, May 24, 2021

Build A Media Library with React, Redux, and Redux-saga - Part 1

In this two-part tutorial, we’ll build and deploy a media library app. The application allows users to search and display images and short videos from external services (Flickr and Shutterstock). It would also allow users to select images and videos for preview.

We will build this application with:

We will be using Yahoo’s Flickr API and ShutterStock API for images and short videos respectively.

This tutorial assumes you have a basic understanding of Javascript and React. Don't worry if you have none. We will walk through and build the application from the ground up.

What We'll Build

Part 1 of this tutorial would cover basic React setup with create-react-app package, organizing our project workflow, defining routes, and of course testing it out.

In Part 2, we will be using Redux and its async libraries; we will set it up and then integrate it into our application. Finally, we will deploy our application to Heroku for sharing with our friends. Our application would look thus when we're done.

Our app will be structured to allow you to either contribute to it or use as sample boilerplate for bootstrapping your React/Redux applications.

BEGINNERTAILWIND.COMLearn Tailwind CSS from Scratch

Great philosophy for learning:

Imitate, assimilate, and innovate - *Clark Terry.***

Project Setup

There are loads of React boilerplate out there to help you get started with React. But we’ll be using create-react-app authored by the Facebook team. It allows you create React applications with no configuration. create-react-app provides developers with the benefits of more complex setup out of the box.

Let’s get started…

$ npm install -g create-react-app   # First, install the package globally
$ create-react-app media-library   # Create media-library application

Bam. Our React basic setup is complete with scripts to startbuild, and eject. Take a look at your** package.json.**

Let’s test it out.

$ cd media-library && npm start

Now, we can structure our project directory and add other dependencies.

$ npm install --save redux redux-saga react-router@2.4 react-redux  # Install dependencies
$ rm -rf src/**  # To clean the default sample app

Project Directory

Media-library
    - public
        - favicon.ico
        - index.html
    - src
        - Api
            - api.js
        - actions
            - mediaActions.js
        - common
            - Header.js
        - components
            - HomePage.js
            - PhotoPage.js
            - VideoPage.js
        - constants
            - actionTypes.js
        - containers
            - App.js
            - MediaGalleryPage.js
        - reducers
            - imageReducer.js
            - index.js
            - initialState.js
            - videoReducer.js
        - sagas
            - mediaSaga.js
            - index.js
            - watcher.js
        - styles
            - style.css
        - store
            - configureStore.js
        - routes.js
        - index.js
    - package.json

If the project directory looks verbose, just be patient and let's walk-through. The intent of the project structure is to allow you extend the application’s functionality beyond this tutorial. This would help you stay organized moving forward.

If you’re new to Redux, I recommend* Lin Clark’s** article on *A Cartoon Intro To Redux.

What the heck is happening up there?

  1. View layer is our React component. It makes a request for action based on interactions on/with the application.
  2. Action, commonly called action creator returns a formatted object of the action type and optional payload which is then dispatched to the store.
  3. Redux-saga is a Redux middleware for handling async operations like fetching photos and videos from our API. It makes asynchronous operations look like standard Javascript synchronous code making it easy to read, test, and reason.
  4. APIs are resource locations to fetch photos and videos in our own case.
  5. Reducers are simply pure functions whose purpose in life is to accept the state tree and an action from the store; make a copy of the previous state, transform it and then return a new state to the store.
  6. Store is a single object that holds the complete state of your application. It delegates the reducer with the responsibility of changing state when an action is dispatched.

When the store receives an updated state, it transmits to the view layer to be rerendered.

Now that we understand the workflow, let’s dive into coding.

common/Header.js

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

const Header = () => (
  <div className="text-center">
    <nav className="navbar navbar-default">
      <IndexLink to="/" activeClassName="active">Home</IndexLink>
      {" | "}
      <Link to="library" activeClassName="active">Library</Link>
    </nav>
  </div>
);

export default Header;

Link allows you navigate to different routes in your application.

IndexLink is the same as Link with the exception of OnlyActiveOnIndex prop set on it.

components/HomePage.js

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

// Home page component. This serves as the welcome page with link to the library
const HomePage = () => (
  <div className="jumbotron center">
    <h1 className="lead">Welcome to Media Library built with React, Redux, and Redux-saga </h1>
    <div>
      <Link to="library">
        <button className="btn btn-lg btn-primary"> Visit Library</button>
      </Link>
    </div>
  </div>
);

export default HomePage;

containers/App.js

import React, { Component, PropTypes } from 'react';
import Header from '../common/Header';

// The parent component renders the Header component and component(s) in the
// route the user navigates to.
class App extends Component {
  render() {
    return (
      <div className="container-fluid text-center">
        <Header />
        {this.props.children}
      </div>
    );
  }
}
App.propTypes = {
  children: PropTypes.object.isRequired
};

export default App;

App component is the parent component of our app. Every other component is a child to it. this.props.children is where other child components are rendered.

We will implement the library route and the component that maps to it in Part 2 of this tutorial.

You would notice that for Header and HomePage components, we’re using stateless functional component. This approach allows us to separate our presentational components from the container components.

It’s a good practice as it enforces functional composition and component’s reusability. Whereas container components are responsible for your business logic and connecting with the store, presentational components are responsible for the look of your view.

Simply put, presentational components are components whose purpose in life is to render values to the DOM. Container components also known as smart components provide props and behavior to presentational components.

Let’s wire up our project routes.

routes.js

import React from 'react'; 
import { Route, IndexRoute } from 'react-router';
import App from './containers/App';
import HomePage from './components/HomePage';

// Map components to different routes.
// The parent component wraps other components and thus serves as  the entrance to 
// other React components.
// IndexRoute maps HomePage component to the default route
export default (
  <Route path="/" component={App}> 
    <IndexRoute component={HomePage} />
  </Route>
);

Now let's add the entrance to our application - index.js.

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, browserHistory } from 'react-router';
import routes from './routes';

// We require the routes and render to the DOM using ReactDOM API
ReactDOM.render(
    <Router history={browserHistory} routes={routes} />, 
    document.getElementById('root')
);

We pass in our routes and *browserHistory* as props to Router here. browserHistory uses your browser’s History API to create a clean and real URL without the gibberish that comes with using hashHistory. *hashHistory* has its use case, though.

Router is a high-level API that keeps your UI and URL in sync. It ensures that required props are passed whenever you change URL.

ReactDOM is the API for mounting our application on the DOM node(rootin our own case).

Two more things before we test our app.

Add a bootstrap link to a CDN in our public/index.html.

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>

Let’s add some custom styling.

styles/style.css

body {
  margin: 0;
  padding: 0;
  font-family: Helvetica, Arial, Sans-Serif, sans-serif;
  background: white;
}

.title {
  padding: 2px;
  text-overflow-ellipsis: overflow;
  overflow: hidden;
  display: block;
}

.selected-image, .select-video {
  height: 500px;
}

.selected-image img, .select-video video {
  width: 100%;
  height: 450px;
}

.image-thumbnail, .video-thumbnail {
  display: flex;
  justify-content: space-around;
  overflow: auto;
  overflow-y: hidden;
}

.image-thumbnail img, .video-thumbnail video {
  width: 70px;
  height: 70px;
  padding: 1px;
  border: 1px solid grey;
}

Let’s test our app now…

$ npm start

Navigate to http://localhost:3000 on your browser.

Bam!!! We’re up again

Wrap up

Building application with ReactJs gets better as you understand the flow. In this part, we did:

  1. Set up configurations.
  2. Set up project structure.
  3. Complete Header, Homepage, and App(parent) components.
  4. Map routes to different components.
  5. What else? We tested our app.

In the second part of this tutorial, we will be exploring the power of Redux, Redux-saga and separating our state management system from the React components for scalability and maintainability.

No comments:

Post a Comment

Technology’s generational moment with generative AI: A CIO and CTO guide

 Ref:  A CIO and CTO technology guide to generative AI | McKinsey 1. Determine the company’s posture for the adoption of generative AI As us...