Reputation: 58
I'm trying to add an element to an array in an object in my Redux store. I see the object get added to the store but it is not updating the component. If I leave the page and return it is showing up.
I'm pretty sure this is a state mutation issue but I can't figure out where I'm going wrong unless I fundamentally misunderstand what Immer is doing. In the component I'm using produce to add the string to the array, passing the new object to my reducer and using produce to add that object to an array of those objects.
I've looked through a ton of similar questions that all relate to state mutation, but the way I understand it the return from the component's call to produce should be a fully new object. Then in the reducer the call to produce should be returning a new object array.
This is the first time using Immer in a large project so it's entirely possible I don't fully get how it's working it's magic.
Component
import produce from 'immer';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import uuid from 'uuid/v4';
import { generate } from 'generate-password';
import { updateLeague } from '../../actions/leagues';
import { addTeam } from '../../actions/teams';
import { addUser } from '../../actions/users';
import Team from '../../classes/Team';
import User from '../../classes/User';
import UserWidget from '../utils/user/UserWidget';
class ViewLeague extends Component {
state = {
league : null,
isOwner : false,
owner : '',
teams : [],
inviteEmail: ''
};
componentWillMount() {
console.log('mount props', this.props.leagues);
const { leagues, uuid, leagueId, users, teams } = this.props;
if (leagues.length > 0) {
const league = leagues.find(league => league.uuid === leagueId);
const owner = users.find(user => league.leagueManager === user.uuid);
const leagueTeams = teams.filter(team => league.teams.includes(team.uuid));
this.setState({
league,
isOwner: league.leagueManager === uuid,
owner,
teams : leagueTeams
});
}
}
handleUpdate(event, fieldName) {
this.setState({ [ fieldName ]: event.target.value });
}
findUserByEmail(email) {
//Todo if not found here hit server
return this.props.users.find(user => user.email === email);
}
sendInvite = () => {
const { addTeam, addUser, updateLeague } = this.props;
const { league } = this.state;
const newManager = this.findUserByEmail(this.state.inviteEmail);
const newTeamUuid = uuid();
let newLeague = {};
if (newManager) {
const newTeam = new Team('New Team', newManager.uuid, newTeamUuid);
addTeam(newTeam);
} else {
const newPass = generate({
length : 10,
number : true,
uppercase: true,
strict : true
});
const newUserUuid = uuid();
const newUser = new User('', this.state.inviteEmail, newPass, '', '', newUserUuid);
addUser(newUser);
const newTeam = new Team('New Team', newUserUuid, newTeamUuid);
addTeam(newTeam);
newLeague = produce(league, draft => {draft.teams.push(newTeamUuid);});
updateLeague(newLeague);
console.log('invite props', this.props);
console.log('league same', league === newLeague);
}
//Todo handle sending email invite send password and link to new team
console.log('Invite a friend', this.state.inviteEmail);
};
renderInvite() {
const { isOwner, league, teams } = this.state;
if (isOwner) {
if ((league.leagueType === 'draft' && teams.length < 8) || league.leagueType !== 'draft') {
return (
<div>
<p>You have an empty team slot. Invite a fried to join!</p>
<input type="text"
placeholder={'email'}
onChange={() => this.handleUpdate(event, 'inviteEmail')}/>
<button onClick={this.sendInvite}>Invite</button>
</div>
);
}
}
}
renderViewLeague() {
console.log('render props', this.props.leagues);
const { league, owner, teams } = this.state;
const editLink = this.state.isOwner ?
<Link to={`/leagues/edit/${this.props.leagueId}`}>Edit</Link> :
'';
return (
<div>
<h2>{league.leagueName} </h2>
<h3>League Manager: <UserWidget user={owner}/> - {editLink}</h3>
<p>League Type: {league.leagueType}</p>
{this.renderInvite()}
<br/>
<hr/>
<h2>Teams</h2>
<span>{teams.map((team) => (<p key={team.uuid}>{team.teamName}</p>))}</span>
<span>
<h2>Scoring: </h2>
{league.scoring.map((score, index) => (
<p key={index}>{`Round ${index + 1}: ${score} points`}</p>
)
)}
</span>
</div>
);
}
render() {
if (!this.state.league) {
return (
<div>
<h2>No league Found</h2>
</div>
);
} else {
return (
<div>
{this.renderViewLeague()}
</div>
);
}
}
}
export default connect(
({ leagues: { leagues }, teams: { teams }, users: { users }, auth: { uuid } },
{ match: { params: { leagueId } } }) => ({
leagues,
teams,
users,
uuid,
leagueId
}), ({
addTeam : (team) => addTeam(team),
addUser : (user) => addUser(user),
updateLeague: (league) => updateLeague(league)
})
)(ViewLeague);
Reducer
import produce from 'immer';
import {
ADD_LEAGUE,
UPDATE_LEAGUE
} from '../actions/types';
const DEFAULT_LEAGUES = {
leagues: [ {
leagueName : 'Test League',
leagueManager: 'testUser12345',
uuid : 'testLeague12345',
teams : [ 'testTeam12345', 'testTeam23456' ],
scoring : [ 25, 20, 15, 10, 5, -5 ],
leagueType : 'draft'
} ]
};
const leaguesReducer = (state = DEFAULT_LEAGUES, action) =>
produce(state, draft => {
// noinspection FallThroughInSwitchStatementJS
switch (action.type) {
case ADD_LEAGUE:
draft.leagues.push(action.league);
case UPDATE_LEAGUE:
console.log('updating league', action.league);
const { league } = action;
const leagueIndex = draft.leagues.findIndex(fLeague => league.uuid === fLeague.uuid);
draft.leagues.splice(leagueIndex, 1, league);
}
});
export default leaguesReducer;
Any help is greatly appreciated!! More info available if needed
Upvotes: 3
Views: 3393