Christopher Mellor
Christopher Mellor

Reputation: 444

What is the best way to update redux from a child component?

I'm struggling to grasp the full power of Redux. I don't feel like it should be this complicated. I've got a "smart" parent component that has something like this -

Players.js

class Players extends Component {
state = {
players: this.props.players,
playerToEdit: null
};

componentWillReceiveProps(nextProps) {
const players = nextProps.players;
this.setState({
  players: nextProps.players
});
}

 getPhotos = avatar => {
this.setState({ avatar: avatar });
this.setState({ cameraMode: this.state.cameraMode ? false : true });
};

editPlayer = player => {
this.setState({
  playerToEdit: player
});
};

cancelPlayerEdit = () => {
this.setState({
  playerToEdit: null
});
};

dispatchUpdatedPlayer = updatedPlayer => {
this.props.dispatch(updatePlayerInfo(updatedPlayer));
this.setState({
  playerToEdit: null
});
};

render() {
const { hasCameraPermission } = this.state;
let players = this.state.players;
let { playerToEdit } = this.state;

players = players.sort(function(a, b) {
  let nameA = a.name.toLowerCase(),
    nameB = b.name.toLowerCase();
  if (nameA < nameB) return -1;
  if (nameA > nameB) return 1;
  return 0;
});

let cameraComponent = null;
if (true) {
  cameraComponent = (
    <ThisCamera id={this.state.playerId} onRefresh={this.getPhotos} />
  );
}

let playerScreen = null;
if (playerToEdit) {
  playerScreen = (
    <PlayerProfileScreen
      player={playerToEdit}
      onCancel={this.cancelPlayerEdit}
      onUpdate={this.dispatchUpdatedPlayer}
    />
  );
}

return (
  <View>
    <View style={styles.playerCards}>
      {players.map((player, key) => (
        <PlayerCard key={key} player={player} onPress={this.editPlayer} />
      ))}
    </View>
    {playerScreen}
  </View>
);
 }
 }


function mapStateToProps(state) {
const players = state.players;
players.map((player, key) => 
 return {
  players: players
 };
  }

export default connect(mapStateToProps)(Players);

and a child component that looks a bit like this -

class PlayerCard extends Component {
state = {
player: this.props.player,
onPress: this.props.onPress
};

componentWillReceiveProps(nextProps) {
const player = nextProps.player;

this.setState({
  player: player
});
}

  press = () => {
  this.state.onPress(this.state.player);
};

render() {
const { player } = this.state;
return (
  <View style={styles.card}>
    <TouchableOpacity onPress={this.press}>
      <View style={styles.avatarWrapper}>
        <Image
          style={styles.avatar}
          source={{
            uri:
              SERVER_ROOT_URL +
              "/avatar/" +
              player.id +
              "?" +
              `${new Date()}`
          }}
        />
      </View>
      <View style={styles.info}>
        <View>
          <Text style={styles.name}>{player.name}</Text>
        </View>
        <View>
          <Text style={styles.teams}>
            {this.state.displayedOrgUnit !== null
              ? player.orgUnits.map(it => it.name).join(", ")
              : ""}
          </Text>
        </View>
      </View>
    </TouchableOpacity>
  </View>
);
}
}

It seems strange to me that I have to dispatch actions AND use lifecycle methods. Is it because I'm calling a method defined in the parent? I feel like I'm not using Redux to the best of its abilities. Any help?

Upvotes: 0

Views: 1088

Answers (1)

John
John

Reputation: 116

The idea of the store is that is houses the state of a 'container' (the component that has a direct connection to the store), or rather, all containers for the entire application. The plain old react component (child) should receive all information from its props. It's very rare that I'll use the state of a component when I'm dealing with redux. I think the only time I use a components state is when I'm filling out a form and I don't want to keep temporary information in the store, but there might even be a better way to do that.

It'd probably would make sense to pass the information the child needs to render into the props of the child, which you're already doing. However, there is no reason to store that into the player's state as well. If the player is being kept in the store, and the store changes, then it will also re-render the child. The same thing goes with the action, onPress. The child has access to it's own properties, so again, there is no need to add it to it's state. In effect, the child doesn't need to have any state stored. If the parent changes the child's properties, then the child will update. This will of course also eliminate the need to use the child's life cycle functions here.

There is also absolutely nothing wrong with passing an action to a child to invoke.

I'd also look at using mapDispatchToProps in addition to the mapStateToProps, then you can call all of your actions with "this.props.ActionName"

const mapDispatchToProps = dispatch => {
  return {
    updatePlayer: updatedPlayer => {
      updatePlayerInfo(updatedPlayer);
    },

  };
};

function mapStateToProps(state) {
const players = state.players;
players.map((player, key) => 
 return {
  players: players
 };
  }

export default connect(mapStateToProps, mapDispatchToProps)(Players);

Cleans up the code and accomplishes the same job. Of course this is just opinion...

If you're looking for a good primer on redux and how it's meant to be used...

https://egghead.io/instructors/dan-abramov

Here are a few video tutorials from the father of redux.

Some of the other powerful mechanisms in redux are reducers that call reducers (ReducerComposition), or a single action which can call multiple reducers, or the fact that you can define a single action and call it from any container (Modularity in code). These are things that can not be done with Life Cycle functions... at least not easily.

Upvotes: 1

Related Questions