Reputation: 671
I have this structure in state
allVotes: [
{
id: "vote_1",
title: "How is your day?",
description: "Tell me: how has your day been so far?",
choices: [
{ id: "choice_1", title: "Good", count: 7 },
{ id: "choice_2", title: "Bad", count: 12 },
{ id: "choice_3", title: "Not sure yet", count: 1 },
],
},
{
id: "vote_2",
title: "Programming languages",
description: "What is your preferred language?",
choices: [
{ id: "choice_1", title: "JavaScript", count: 5 },
{ id: "choice_2", title: "Java", count: 9 },
{ id: "choice_3", title: "Plain english", count: 17 },
],
},
],
and need to update count. This works (in a Controller Component)
registerVote(vote, choice) {
this.setState(prevState => {
return {
allVotes: prevState.allVotes.map(
pVote =>
pVote.id !== vote.id
? pVote
: {
...pVote,
choices: pVote.choices.map(
pChoice =>
pChoice.id !== choice.id
? pChoice
: {
...pChoice,
count: pChoice.count + 1,
},
),
},
),
};
});
}
but it looks very complicated and I am not quite sure if this is the right way. Is there better way?
Upvotes: 2
Views: 210
Reputation: 23705
You can decomposite your component into smaller ones. Say: Form, Votes, Vote.
class Vote extends React.Component {
increment = () => {
this.setState(prevState => ({
count: prevState.count + 1
}), this.onItemChange);
}
onItemChange = () => {
this.props.onItemChange(this.state, this.props.id);
}
}
class Votes extends React.Component {
onItemChangedCallback = (itemToUpdate, idToUpdate) => {
this.setState(prevState => ({
allVotes: prevState.allVotes.map(vote => vote.id === idToUpdate? {...itemToUpdate}: vote; )
}), this.onVotesChangeCallback);
}
onVotesChangeCallback = () => {
this.props.onVotesChange(this.state.allVotes)
}
render() {
return (<div>this.state.allVotes.map(vote => <Vote id={vote.id} key={vote.id} vote={vote} onItemChange={this.onItemChangedCallback} />)</div>);
}
}
Besides it means more classes/components this fits "single responsibility" principle: so Votes
handles moving elements inside the list, deletion and adding new but not timestamp value, voters' amount or voters list. Vote
handles single item. And so on. Better to test, easier to read, easier to reuse in different context.
[UPD] after more thinking on that I believe that it's better passing unique id from Votes to Vote rather than positional index(as well as key
props suggests). And later use it id to figure out what collection's element should be replaced. Updated code snippet accordingly.
[UPD2] I guess Vote
can be functional/stateless component but I better keep it class-based to keep idea easier to follow
Upvotes: 2
Reputation: 138267
Thats actually the way to do if you want to keep immutability, however you could simplify it a bit by using some helper functions:
const change = (key, fn) => obj => ({ ...obj, [key]: fn(obj[key]) });
const changeOne = (filter, fn) => arr => arr.map(el => filter(el) ? fn(el) : el);
this.setState(change("allVotes", changeOne(v => v.id === vote.id, change("count", c => c + 1))));
Upvotes: 2