0

I am storing data for various rooms in a global app container. I have a sub-container called roomDetails that uses a reselect selector to select a room from the global state using ownProps.params.slug. This is done using mapStateToProps.

My problem is that when I change route params (rooms/:roomId) from:

rooms/roomid1

to

rooms/roomid2

the component room props are not updating.

I'm changing the route using Link:

<Link to={"/room/roomid1"} key={"room-roomid1"}>room 1</Link>

How can I get the component's room props to reselect a room from the global rooms state when the route params change?

Note, the room props load as they should on first page load, or if i go to a route that's outside of the dynamic route, and then return. They just don't load correctly when switching between the same dynamic route, where only component properties (route params) are changing.

RoomDetails/index.js

const mapStateToProps = (state, ownProps) => {
  return createStructuredSelector({
    room: selectRoom(ownProps.params.roomId),
  });
}

...

  render() {
    let roomContent = null;
    if (this.props.room) {
      roomContent = (
        <div>
          {this.props.room.get('name')}
        </div>
      );
    }
    return (
      <div>
        {roomContent}
      </div>
    );
  }

selectors.js

import { createSelector } from 'reselect';

const selectGlobal = () => (state) => state.get('global');

const selectRooms = () => createSelector(
  selectGlobal(),
  (globalState) => globalState.get('rooms')
);

const selectRoom = (roomId) => createSelector(
  selectRooms(),
  (roomsState) => roomsState && roomsState.get(roomId)
);
Dillon S.
  • 3
  • 2

2 Answers2

0

Reselect will only respond to whatever variables are passed to the first argument. Therefore, you must update some variable whenever that route changes. There are two options that come to mind:

  • Implement react-router-redux and then respond to it in your selector

  • Dispatch an event that updates the roomId in your store on the component mounted at that route.

For the second option, you would do something like this. Say that this component is mounted at the route /rooms/:roomId:

class Rooms extends Component {
  changeRoom = (roomId = this.props.roomId) => this.props.setRoom(roomId);

  componentDidMount() {
    this.changeRoom();
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.roomId !== nextProps.roomId) {
      this.changeRoom(nextProps.roomId);
    }
  }
}

Say that this updates state.room.selected. You could specify that in your selector.

import { createSelector } from 'reselect';

const getRooms = state => state.rooms.data;
const getSelected = state => state.rooms.selected;

export const getCurrentRoom = createSelector(
  [ getRooms, getSelected ],
  (rooms, selected) => rooms.find(room.id === selected)
)
corvid
  • 10,733
  • 11
  • 61
  • 130
  • Shouldn't the selector "selectRoom" respond though since what I'm passing into it (ownProps.params.roomId) _IS_ changing? – Dillon S. Jan 10 '17 at 16:18
  • can you console.log it to confirm that it is changing? – corvid Jan 10 '17 at 16:23
  • When I do "componentWillReceiveProps(nextProps) { console.log('Next room:', nextProps.params.roomId); }", it prints the correct room id every time I click a link to change rooms – Dillon S. Jan 10 '17 at 16:25
  • So props.params.roomId (route param) is updating, but props.params.room is not. – Dillon S. Jan 10 '17 at 16:26
  • I mean, log it in the selector. Eg: `(rooms, selected) => { console.log(rooms, selected); return rooms.get(selected) }` – corvid Jan 10 '17 at 17:02
  • No, the selector is only selecting the room once, even though the arguments to the selector are changing. Is this because I'm using createStructuredSelector? It almost certainly has to do with memoization, I just can't figure out exactly why the selector isn't calling, given that I can confirm the props are changing. – Dillon S. Jan 10 '17 at 17:07
  • Also `mapStateToProps` is only being called once. It doesn't call again when the route changes, even though the properties change in `componentWillReceiveProps` – Dillon S. Jan 10 '17 at 17:11
0

I removed the params from selectRoom and changed mapStateToProps to:

const mapStateToProps = createStructuredSelector({
  room: selectRoom(),
});

I then updated the selectors like this and it's working...

const selectGlobal = () => (state) => state.get('global');

const selectedRoom = () => (state, props) => props && props.params.roomId;

const selectRooms = () => createSelector(
  selectGlobal(),
  (globalState) => globalState.get('rooms')
);

const selectRoom = () => createSelector(
  selectRooms(),
  selectedRoom(),
  (roomsState, roomId) => roomsState.get(roomId)
);

I still don't understand why the selector wasn't being called when I was passing props.params.roomId to it...

Maybe since selectRooms() has no argument it always gets called?

Dillon S.
  • 3
  • 2