redux-saga runAllEffect 에러 해결하기

클론코딩을 하던 중 이런 에러를 마주하였다.

스크롤을 내려보면 runAllEffect에서 발생한 에러임을 알 수 있다.
TypeError: Cannot read property ‘forEach’ of null

runAllEffect 에러

해결방안

액션을 구독할 때에 take함수를 쓰는데, import를 해주지 않아서 발생한 에러였다. take를 import 해주었다.
자바스크립트라 어이없는 실수가 많다..

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { all, delay, fork, take } from "redux-saga/effects";

// function logInAPI(data) {
// return axios.post("/api/login");
// }

function* logIn(data) {
try {
// const result = yield call(logInAPI, action.data);
yield delay(1000);
yield put({
type: "LOG_IN_SUCCESS",
data: action.data,
});
} catch (err) {
yield put({
type: "LOG_IN_FAILURE",
data: err.response.data,
});
}
}

function* logOut() {
try {
yield delay(1000);
yield put({
type: LOG_OUT_SUCCESS,
});
} catch (err) {
console.error(err);
yield put({
type: LOG_OUT_FAILURE,
error: err.response.data,
});
}
}

function* watchLogIn() {
yield take("LOG_IN_REQUEST", logIn);
}

function* watchLogOut() {
yield take("LOG_OUT_REQUEST", logOut);
}

export default function* userSaga() {
yield all([fork(watchLogIn), fork(watchLogOut)]);
}
Server Error TypeError: Cannot read property 'forEach' of null

This error happened while generating the page. Any console logs will be displayed in the terminal window.
Call Stack
end
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (1017:18)
Object.abort
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (817:5)

file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (598:22)
immediately
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (60:12)
runForkEffect
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (588:3)
runEffect
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (1208:7)
digestEffect
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (1275:5)

file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (677:5)
Array.forEach

runAllEffect
file:///C:/Users/SDPark-pc/Desktop/REACT/nodebird/front/node_modules/@redux-saga/core/dist/redux-saga-core.dev.cjs.js (676:8)

React로 NodeBird SNS 만들기 2 redux-saga

리덕스에 비동기 작업들을 처리하는 리덕스 사가를 사용해보자!
본 게시글은 next.js +redux + redux-saga기준으로 작성하였다.

설치

1
npm i redux-saga

Store에 적용하기

Store를 만들고 미들웨어를 추가해준다.
그 후 반환 전 사가 미들웨어를 실행해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { createWrapper } from "next-redux-wrapper";
import { applyMiddleware, compose, createStore } from "redux";
import reducer from "../reducers";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";

const configureStore = () => {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const enhancer =
process.env.NODE_ENV === "production"
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(applyMiddleware(...middlewares));
const store = createStore(reducer, enhancer);
store.sagatask = sagaMiddleware.run(rootSaga);
return store;
};

const wrapper = createWrapper(configureStore, {
debug: process.env.NODE_ENV === "development",
});

export default wrapper;

제너레이터

중단점이 있는 함수이다 next()를 통해 다음 중단점 전까지 실행한다.
return된 값은 value에 저장된다.
saga에서는 이벤트 리스너로 활용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
const func = function* () {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
yield;
};

func; //func{<suspended>}
func.next(); //1 {value:undefined,done:false}
func.next(); //2 {value:undefined,done:false}
func.next(); //3 {value:undefined,done:true}

saga에서의 제너레이터 적용

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
function* logIn(action) {
try {
const result = yield call(logInAPI, action.data);
yield put({
type: "LOG_IN_SUCCESS",
data: result.data,
});
} catch (err) {
yield put({
type: "LOG_IN_FAILURE",
data: err.response.data,
});
}
}

function* watchLogIn() {
yield take("LOG_IN_REQUEST", logIn);
}

function* watchLogOut() {
yield take("LOG_OUT_REQUEST");
}

export default function* rootSata() {
yield all([fork(watchLogIn), fork(watchLogOut)]);
}

saga 이펙트

sagas/index.js에 이펙트를 작성하였다.

all

  • 배열안의 함수를 동시에 실행한다
1
2
3
export default function* rootSata() {
yield all([fork(watchLogIn), fork(watchLogOut)]);
}

fork

  • 매개변수로 받은 함수를 비동기 실행하는 함수이다.
1
2
3
export default function* rootSata() {
yield all([fork(watchLogIn), fork(watchLogOut)]);
}

call

  • 매개변수로 받은 함수를 동기 실행하는 함수이다.
  • call(함수,매개변수1,매개변수2,…)의 형식으로 호출해주어야 한다.
  • 아래 함수의 경우 call 아래줄은 500ms동안 지연이 일어난 후에 실행된다.
1
2
3
4
function* handleInput(input) {
// 500ms마다 지연
yield call(delay, 500);
}

take

  • 해당 액션이 실행될 때 까지 기다리는 함수이다.
  • LOG_IN_REQUEST 액션 발생시 logIn함수를 실행한다.
1
2
3
function* watchLogIn() {
yield take("LOG_IN_REQUEST", logIn);
}

put

  • store에게 dispatch하는 이펙트이다.
  • saga 미들웨어가 실행된 store에 전달한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* logIn(action) {
try {
const result = yield call(logInAPI, action.data);
yield put({
type: "LOG_IN_SUCCESS",
data: result.data,
});
} catch (err) {
yield put({
type: "LOG_IN_FAILURE",
data: err.response.data,
});
}
}

saga에서의 제너레이터 적용

  • 액션별로 함수를 지정해주고 액션이 발생시 그 함수를 호출해준다.
    이펙트 앞에는 yield를 붙여주어야 보장된다.

  • rootSaga에서 해당 이벤트가 발생시 핸들러에게 action을 같이 넘긴다.
    기존 redux와 같이 action.data, action.type로 이벤트 타입과 같이 넘겨진 데이터에 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
import { all, fork, take, call, put } from "redux-saga/effects";
import axios from "axios";

function logInAPI(data) {
return axios.post("/api/login", data);
}

function* watchLogOut() {
yield take("LOG_OUT_REQUEST");
}

기타 saga 이펙트

take

  • 일회용이다. 한 번 이벤트를 받으면 사라진다.
  • while로 감싸 계속 사용할 수 있다.
1
2
3
4
5
function* watchLogOut() {
while (true) {
yield take("LOG_OUT_REQUEST");
}
}

takeEvery

  • 계속해서 사용할 수 있다.
  • while문을 대체할 수 있고, while문은 동기적으로 동작하지만 takeEvery는 비동기적으로 동작한다.
1
2
3
function* watchLogOut() {
yield takeEvery("LOG_OUT_REQUEST");
}

delay

  • 해당 시간동안 대기한다.
1
2
3
function* watchLogOut() {
yield delay(1000); //1초 대기
}

debounce

  • 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 한다. 호출스택의 제일 윗부분의 요청이 실행되면 값이 task에 저장되어 나머지가 전부 cancel된다.

delay 이용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* handleInput(input) {
// 500ms마다 지연
yield call(delay, 500)
...
}
function* watchInput() {
let task;
while (true) {
const { input } = yield take("INPUT_CHANGED");
if (task) {
yield cancel(task);
}
task = yield fork(handleInput, input);
}
}

takeLatest 이용

  • 마지막 요청만 인식하여 한 번만 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
import { delay } from 'redux-saga'

function* handleInput({ input }) {
// 500ms마다
yield call(delay, 500)
...
}

function* watchInput() {
// 현재 실행중인 handleInput 작업을 취소합니다.
yield takeLatest('INPUT_CHANGED', handleInput);
}

throttle

  • 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 한다.
1
2
3
4
5
6
7
function* handleInput(input) {
// ...
}

function* watchInput() {
yield throttle(500, "INPUT_CHANGED", handleInput);
}

ref

https://mskims.github.io/redux-saga-in-korean/recipes/