ref – https://www.youtube.com/watch?v=DPOzlL1fpnU
npx create-react-app movie-app
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/material react-redux @reduxjs/toolkit redux-saga react-router-dom axios
First, we have a simple Promise that fetches data from the server.
This is a non-blocking operation, because it involves I/O with the web environment.
Thus when we asynchronously execute the fetch, we can let it process and go on to other tasks.
src/redux/api.js
1 2 3 4 5 6 7 8 |
import axios from "axios"; const REACT_APP_MOVIE_API_KEY = '924f6884'; const API_ENDPOINT = `https://www.omdbapi.com/?i=tt3896198&apikey=${REACT_APP_MOVIE_API_KEY}`; export const fetchMovies = async (movieName) => { return axios.get(`${API_ENDPOINT}&s=${movieName}`); } |
Since we asynchronously execute the fetch, we use call. This is because call stops the saga middleware and waits for its operation to finish.
Once we get the response, then we continue on. fetchMovies still processes it asynchronously.
Finally, we use put (non-blocking) to put the data in the store. (We have received the data already, so storing data can be done asynchronously here)
src/redux/movieSagas.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import { takeLatest, put, fork, call } from "redux-saga/effects"; import { getMovies, setMovies } from "./feature/movieSlice"; import { fetchMovies } from "./api"; function* onLoadMoviesAsync({payload}) { try { const movieName = payload; const response = yield call(fetchMovies, movieName); // call stops saga middleware and waits for fetchMovies's response. console.log('response status: ', response.status); // we're looking for 200 to be successful if (response.status===200) { console.log(`response successful`); yield put(setMovies({...response.data})); } } catch (error) {} } function *onLoadMovies() { yield takeLatest(getMovies.type, onLoadMoviesAsync) } export const movieSagas = [fork(onLoadMovies)]; |
and executes onLoadMoviesAsync when it receives that message.
Notice that takeLatest listens for getMovies.type, which logs ‘movie/getMovies’. But how did it create this tring?
It takes the name property of the slice:
1 2 3 4 5 6 7 8 9 |
const movieSlice = createSlice({ name: "movie", initialState: { moviesList: [], movie: {} }, ... ... }); |
and then append the action name “getMovies” behind it.
src/redux/feature/movieSlice.js
1 |
export const { getMovies, setMovies } = movieSlice.actions; |
If you log getMovies, which is a function reference, you’ll see that it creates an action for you:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
export function createAction(type: string, prepareAction?: Function): any { function actionCreator(...args: any[]) { // start if (prepareAction) { let prepared = prepareAction(...args) if (!prepared) { throw new Error('prepareAction did not return an object') } return { type, payload: prepared.payload, ...('meta' in prepared && { meta: prepared.meta }), ...('error' in prepared && { error: prepared.error }), } } return { type, payload: args[0] } } // end actionCreator.toString = () => `${type}` actionCreator.type = type actionCreator.match = (action: Action<unknown>): action is PayloadAction => action.type === type return actionCreator } |
type is passed in from createAction from another execution so we know that we’re being used as a curried function.
Run the app and we start with initial state:
Then we do a get movies, which returns the initial state:
We run our dispatch getMovies in useEffect of our Search component, which has default value ‘spider’ and get a return of all movies with spider keyword.
– 38:00 at the video