lundegaard / redux-tools

💪 Maintaining large Redux applications with ease.
https://redux-tools.js.org
MIT License
34 stars 9 forks source link

Clean up accion changes state #164

Open mir333 opened 4 years ago

mir333 commented 4 years ago

Hi

I have a tabbed UI and the taps have a connection to an epic that loads data for it. The UI works mostly OK and when I'm changing the tabs the redux-tools/CLEAN_UP is run. It is usually empty but for one tab it resets the state. The tabs are nearly identical. They just have different epics connected.

Any idea why the CLEAN_UP is modifying the state for one tab and not for the others?

wafflepie commented 4 years ago

Hi, I'm assuming you're running the latest version of Redux Tools (0.9.1 for all packages). If not, I suggest upgrading to this version as the older ones have some known issues.

CLEAN_UP_STATE is only dispatched when a reducer is ejected. This can happen for a variety of reasons:

  1. Manually calling store.ejectReducers.
  2. The namespace or feature props change.
  3. The reducer(s) change.
  4. The component is completely remounted.

Reasons 1. and 2. are quite obvious. The latter ones are more difficult to track down and often happen when you're creating React components dynamically and not memoizing them properly using useMemo. Make sure that the components wrapped in your withReducers decorators are not unmounting when they shouldn't. You can check this easily by using

useEffect(() => {
  return () => alert('unmounted')
}, [])

inside the component that is wrapped in your withReducers decorator. The solution is to either improve the memoization, move withReducers up the component scope so it's not a part of the subtree which is being remounted (component cannot be memoized properly), or by passing isPersistent: true as the second argument to withReducers (note that state will stay in the Redux store even after you exit the relevant part of the application, this option should be used with caution).

Here's another example of wrong implementation:

const Something = () => null

const App = ({ someReducer }) => {
  const SomethingContainer = withReducers({ some: someReducer })(Something)

  return <SomethingContainer />
}

Again, it's necessary to use useMemo to create SomethingContainer and memoize over someReducer. In general, it's recommended to always define reducers statically.


Without seeing your code, it's hard to know where the issue may be, but I suspect that you're using withReducers somewhere inside your tabs (and thus the current tab is dismounted when you tab to another tab).

It's not possible to do something like this:

const TabA = withReducers({ common: commonReducer })(A)
const TabB = withReducers({ common: commonReducer })(B)
const TabC = withReducers({ common: commonReducer })(C)

const MyTabs = ({ currentTab }) => {
  if (currentTab === 'a') return <TabA />
  if (currentTab === 'b') return <TabB />
  if (currentTab === 'c') return <TabC />
  return null
}

state.common will always be lost upon tabbing. It's necessary to either wrap MyTabs in withReducers({ common: commonReducer }) or use custom logic with useReducers or calling store.inject/ejectReducers manually.