简体   繁体   中英

Redux-Saga & Redux-Toolkit Wiring

I have been trying to introduce redux-sagas and redux-toolkit to my project. It seems that I am having some wiring problems. Not sure how to fix it. Let me know if you have any ideas. Here is the error I am getting. Here is the link to github

在此处输入图像描述

file - configureAppStore.js

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import tweetSagas from '../saga/tweet.js';

const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const middleware = [...getDefaultMiddleware({ thunk: false }), ...middlewares];

const configureAppStore = () => {
  // reduxjs/toolkit configureStore enables to dispatch async actions
  return configureStore({
    reducer: reducer,
    middleware: middleware,
  });
};

sagaMiddleware.run(tweetSagas);

export default configureAppStore;

file - saga/tweet.js

import { takeEvery, call, put, fork } from 'redux-saga/effects';
import axios from 'axios';
import * as actions from '../store/action/saga.js';
const port = process.env.REACT_APP_PORT;
const hostname = process.env.REACT_APP_LOCALHOST;
const baseURL = `http://${hostname}:${port}`;

function api({ dispatch }) {
  return function (next) {
    return function* (action) {
      if (action.type !== actions.sagaApiCallBegan.type) return next(action);
      next(action); // 'sagaApiCallBegan' to show in redux dev tools
      const { url, method, onSuccess, onError } = action.payload;
      try {
        const response = yield call(
          async () =>
            await axios.request({
              baseURL: baseURL,
              url,
              method,
            })
        );
        if (onSuccess)
          yield put(dispatch({ type: onSuccess, payload: response.data }));
      } catch (error) {
        if (onError) yield put(dispatch({ type: onError, payload: error }));
      }
    };
  };
}

function* watchApi() {
  yield takeEvery(actions.sagaApiCallBegan.type, api);
}

const tweetSagas = [fork(watchApi)];

export default tweetSagas;

file - store/tweets.js

import { createSlice } from '@reduxjs/toolkit';
import {
  sagaApiCallBegan,
  sagaApiCallSuccess,
  sagaApiCallFailed,
} from './action/saga';
import { webSocketCallBegan, webSocketCallFailed } from './action/websocket.js';
import { normalize } from 'normalizr';
import { tweetSchema } from '../store/Schema/tweet.js';

const initialState = () => ({
  byTweetId: {},
  byUserId: {},
  allTweetIds: [],
});

// action, actionTypes and reducer
const slice = createSlice({
  name: 'tweets',
  initialState: initialState(),
  // reducers
  reducers: {
    tweetAdded: (state, action) => {
      const { entities, result } = normalize(action.payload, tweetSchema);
      Object.assign(state.byTweetId, entities.byTweetId);
      Object.assign(state.byUserId, entities.byUserId);
      state.allTweetIds.push(result);
    },
    tweetStoreReseted: (state) => initialState(),
  },
});

export const { tweetAdded, tweetStoreReseted } = slice.actions;
export default slice.reducer;

// Action creators
export const fetchTweets = (term) =>
  sagaApiCallBegan({
    url: `/setsearchterm/${term}`,
    method: 'get',
    onSuccess: sagaApiCallSuccess.type,
    onError: sagaApiCallFailed.type,
  });

export const fetchTweetsPause = () =>
  sagaApiCallBegan({
    url: '/pause',
    method: 'GET',
    onSuccess: sagaApiCallSuccess.type,
    onError: sagaApiCallFailed.type,
  });

export const getTweet = (message) =>
  webSocketCallBegan({
    message: message,
    onSuccess: tweetAdded.type,
    onError: webSocketCallFailed.type,
  });

file - action/saga.js

import { createAction } from '@reduxjs/toolkit';

export const sagaApiCallBegan = createAction('saga/apiCallBegan');
export const sagaApiCallSuccess = createAction('saga/apiCallSuccess');
export const sagaApiCallFailed = createAction('saga/apiCallFailed');

I don't see a point to making your store configuration a function, but you should make sure that you are invoking that function in your App.jsx file. I'm not sure if you are as the code is missing here.

Here are some issues I did find that are pretty easy to fix though.

1 Redux sagas uses Generators

function api

For this function to work it needs to be

function* api
  1. Prioritize the side effect functions over dispatch
dispatch(put({ ... })

You never need to use dispatch . Instead use put or the other side effect functions for dispatching events. Put is a blocking dispatch call. So this code should be

Passing put to dispatch will also cause an error in your app as dispatch always expects an object.

yield put({ ... })
  1. Root sagas are also Generators
const tweetSagas = [fork(watchApi)];

When making a rootSaga, you need to use the yield keyword in front of the fork function. A root saga is also a generator function, so this code has to change to the following.

export default function* tweetSagas () {
  yield fork(
    watchApi()
  )
}

You also have to use the yield keyword in front of your fork function. returning a value from generators with sagas won't do you much good.

I created and use saga-toolkit that allows async thunks to get resolved by sagas.

slice.js

import { createSlice } from '@reduxjs/toolkit'
import { createSagaAction  } from 'saga-toolkit'

const name = 'example'

const initialState = {
  result: null,
  loading: false,
  error: null,
}

export const fetchThings = createSagaAction(`${name}/fetchThings`)

const slice = createSlice({
  name,
  initialState,
  extraReducers: {
    [fetchThings.pending]: () => ({
      loading: true,
    }),
    [fetchThings.fulfilled]: ({ payload }) => ({
      result: payload,
      loading: false,
    }),
    [fetchThings.rejected]: ({ error }) => ({
      error,
      loading: false,
    }),
  },
})

export default slice.reducer

sagas.js

import { call } from 'redux-saga/effects'
import { takeLatestAsync } from 'saga-toolkit'
import API from 'hyper-super-api'
import * as actions from './slice'

function* fetchThings() {
  const result = yield call(() => API.get('/things'))
  return result
}

export default [
  takeLatestAsync(actions.fetchThings.type, fetchThings),
]

Here is the answer

file - configureAppStore.js

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import reducer from './reducer';
import toast from './middleware/toast.js';
import websocket from './middleware/websocket.js';
import createSagaMiddleware from 'redux-saga';
import tweetSagas from '../saga/tweet.js';

const configureAppStore = () => {
  const sagaMiddleware = createSagaMiddleware();

  const middlewares = [sagaMiddleware, websocket, toast];

  const middleware = [
    ...getDefaultMiddleware({ thunk: false }),
    ...middlewares,
  ];

  const store = configureStore({
    reducer: reducer,
    middleware: middleware,
  });

  sagaMiddleware.run(tweetSagas);

  return store;
};

export default configureAppStore;

file - saga/tweet.js

import { takeEvery, call, put, fork } from 'redux-saga/effects';
import axios from 'axios';
import * as actions from '../store/action/saga.js';
const port = process.env.REACT_APP_PORT;
const hostname = process.env.REACT_APP_LOCALHOST;
const baseURL = `http://${hostname}:${port}`;

const fetchApi = async ({ baseURL, url, method }) =>
  await axios.request({
    baseURL: baseURL,
    url: url,
    method: method,
  });

function* api(action) {
  const { url, method, onSuccess, onError } = action.payload;
  const options = {
    baseURL: baseURL,
    url: url,
    method: method,
  };
  try {
    const response = yield call(fetchApi, options);
    if (onSuccess)
      yield put({
        type: onSuccess,
        payload: response.data,
      });
  } catch (error) {
    if (onError) yield put({ type: onError, payload: error });
  }
}

function* watchApi() {
  yield takeEvery(actions.sagaApiCallBegan.type, api);
}

export default function* tweetSagas() {
  yield fork(watchApi);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM