DEV Community

loading...
Cover image for Learn React & Redux-Saga By Building Netflix

Learn React & Redux-Saga By Building Netflix

Hiep Le
I'm Hiep. I work as a full-time software engineer. Most of my open-source projects are focused on one thing - to help people learn 📚.
Updated on ・10 min read

learn-react-by-building-netflix

Click ⭐ if you like the project. Pull Requests are highly appreciated ❤️

Github link: https://github.com/hieptl/netflix-clone/tree/main/advanced/netflix-saga

I'm Hiep. I work as a full-time software engineer. Most of my open-source projects are focused on one thing - to help people learn 📚.

The repository helps you learn Redux Saga by building Netflix. It means that you are learning Redux Saga by building a real-life project. I will explain concepts in details. This post is the fourth part in my series and it is suitable for beginners.

If you feel the repository is useful, please help me share the post and give me a Github ⭐. It will make me feel motivation to work even harder. I will try to make many open sources and share to the community.

Preface

This course will help you to learn Redux Saga by building Netflix. It means that you are learning by doing a real-life project.

Table of Contents

Table of Images.

0. How to Run the Project.

  • Step 1: Clone the project by using git clone or download the zip file.

  • Step 2: Open "terminal" / "cmd" / "gitbash" and change directory to "netflix-clone" and run "npm install" to install dependencies.

  • Step 3: Run "npm start" to run the fron-end project.

1. Live Demo.

The login function was implemented by using Redux and Redux-Saga. You can try to use the account above in order to test the feature. The result will be shown on the console log.

2. Introduction about the Creator.

2.1. Greenwich University.

  • Valedictorian.

  • GPA 4.0 / 4.0.

  • Machine Learning paper - Recommendation System - IEEE/ICACT2020.

  • Co-Founder / Mentor IT club.

2.2. Hitachi Vantara Vietnam.

  • Employee of the year.

  • Second prize - innovation contest.

  • Techlead - HN branch.

  • One of CoE Leaders (Center of Excellence).

3. Prequisites.

3.1. Softwares.

  • Install NodeJS.

  • An IDE or a text editor (VSCode, Intellij, Webstorm, etc).

3.2. Technical Skills.

3.3. Materials.

  • Html, css, js (source code) was prepared because I want to focus on React and share knowledge about React. Building html and css from scratch would take a lot of time.

  • README.md (the md file will contain everything about the course).

  • Netflix data will be used to import to Firebase. In this course, we use Firebase as our back-end service.

4. Purposes of the Course.

4.1. Final Project.

  • The course would help you have understanding about React and Redux Saga.

  • You could build the final project with end-to-end solution (front-end solution using React and back-end solution using Firebase).

4.2. Job.

  • After finishing the course, you could get a job with fresher / junior position.

5. Redux Middleware.


redux

Figure 1. Redux Middleware.

5.1 What.

  • Redux middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.

5.2 Why.

  • Redux middleware were designed to enable writing logic that has side effects.

  • Middleware like Redux Thunk or Redux Promise just gives you “syntax sugar” for dispatching thunks or promises

5.3 How.

  • In order to use Redux Middleware in your application, you need to import and use applyMiddleware function from redux. It will help you to apply midllewares in your application.

5.4 When.

  • Redux middleware is suitbale for medium or large applications.

  • It help you to separate the business code and the view.

  • Redux middleware were designed to enable writing logic that has side effects. As what mentioned, a Redux middleware can do anything when it sees a dispatched action: log something, modify the action, delay the action, make an async call, and more. Also, since middleware form a pipeline around the real store.

6. Redux Saga.

6.1. What is Redux Saga.

  • redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.

  • The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.

  • Sagas enable numerous approaches to tackling parallel execution, task concurrency, task racing, task cancellation, and more. Keep total control over the flow of your code.

6.2. Differences between Redux Thunk and Redux Saga.

Redux Thunk Redux Saga
Advantages Simple, strong, easy to use, it is suitable for beginners It is suitable for large scale applications. It helps us to write cleaner code and test the code easier when comparing with Redux Thunk
Disadvantages It is suitable for small applications. There is a learning curve for beginners and it is not suitable for small applications.

7. Apply Redux Saga to Netflix.


apply-redux-saga-to-netflix

Figure 2. Apply Redux Saga to Netflix.

It is time to understand how Redux Saga works by applying it to our Netflix application. In the first part of this series, we built the login function. However, we just wrote a function to call Firebase authentication service when clicking on "Sign In" button. It is time to change the code by using Redux-Saga.

  • Step 1: We need to install redux-saga by running npm install redux-saga.

  • Step 2: Update store.js file with the following code.

// import create store.
import { createStore, applyMiddleware } from "@reduxjs/toolkit";
// import root reducer.
import rootReducer from "./reducers";
// import redux saga.
import createSagaMiddleware from "redux-saga";
// import root saga.
import rootSaga from "./sagas";

const sagaMiddleware = createSagaMiddleware();

const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga);

export default store;

Enter fullscreen mode Exit fullscreen mode

1st NOTE:

  • We import createSagaMiddleware from redux_saga. This function is used to create a saga middleware
const sagaMiddleware = createSagaMiddleware();
  • As mentioned above, in order to apply redux middleare in our application, we need to use applyMiddleware. In this case, it accepts the created sagaMiddleware as the first parameter.

  • The run method is used to enable saga in our application. It accepts rootSaga as the first parameter. In fact, we can have many sagas in the application. For this reason, we need to combine all of them into a single root saga. The process is similar to combining many reducers into a single reducer. We will create the root saga at the end of the article.

  • Step 3: We need to create services folder, this folder is used to store services in the application. Services will take responsibility to interact with back-end services.

  • Step 4: We are building the login feature. Therefore, we need to create AuthService.js file in services folder. This file will interact with back-end services which are related to auth functions.

// import firebase auth.
import { firebaseAuth } from "../firebase/firebase";

export const login = async ({ email, password }) => {
  try {
    return await firebaseAuth.signInWithEmailAndPassword(email, password);
  } catch (error) {
    console.log(error);
  }
};
Enter fullscreen mode Exit fullscreen mode

2nd NOTE:

  • We need to call Firebase authentication service. That's why firebaseAuth will be imported.

  • login function accept email, password as parameters. It will be used in the following section.

  • It is time to create a login action (redux action). The login action will be dispatched when clicking "Sign In" button.

  • Step 5: Create AuthActions.js in actions folder.
export const LOGIN = "LOGIN";
Enter fullscreen mode Exit fullscreen mode

3rd NOTE:

  • In AuthActions.js file, we define an action type. It is used to specify the type of the action when clicking on "Sign In" button.
  • Step 6: Create sagas folder. The folder is used to store different sagas in the application.

  • Step 7: Create AuthSaga.js inside src/sagas folder. This file is used to specify sagas which are related to the auth function.

// import redux saga.
import { call, put, takeLatest } from "redux-saga/effects";
// import auth service.
import * as authService from "../services/AuthService";
// import actions.
import * as loadingActionTypes from "../actions/LoadingActions";
import * as authActionTypes from "../actions/AuthActions";

function* login(action) {
  try {
    const { email, password } = action.payload;
    yield put({ type: loadingActionTypes.SHOW_LOADING });
    const userCredentials = yield call(authService.login, { email, password });
    if (userCredentials) {
      console.log(userCredentials);
    }
    yield put({ type: loadingActionTypes.HIDE_LOADING });
  } catch (error) {
    console.log(error);
  }
}

function* auth() {
  yield takeLatest(authActionTypes.LOGIN, login);
}

export default auth;
Enter fullscreen mode Exit fullscreen mode

4th NOTE:

  • We import call, put, takeLatest from redux-saga.

  • call(fn, ...args): Creates an Effect description that instructs the middleware to call the function fn with args as arguments. fn: Function - A Generator function, or normal function which either returns a Promise as result, or any other value. args: Array - An array of values to be passed as arguments to fn. In this case, we use call to call the auth service and the auth service will call Firebase authentication service.

call(authService.login, { email, password })
  • In some cases, we need to dispatch actions inside the current action, for example, we need to dispatch actions to show/hide Loading component before / after calling back-end services. In order to achieve that, we need to use put.

  • put(action): Creates an Effect description that instructs the middleware to schedule the dispatching of an action to the store. This dispatch may not be immediate since other tasks might lie ahead in the saga task queue or still be in progress.

put({ type: loadingActionTypes.SHOW_LOADING })
...
put({ type: loadingActionTypes.HIDE_LOADING })
  • takeLatest(pattern, saga, ...args): Each time an action is dispatched to the store. And if this action matches pattern, takeLatest starts a new saga task in the background. If a saga task was started previously (on the last action dispatched before the actual action), and if this task is still running, the task will be cancelled. In this case, takeLatest(authActionTypes.LOGIN, login) indicate that the login saga will be used when an action with "LOGIN" type is dispatched.
  • Step 6: Replace LoginForm.js file with the following code.
/**
 * Github: https://github.com/hieptl/netflix-clone.
 * Dev.to: https://dev.to/hieptl/learn-react-by-building-netflix-1127
 */
// import react.
import { useState } from "react";
// import useDispatch to dispatch an action to the store.
import { useDispatch } from "react-redux";
// import actions.
import * as authActions from "../../actions/AuthActions";
/**
 * create LoginForm component.
 */
function LoginForm() {
  // create email and password state to store user's credentials.
  const [email, setEmail] = useState();
  const [password, setPassword] = useState();

  const dispatch = useDispatch();

  /**
   * handle event when the user clicks on "Login" button.
   */
  const login = () => {
    dispatch({ type: authActions.LOGIN, payload: { email, password } });
  };

  /**
   * update email state when the user inputs the email field.
   * @param {*} e - synthetic event to get the latest email's value.
   */
  const onEmailChanged = (e) => {
    // get email value.
    const updatedEmail = e.target.value;
    // update email state.
    setEmail(() => updatedEmail);
  };

  /**
   * update password state when the user input the password field.
   * @param {*} e - synthetic event to get the latest password's value.
   */
  const onPasswordChanged = (e) => {
    // get password value.
    const updatedPassword = e.target.value;
    // update password state.
    setPassword(() => updatedPassword);
  };

  return (
    <div className="login-body">
      <div className="login-body__form">
        <h1>Sign In</h1>
        <div className="login-body__input mb-16">
          <input
            type="text"
            placeholder="Email or phone number"
            onChange={onEmailChanged}
          />
        </div>
        <div className="login-body__input">
          <input
            type="password"
            placeholder="Password"
            onChange={onPasswordChanged}
          />
        </div>
        <button className="login-body__submit-btn" onClick={login}>
          Sign In
        </button>
        <div className="login-body__options">
          <span>Remember me</span>
          <span className="login-body__need-help">Need help?</span>
        </div>
        <div className="login-body__footer">
          <div className="login-body__fb">
            <img
              src="https://assets.nflxext.com/ffe/siteui/login/images/FB-f-Logo__blue_57.png"
              alt="fb"
            />
            <span>Login with Facebook</span>
          </div>
          <div className="login-body__new-to-nl">
            <span>New to Netflix ?</span>
            <span className="login-body__sign-up">Sign up now.</span>
          </div>
          <div className="login-body__google_captcha">
            This page is protected by Google reCAPTCHA to ensure you're not a
            bot.
            <span className="login-body__learn-more">Learn more.</span>
          </div>
        </div>
      </div>
    </div>
  );
}
// export LoginForm component.
export default LoginForm;
Enter fullscreen mode Exit fullscreen mode

FINAL NOTE:

  • We import useDispatch to dispatch redux actions.

  • We also import AuthActions and call the login action with the following code:

dispatch({ type: authActions.LOGIN, payload: { email, password } })
  • The type is used to specify the type of the action.

  • The payload is used to specify the request body of the action.

Conclusion

In this course, we have learn about Redux Saga by building Netflix. I hope that you can apply Redux Saga to your projects. If you feel the projects is useful, please help me share it to the community and give me Github ⭐

References

[1]. https://redux.js.org/
[2]. https://redux-saga.js.org/

Discussion (0)