Update Redux state on route change
Asked Answered
F

3

12

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?

Felder answered 19/6, 2016 at 19:36 Comment(0)
M
21

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
    }
}
Muskrat answered 19/6, 2016 at 20:4 Comment(6)
is LOCATION_CHANGE the same as @@router/LOCATION_CHANGE?Felder
Yes. You could also use '@@router/LOCATION_CHANGE', but it's not recommended.Muskrat
I just realised what's causing the problem and it's slightly different from this question. Maybe you could help? #37912680Felder
Is this applicable as well if you want to reset the state of only a specific route only?Folderol
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.Gert
Also does not work on router v6Embosom
G
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).

Gifferd answered 5/7, 2017 at 4:22 Comment(2)
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.Harquebus
Won't this render with the previous state and then immediately render again with the new state?Bambino
F
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.

Falsecard answered 8/1, 2018 at 9:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.