API_CALL_REQUEST описывает что мы начинаем процесс получения данных с API
API_CALL_SUCCESS описывает что store успешно получил данные и процесс получения данных завершен
API_CALL_FAILURE описывает что API вызов завершился ошибкой
watcherSaga
- это сага, которая следит за тем, чтобы экшен был отправлен в store, вызывая workerSaga
.
takeLatest
- это вспомогательная функция, предоставляемая redux-saga, которая будет запускать новую workerSaga
, когда она получит API_CALL_REQUEST
. В то же время отменяет ранее запущенную workerSaga
, для отмены слишком частых или ненужных вызовов API.
fetchDog
просто использует axios для запроса случайного изображения собаки из API и возвращает Promise для ответа.
workerSaga
вызывает функцию fetchDog
, используя другой вызов helper function redux-saga и сохраняет результат (из promise) в переменной ответа.
- Если
fetchDog
был успешным, мы получаем изображение собаки из ответа и отправляем действие API_CALL_SUCCESS с payload в store, используя соответсвующий экшн redux-saga.
- Если произошла ошибка в
fetchDog
, мы сообщим об этом Store, отправив действие API_CALL_FAILURE.
// rootReducer.js
// action types
const API_CALL_REQUEST = "API_CALL_REQUEST";
const API_CALL_SUCCESS = "API_CALL_SUCCESS";
const API_CALL_FAILURE = "API_CALL_FAILURE";
// reducer with initial state
const initialState = {
fetching: false,
dog: null,
error: null
};
export function reducer(state = initialState, action) {
switch (action.type) {
case API_CALL_REQUEST:
return { ...state, fetching: true, error: null };
break;
case API_CALL_SUCCESS:
return { ...state, fetching: false, dog: action.dog };
break;
case API_CALL_FAILURE:
return { ...state, fetching: false, dog: null, error: action.error };
break;
default:
return state;
}
}
// rootSaga.js
import { takeLatest, call, put } from "redux-saga/effects";
import axios from "axios";
// watcher saga: watches for actions dispatched to the store, starts worker saga
export function* watcherSaga() {
yield takeLatest("API_CALL_REQUEST", workerSaga);
}
// function that makes the api request and returns a Promise for response
function fetchDog() {
return axios({
method: "get",
url: "https://dog.ceo/api/breeds/image/random"
});
}
// worker saga: makes the api call when watcher saga sees the action
function* workerSaga() {
try {
const response = yield call(fetchDog);
const dog = response.data.message;
// dispatch a success action to the store with the new dog
yield put({ type: "API_CALL_SUCCESS", dog });
} catch (error) {
// dispatch a failure action to the store with the error
yield put({ type: "API_CALL_FAILURE", error });
}
}
// entry.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import { createStore, applyMiddleware, compose } from "redux";
import createSagaMiddleware from "redux-saga";
import { Provider } from "react-redux";
import { reducer } from "./redux";
import { watcherSaga } from "./sagas";
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();
// dev tools middleware
const reduxDevTools =
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
// create a redux store with our reducer above and middleware
let store = createStore(
reducer,
compose(applyMiddleware(sagaMiddleware), reduxDevTools)
);
// run the saga
sagaMiddleware.run(watcherSaga);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
// app.js
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import { connect } from "react-redux";
class App extends Component {
render() {
const { fetching, dog, onRequestDog, error } = this.props;
return (
<div className="App">
<header className="App-header">
<img src={dog || logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Dog Saga</h1>
</header>
{dog ? (
<p className="App-intro">Keep clicking for new dogs</p>
) : (
<p className="App-intro">Replace the React icon with a dog!</p>
)}
{fetching ? (
<button disabled>Fetching...</button>
) : (
<button onClick={onRequestDog}>Request a Dog</button>
)}
{error && <p style={{ color: "red" }}>Uh oh - something went wrong!</p>}
</div>
);
}
}
const mapStateToProps = state => {
return {
fetching: state.fetching,
dog: state.dog,
error: state.error
};
};
const mapDispatchToProps = dispatch => {
return {
onRequestDog: () => dispatch({ type: "API_CALL_REQUEST" })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);