0
votes

I have an app with 2 reducers (city and doctor) I use sagas. I have a problem that with weird state overrides.

For example here is the start of fetch doctors action. As you see state was changed for doctor isLoading: false to isLoading: true enter image description here

After that fetch cities action was started right before. And isLoading for doctors was changed back to false. enter image description here

On every action dispatch, another reducer state reset.

enter image description here enter image description here

I have doubt that it's a NextJS specific problem so root store is creating couple of times and cause a race condition.

Technologies: react, redux, react-redux, next-redux-wrapper, next-redux-saga, redux-saga

app.js

....
export default withRedux(createStore)(withReduxSaga(MyApp))

store.js

import { applyMiddleware, createStore } from 'redux'
import createSagaMiddleware from 'redux-saga'

import rootReducer from './rootReducer'
import rootSaga from './rootSaga'


const bindMiddleware = middleware => {
  if (process.env.NODE_ENV !== 'production') {
    const { composeWithDevTools } = require('redux-devtools-extension')
    return composeWithDevTools(applyMiddleware(...middleware))
  }
  return applyMiddleware(...middleware)
}


function configureStore(initial={}) {
  const sagaMiddleware = createSagaMiddleware()
  const store = createStore(
    rootReducer,
    initial,
    bindMiddleware([sagaMiddleware])
  )

  store.sagaTask = sagaMiddleware.run(rootSaga)

  return store
}

export default configureStore

rootReducer.js

import { combineReducers } from 'redux'
import { cityReducer } from "./reducers/city"
import { doctorReducer } from "./reducers/doctor"

export default combineReducers({
  city: cityReducer,
  doctor: doctorReducer
})

city/reducer.js

import {actionTypes} from "../../actions/city"

const initialState = {
  cities: []
}

export const cityReducer = (state = initialState, action) => {
  switch (action.type) {
    case actionTypes.FETCH_CITIES:
      return initialState
    case actionTypes.FETCH_CITIES_SUCCESS:
      return {
        cities: action.data
      }
    case actionTypes.FETCH_CITIES_FAILURE:
      return initialState
    default:
      return initialState
  }
}

city/actions.js

export const actionTypes = {
  FETCH_CITIES: 'FETCH_CITIES',
  FETCH_CITIES_SUCCESS: 'FETCH_CITIES_SUCCESS',
  FETCH_CITIES_FAILURE: 'FETCH_CITIES_FAILURE',
}

export const fetchCities = () => {
  return {
    type: actionTypes.FETCH_CITIES
  }
}
export const fetchCitiesSuccess = (data) => {
  return {
    type: actionTypes.FETCH_CITIES_SUCCESS,
    data
  }
}
export const fetchCitiesFailure = () => {
  return {
    type: actionTypes.FETCH_CITIES_FAILURE
  }
}

city/saga.js

import {call, put, takeLatest} from "redux-saga/effects"
import {actionTypes, fetchCitiesFailure, fetchCitiesSuccess} from "../../actions/city"
import {getCities} from "../../api"


export function* watchFetchCitiesRequest() {
  yield takeLatest(actionTypes.FETCH_CITIES, workerFetchCitiesRequest)
}

function* workerFetchCitiesRequest() {
  try {
    const {data} = yield call(getCities)

    yield put(fetchCitiesSuccess(data.results))
  } catch (e) {
    yield put(fetchCitiesFailure())
  }
}

(!) For the doctors reducer/saga/actions the structure is the same except names.

Page.js is the main layout for every page. Basically wraps every page content in _document.js

const Page = ({dispatch}) => {
  useEffect(() => {
    dispatch(fetchCities())
  }, [])
} 
const mapStateToProps = ({city}) => ({cities: city.cities})
export default connect(mapStateToProps)(Page)

DoctorList.js Component that /doctor page consumes

import {useEffect} from "react"
import {fetchDoctors} from "../../actions/doctor"
import {getCity} from "../../utils/functions"
import {DoctorItemPreview} from "./DoctorItem"

const DoctorList = ({dispatch, isLoading, isError, response}) => {

  useEffect(() => {
    getCity() ? dispatch(fetchDoctors()) : null
  },[getCity()])
  return <>
    {!isLoading ? <>

    </> : <>
      {[...Array(10)].map((e, i) => <span key={i}>
        <DoctorItemPreview/>
      </span>)}
    </>}
  </>
}

const mapStateToProps = ({doctor, city}) => ({
  isLoading: doctor.isLoading,
  isError: doctor.isError,
  response: doctor.response,
})
export default connect(mapStateToProps)(DoctorList)

What can be the place where the problem appears? What parts of code do you need for the answer? Thanks

1

1 Answers

2
votes

I am pretty sure your reducers will overwrite your current state when returning initialState. See this answer on GitHub.

There are no known race conditions or other problems related to multiple root stores nor reducers in next-redux-saga.