11

I've been trying to figure this out for a while and I'm getting more and more confused.

I want to reset/change Redux state every time I leave or change route. I'm using react-router-redux with history.listener dispatching an action every time route changes

history.listen(location => store.dispatch(resetManualsCategory()));

Action creator:

export function resetManualsCategory() {
    return {
        type: 'RESET_MANUALS_CATEGORY'
    }
}

Reducer

export function manualCategories(state=[], action) {
    switch (action.type) {
        case 'SELECT_MANUALS_CATEGORY':
           [...]

        case 'RESET_MANUALS_CATEGORY':
            console.log('test reducer');
            return Object.assign({}, state, {
                manualCategory: 'test'
            })

        default:
            return state
    }
}

What confuses me the most, the state updates if I refresh the page or click twice on the route in the top nav, but a single route change doesn't affect the redux state even though the action and reducer fire (displays the test message in the console).

What am I doing wrong and what actually is happening here?

spik3s
  • 1,169
  • 2
  • 11
  • 26

3 Answers3

22

react-router-redux provides a LOCATION_CHANGE action, which is already dispatched on every route change. You could do simple:

import { LOCATION_CHANGE } from 'react-router-redux'

export function manualCategories(state=[], action) {
    switch (action.type) {
        case LOCATION_CHANGE:
            /*
              action.payload is something like:
              {
                pathname: '/',
                search: '',
                hash: '',
                state: null,
                action: 'PUSH',
                key: 'xwl8yl',
                query: {},
                $searchBase: {
                  search: '',
                  searchBase: ''
                }
              }
            */

        default:
            return state
    }
}
Diego Haz
  • 874
  • 1
  • 5
  • 24
  • is ``LOCATION_CHANGE`` the same as ``@@router/LOCATION_CHANGE``? – spik3s Jun 19 '16 at 21:12
  • Yes. You could also use '@@router/LOCATION_CHANGE', but it's not recommended. – Diego Haz Jun 19 '16 at 21:19
  • I just realised what's causing the problem and it's slightly different from this question. Maybe you could help? http://stackoverflow.com/questions/37912180/react-router-redux-takes-two-clicks-on-a-link-to-update-location-state – spik3s Jun 19 '16 at 21:49
  • Is this applicable as well if you want to reset the state of only a specific route only? – ickyrr Nov 13 '17 at 09:55
  • 2
    This answer doesn't work anymore. react-router-redux has been renamed to connected-react-router, and it doesn't look like it works the same way anymore. – DLAN Oct 12 '20 at 05:47
8

I used different approach for resetting the state when changing routes. Instead of listening to history/ router, I used componentWillMount event on my page containers to dispatch a "reset page" action. Example:

Router:

<Provider store={store}>
  <Router history={history}>
    <Route path="/page1" component={Page1Container} />
    <Route path="/page2" component={Page2Container} />
  </Router>
</Provider>

Page1 container:

class Page1Container extends Component {

  componentWillMount() {
    this.props.resetPage()
  }

  render() {
    // render Page1
  } 
}

const mapStateToProps = (state) => {
  return {
    // ...
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    // ...
    resetPage: () => { dispatch({type: 'PAGE1_RESET_PAGE'}) }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Page1Container)

Reducer:

const initialState = null

export function Page1Reducer (state = initialState, action) {
  switch (action.type) {
    case 'PAGE1_RESET_PAGE': {
      state = initialState
      break
    }

    // ...
  }
}

Update: As @DavidHarkness mentioned, the flaw of this approach is that the component will render twice on mounting. And apart from that, I now tend to think that this solution is not elegant and it is better to avoid using bussiness logic inside components. Moving the logic inside the reducer would be a better option (see @DiegoHaz answer).

AlexM
  • 3,063
  • 2
  • 20
  • 24
  • This seems like it would be a good solution if for some reason you only need to reset the part(s) of state the component used. – MattD Aug 03 '17 at 03:46
  • 1
    Won't this render with the previous state and then immediately render again with the new state? – David Harkness May 01 '20 at 20:52
3

Currently I'm making use of componentWillUnmount() to do this. I have an action setup in the reducer I want to reset to reset its state and I dispatch the action from the componentWillUnmount() method.

One issue you may experience with this is that when using React Router a route change will not trigger a refresh and a component will not remount on the same route, so for example if you have a posts/new and a posts/edit/:id route both using the same component to edit a post, going between these routes will not cause the component to remount.

Because of this componentWillUnmount() will NOT run, the same component will just receive new props. Therefore you could use componentWillReceiveProps(newProps) to perform a comparrison and update accordingly.

If you do want to force the component to remount you need to ensure the component you want to remount has a different key attribute, see here and here for more information.

tomhughes
  • 3,263
  • 1
  • 17
  • 30