I've got a React application and I'm trying to use Reux-ORM and I'm struggling with selectors. I've got a simple app with normalized data
// /models/team.js
import { Model, many, attr } from 'redux-orm';
import Player from './player';
class Team extends Model {
static reducer(action, Team, session) {
[...]
}
}
Team.modelName = "Team";
Team.fields = {
id: attr(),
players: many('Player;),
}
and
// /models/player.js
import { Model, attr } from 'redux-orm';
class Player extends Model {
static reducer(action, Player, session) {
[...]
}
}
Player.modelName = "Player";
Player.fields = {
id: attr(),
}
I register them into the ORM as follows:
// /selectors/selector.js
import { ORM } from 'redux-orm';
import Team from '../models/team';
import Player from '../models/player';
const orm = new ORM();
orm.register(Team, Player);
export default orm;
So far so good. Now I would like to have a React Component that renders a list of all teams with all players. So the first thing I did was to write a selector:
// /selectors/selector.js
import { createSelector } from "redux-orm";
import orm from "../models/index";
export const teams = createSelector(
orm,
state => state.orm,
session => {
return session.Team.all().toRefArray();
}
);
This gives a list of teams, but not yet its associated players. So in my component, I can now use teams
from my props with if I add a function const mapStateToProps = state => ({teams: teams(state)});
But now my question is, what's the best way to get the team-specific players? Do I add them to the returned teams in the selector? Or do I write a separate selector? And would I pass on the players to the the component directly, or rather make a separate component in the Team component?
class Teams extends React.Components {
render() {
return this.props.teams.map(team => {
/* Option 1: */
const teamPlayer = team.players // returned by initial selector
/* Option 2: */
const teamPlayers = [some call to selector based on 'team'];
/* Option 3: */
// Don't pass on the players but the id instead
return (
<Team
/* Option 1 & 2: */
players= {teamPlayers}
/* Option 3: */
teamId = {team.id} // and within the Team component make a selector call
/>
}
}
Implementationally, I got stuck at making the selectors and couldn't find examples for this case for redux-orm v0.19
, and conceptually I'm still not sure which way is the best to go. All help is much appreciated!
Update (2019-09-30):
Since v0.14 there is a much simpler way to create common selectors. First of all, the
teams
selector can be written like this:Similar to what you would do with a
QuerySet
you can create a selector to retrieve a team's players as an array of references:Using react-redux you can simply pass a single team to a React component which then fetches all related data upon store updates.
You could also use the
teamPlayers
selector in the parent component but in my experience it's usually better for reusability if a component retrieves its dependencies by itself. So sometimes it may even be better to passteam.id
toTeam
instead of the entireteam
ref. Didn't do it here because it would seem useless.Be aware that
teams(state)
returns all teams in the order of insertion. Do not try to index into it liketeams(state)[team.id]
as theteam.id
does not typically match the team's index in the array when iterating. To fetch a single team pass its primary key liketeams(state, team.id)
. For more details please take a look at our new docs.The short answer is: there is no right way. It has been asked here before.
However, the easiest way to do this is to append the player references directly like so:
This would allow you to access your players directly as an array property of your team objects, and you wouldn't need to add anything else. It has the downside of possible overhead in case you want to reuse the selector without accessing the players array, though. In your view you could either pass the whole team reference or parts of it as a prop:
Alternatively you could create a separate selector for each relation you need to query:
This has the fairly obvious problem of needing to add a selector or at least the minimum boilerplate each time you want to use a relationship accessor.
Calling selectors within components themselves is not the way to do it as only
mapStateToProps
will be called when your store updates. I would strongly advise against the third option unless you are willing and have a good reason toconnect
again in the child component.If you feel that all of this is kind of messy, you are definitely not alone -- I have been thinking about how to improve this for a while now. We may be able to have Redux-ORM provide an easier way to create or access these kinds of selectors that everyone needs. Right now it is very flexible but not exactly convenient, yet.