I saw a twitter post by Dan Abramov recommending to export selectors with related reducers, and I was wondering, should selectors in general be 1-to-1 with reducers? Or is it perfectly OK to have selectors select state across multiple reducers, all depending on what information a component needs?
I seem to have selectors be doing two different things right now. Some selectors will select only the necessary state out of a specific reducer or specific reducers (so their only job is to pick out substates of my reducers), and then I have some selectors compute data based on information in a specific reducer or specific reducers (like redux's computing derived data section). I also have selectors that do a combination of the two.
One more thing, is it a good practice to ALWAYS access application state in reducers through selectors? (I use redux-saga which provides the "select" function and I think promotes that idea)
Anyway my selectors seem pretty unorganized to me, and I'm not sure what I'm doing wrong. I was hoping to get a sense of how to use selectors in a large application.
I'm using one selector per container instead. So I keep my reducers with the _raw_ data while I compose more concrete data with selectors in every container. In fact, I'm using them in the 3rd argument of the redux's connect method: mergeProps
connect(mapStateToProps, actions, mergeProps)(Container)
This way I can build callbacks for the view models combining store data with actions.
For instance:
const store = {
songs: [
{id: 1, title: 'Maybe', artist: 'Oasis'},
{id: 2, title: 'Creep', artist: 'Radiohead'},
{id: 3, title: 'Desorden', artist: 'Los Planetas'},
],
playlist: [
{id: 1, title: 'foo', songs: [1, 2]}
{id: 2, title: 'bar', songs: [3]}
],
currentPlaylist: 1
}
// actions
function playSong(songId) {
return {
type: 'PLAY_SONG',
songId
}
}
// in container
connect(mapStateToProps, {playSong}, playlistViewSelector)(PlaylistView)
const mapStateToProps = (state) => {
return {
playlist: state.playlist,
songs: state.songs,
currentPlaylist: currentPlaylist
}
}
const playlistViewSelector = createSelector(
(state) => state.playlist,
(state) => state.songs,
(state) => state.currentPlaylist,
(state, actions) => actions.playSong,
(playlist, songs, currentPlaylist, playSong) => {
return {
songs: playlist.find(p => p.id === currentPlaylist)
.map(songId => songs.find(song => song.id === songId))
.map(song => return {
name: song.name,
play: () => playSong(song.id)
})
}
})
The main point is the play method, so Container doesn't compose any data, this is done by the selector.
Good things on doing this way: It's pretty easy to test and removes logic from Containers.
In fact, in this case, we could create another selector to get the songs objects per playlist and use it in this selector, so we would get:
const playlistViewSelector = createSelector(
songsPerPlaylistSelector
(state, actions) => actions.playSong,
(songsPerPlaylist, playSong) => {
return {
songs: songsPerPlaylist.map(song => return {
name: song.name,
play: () => playSong(song.id)
})
}
})
Sorry for the long answer, I hope you find it useful.
No hey the longer the better just trying to learn here. Just a few questions:
I ask that last question because I've seen some advocate for using selectors everywhere in code so that selectors are the only thing that need to know the shape of the application state, and in the event that your state shape changes, you only need to worry about the selectors instead of also going into your thunks or sagas and changing how data is accessed (which I think is why Dan A. had mentioned exporting selectors with related reducers).
This implementation works form me as keep selectors easy to group, find and read; but don't take this as the only way to doing it.
Closing due to inactivity
my bad, I was hoping to get some other responses, but it doesnt look like anyone else has much to say anyway!
Hey @nickzarate, I didn't mean to imply you had done anything wrong! I was doing some tidying up and it just looked like the conversation had dried up.
Had the very same question. My thoughts are to lean toward always creating selectors. I found that practice helpful when moving part of your state to a new section of the tree. Application features continue to evolve over time and it's inevitable that your state tree will change with it.
We currently keep selectors outside of reducers as well. This helps when we combine selectors that grab different parts of the state tree.
I like the idea of storing selectors within reducer files, keeping selectors within the context of reducers makes sense. I could see importing selectors from other reducers files to work just fine. my only question would be which reducer imports the selector. Might make sense to have a vm specific reducer that imports selectors from a reducer that reflects the state on the server.
For future reference, this is the tweet from Dan Abramov: https://twitter.com/dan_abramov/status/664581975764766721
Most helpful comment
I'm using one selector per container instead. So I keep my reducers with the _raw_ data while I compose more concrete data with selectors in every container. In fact, I'm using them in the 3rd argument of the redux's connect method:
mergePropsThis way I can build callbacks for the view models combining store data with actions.
For instance:
The main point is the
playmethod, so Container doesn't compose any data, this is done by the selector.Good things on doing this way: It's pretty easy to test and removes logic from Containers.
In fact, in this case, we could create another selector to get the songs objects per playlist and use it in this selector, so we would get:
Sorry for the long answer, I hope you find it useful.