TypeError :state.foo is undefined - redux

When I hit a button that dispatches an action, my app breaks in the reducer. I used to have the filtering in the component but it is cleaner if the logic was in the reducer but the reducer doesn't seem to be able to see the state. It used to be able too.
//reducer.jsx
const initialPizzaOrderStatus =
{
voteList: [],
inCount: 0,
orderLocked: false,
}
const addedVotes = (state = initialPizzaOrderStatus.voteList, action) => {
switch (action.type) {
case ATTENDANCE_SUBMITTED:
if (state.indexOf(action.payload.username) !== -1) {
return state
}
return [ ...state, action.payload.username]
case ATTENDANCE_REVOKED:
if(state.indexOf(action.payload.username) !== -1)
{
return state.filter(function(value){
return value !== action.payload.username;
});
}
return state
default:
return state
}
}
const countVotes = (state = initialPizzaOrderStatus, action) => {
switch (action.type) {
case ATTENDANCE_SUBMITTED:
console.log("countVotes Sub")
console.log(state)
if (state.voteList.indexOf(action.payload.username) !== -1) {
return state.inCount
}
return state.inCount+1
case ATTENDANCE_REVOKED:
console.log("countVotes Rev")
console.log(state)
if(state.voteList.indexOf(action.payload.username) !== -1)
{
return state.inCount - 1
}
return state.inCount
default:
return state.inCount
}
}
export const pizzaOrderReducer = (state = initialPizzaOrderStatus, action={}) =>
{
console.log("pizzaOrderReducer Current State: ")
console.log(state)
switch(action.type)
{
case ORDER_LOCKED:
return Object.assign({}, state, {
orderLocked: true})
case ATTENDANCE_SUBMITTED || ATTENDANCE_REVOKED:
return {
voteList: addedVotes(state.voteList, action),
inCount: countVotes(state.inCount, action),
};
default:
return state
}
}
//action.jsx
import {
ATTENDANCE_SUBMITTED,
ATTENDANCE_REVOKED,
} from './constants'
const voteInUnsafe = ( username ) => ({
type: ATTENDANCE_SUBMITTED,
payload: {
username,
}
});
export const voteIn = (username) => (dispatch) => {
dispatch(voteInUnsafe(username))
}
const voteOutUnsafe = ( username ) => ({
type: ATTENDANCE_REVOKED,
payload: {
username,
}
});
export const voteOut = (username) => (dispatch) => {
dispatch(voteOutUnsafe(username))
}
//VotingContainer.jsx
const VotingContainer = ({ voteIn, voteOut, username }) => (
<VotingButtons>
<div className='button-container'>
<div className='inner-container mt20'>
<Button
variant="contained"
color="default"
onClick={() => voteIn(username)}
>
I'm In!
</Button>
</div>
<div className='inner-container mt20'>
<Button
variant="contained"
color="default"
onClick={() => voteOut(username)}
>
I'm Out!
</Button>
</div>
</div>
</VotingButtons>
)
VotingContainer.propTypes = {
username : PropTypes.string.isRequired,
voteIn: PropTypes.func.isRequired,
voteOut: PropTypes.func.isRequired,
}
const mapDispatchToProps = {
voteIn,
voteOut,
}
export default connect(
null,
mapDispatchToProps,
)(VotingContainer)
I expect it to update the voteList state but it gives me a TypeError when trying to read state in the reducer. See error below
TypeError: state.voteList is undefined
countVotes
E:/Users/Joseph/Documents/GitHub/Pizza Wednesday/ngc.pizza/client/src/reducers.jsx:90
87 | switch (action.type) {
88 | case ATTENDANCE_SUBMITTED:
89 | console.log("countVotes Sub")
> 90 | if (state.voteList.indexOf(action.payload.username) !== -1) {
| ^ 91 | return state.inCount
92 | }
93 | return state.inCount+1

You are passing
state = initialPizzaOrderStatus.voteList so state won't have a voteList in it, is it itself.
change:
if (state.voteList.indexOf(action.payload.username) !== -1)
to:
if (state.indexOf(action.payload.username) !== -1)
Edited:
A better fix could be passing initialPizzaOrderStatus object (as initialState) instead voteList property only. Then you will get the rest of the properties available in state
const addedVotes = (state = initialPizzaOrderStatus.voteList, action) => {
->
const addedVotes = (state = initialPizzaOrderStatus, action) => {

Related

Auto-delete a parent entity in a redux reducer when one of it children is empty

I have a Redux reducer which handles among other things products. Each product has an array field named productImages. An action type DELETE_PRODUCT_IMAGE_SUCCESS removes a specific image from that array.
How can I automatically delete a product once all its productImages are removed?
I've tried using useEffect to no avail.
My codesandbox is available here.
case appConstants.DELETE_PRODUCT_IF_NO_IMAGE:
return {
...state,
products: state.products.filter(
(product) => product?.productImages?.length > 0
)
};
You could update the list when you delete an image from the product.
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
products: state?.products
.map((item, index) => {
if (index !== action.payload?.productIndex) return item;
return {
...item,
productImages: item?.productImages.filter(
({ imageFileName = null }) =>
imageFileName !== action?.payload?.imageFileName
)
};
})
.filter((product) => product?.productImages?.length > 0)
};
case appConstants.DELETE_PRODUCT_IF_NO_IMAGE:
return state;
It seems you are calling the purge method only once when the app loads:
useEffect(() => {
dispatch(deleteProductIfNoImage());
}, [dispatch]);
A more efficient way would be to introduce sub-reducers, it would make your task easier:
const product = (state, action) => {
const { productImages = [] } = state;
const { imageFileName } = action.payload || {};
switch (action.type) {
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
productImages: productImages.filter(
(productImage) =>
productImage.imageFileName !== imageFileName
),
};
default:
return state;
}
};
const products = (state, action) => {
const { productIndex } = action.payload || {};
switch (action.type) {
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
products: products
.map((item, index) =>
index === productIndex ? product(item) : item
)
.filter((item) => item.productImage?.length > 0),
};
default:
return state;
}
};
const appReducer = (state, action) => {
switch (action.type) {
case appConstants.DELETE_PRODUCT_IMAGE_SUCCESS:
return {
...state,
products: products(state, action),
};
default:
return state;
}
};
If you follow this sub-reducers advice which Dan Abramov suggested in his online redux course + switch to dictionaries and product ids, the reducer code would be much cleaner and possibly more maintainable:
const product = (state, action) => {
switch (action.type) {
case appConstants.CHANGE_PRODUCT_CODE:
return {
...state,
newProductCode: action.payload.productCode,
};
case appConstants.CHANGE_PRODUCT_NAME:
return {
...state,
productName: action.payload.productCode,
};
case appConstants.CHANGE_PRODUCT_CATEGORY:
return {
...state,
productName: action.payload.productCategory,
};
}
};
const products = (state = initialState, action) => {
switch (action.type) {
case appConstants.CHANGE_PRODUCT_CODE:
case appConstants.CHANGE_PRODUCT_NAME:
case appConstants.CHANGE_PRODUCT_CATEGORY:
return {
...state,
products: {
...state,
[action.id]: product(products[action.id], action)
}
}
}
};
Furthermore, I recommend checking out redux toolkit which is the new redux standard for building stores and perhaps utilizing immerJs instead of sub-reducers in your redux reducer code.

TypeError: dispatch is not a function when clicking the toggle button

I am using react redux-thunk. I have a set of users data that I get from an API and this is the schema:
.
I've connected the "active" property with the checked attribute of a Switch MUI button, so naturally when calling the API I have some users with their switch button already on "true". What I am trying to do is to just make the switch functional, and just be able to click it and change its state, not necessarily doing anything with that.
Here's my toggleType.js:
export const TOGGLE = "TOGGLE";
Here's my toggleAction.js:
import { TOGGLE } from "./toggleType";
const statusToggleAction = () => {
return {
type: TOGGLE,
};
};
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction);
};
};
Here's my toggleReducer.js:
import { TOGGLE } from "./toggleType";
const initialState = {
status: false,
};
const toggleReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE:
status: true;
default:
return state;
}
};
export default toggleReducer;
Everything is under my userContainer.js, like that:
function UserContainer({ userData, fetchUsers }) {
useEffect(() => {
fetchUsers();
}, []);
return userData.loading ? (
<h2>Loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<Container maxWidth="lg" style={{ flexGrow: 1, height: "100%" }}>
<h2>User List</h2>
<div>
{userData &&
userData.users &&
userData.users.map((user) => (
<div key={user.id}>
<p>{user.name}</p>
<Switch checked={user.active} onChange={statusToggle()} />
</div>
))}
</div>
</Container>
);
}
const mapStateToProps = (state) => {
return { userData: state.user, statusToggle: state.status };
};
const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers()),
statusToggle: () => dispatch(statusToggle()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
This is the error I am getting whenever I am clicking one of those switches:
Any ideas are welcome, I "learned" redux like 3 days ago!
toggleReducer function in toggleReducer.js, replace status: true; with return { status: true }.
Just return action in statusToggle function in toggleAction.js without dispatch as following.
export const statusToggle = () => {
return statusToggleAction();
};
Or just call statusToggleAction directly in userContainer.js as following.
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction());
};
};

update value of an element of object array in redux store

There is a challenge update existing elements value in json array in redux store by an action creater.
You can run code here also shared it below;
console.clear()
const CreateTeam = (team, point) => {
return {
type:"CREATE_TEAM",
payload: {team, point}
}
}
const UpdateTeam = (team, point) => {
return {
type:"UPDATE_TEAM_POINT",
payload: {team, point}
}
}
const TeamReducer = (state = [], action) => {
if(action.type == "CREATE_TEAM")
{
return [...state, action.payload]
}
if(action.type == "UPDATE_TEAM_POINT")
{
let point=action.payload.point;
return [...state, {
...state.teams,
point:point
}]
}
return state;
}
const { createStore, combineReducers } = Redux;
const league = combineReducers({
teams: TeamReducer
})
const store = createStore(league);
store.dispatch(CreateTeam("TeamA",10));
store.dispatch(CreateTeam("TeamB",20));
store.dispatch(UpdateTeam("TeamA",15));//not work
console.log(store.getState())
create actions works fine, I expected the point value of TeamA set to 15.. but its added new object has only "point" property value 15
There is an error in name of actionTypes:
action dispatches type:"UPDATE_TEAM"
reducer handles action.type == "UPDATE_TEAM_POINT"
You have to perform immutable change, try this:
const TeamReducer = (state = [], action) => {
if(action.type == "CREATE_TEAM")
{
return [...state, action.payload]
}
if(action.type == "UPDATE_TEAM")
{
const {team, point} = action.payload;
const changedIdx = state.findIndex((item) => item.team === team);
return [...state.slice(0, changedIdx), action.payload, ...state.slice(changedIdx + 1)]
}
return state;
}

TypeError: defaultPieces inside of componentDidMount is not a function

Inside of my PossibleMatches component, I have imported a few asynchronous functions from my actions/index file and as I call these functions inside of componentDidMount, the Error I'm getting back is: defaultPieces is not a function.
Below are the contents of my PossibleMatches component and actions/index.js file:
For the sake of brevity, I did my best to add everything that is relevant to the main problem.
PossibleMatches.js
import { connect } from 'react-redux';
import {arrangePieces, defaultPieces, setEvaluatedPiece, getCorrespondingPieces} from '../actions/index';
constructor(props){
super(props);
const initialState = {
currentComponent: {whichPiece: {whichType: null, currentUpperComponent: null, currentLowerComponent: null}},
UpperComponents: this.props.UpperComponents,
LowerComponents: this.props.LowerComponents,
UpperComponentEnabled: false,
LowerComponentEnabled: false,
isFetched: false,
isFetching: true
}
this.state = {
...initialState
}
this.residingUpperComponent = createRef();
this.residingLowerComponent = createRef();
//Also need to this.prop.setEvaluatedPiece based off of this.props.setCurrentComponent if callback from Lower or Upper Components elapses time
this.setNewPiece = this.setNewPiece.bind(this);
this.renderDecision = this.renderDecision.bind(this);
}
async componentDidMount(){
const {store} = this.context;
let stateRef = store.getState();
const { defaultPieces, arrangePieces } = this.props;
try {
const summon = () => { defaultPieces();
arrangePieces();}
await summon();
} catch(error){
throw Error(error);
}
console.log("AFEWOVMAOVHIE")
this.setState({isFetched: true, isFetching: false});
}
renderDecision(){
const { currentComponent, LowerComponentEnabled, UpperComponentEnabled, isFetching, isFetched} = this.state;
const { suggestedBottoms, suggestedTops, UpperComponents, LowerComponents } = this.props;
if (isFetching){
return (<div className='activityLoader'>
<ActivityIndicator number={3} duration={200} activeColor="#fff" borderWidth={2} borderColor="50%" diameter={20}/>
</div>);
} else if (isFetched){
return (
<div className = "PossibleMatches_Container">
<i className = 'captureOutfit' onClick = {this.snapshotMatch}></i>
<TransitionGroup component = {PossibleMatches}>
** ** ** {UpperComponents.map((component) => {
return (<UpperComponent key={component.created_at} id={component.id}
switchComponent={this.switchFocus}
setCurrentPiece={this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {() => {if (LowerComponentEnabled === false){this.setState({LowerComponentEnabled: true})} else return; this.setState({currentLowerComponent: suggestedBottoms[0]})}}
isLowerComponentEnabled = {LowerComponentEnabled}
ref = {this.residingUpperComponent}
className = {currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>
)
})
}
</TransitionGroup>
<TransitionGroup component = {PossibleMatches}>
{LowerComponents.map((component) => {
return (<LowerComponent key={component.created_at} id={component.id}
setCurrentPiece = {this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {() => {if (UpperComponentEnabled === false){this.setState({UpperComponentEnabled: true})} else return; this.setState({currentUpperComponent: suggestedTops[0]})}}
switchComponent = {this.switchFocus}
isUpperComponentEnabled = {UpperComponentEnabled}
ref = {this.residingLowerComponent}
className = {this.state.currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : this.state.currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>)
})
}
</TransitionGroup>
</div>
)
}
}
render(){
return(
<div className = 'GorClothingContainer'>
<Wardrobe upperComponent={this.state.currentComponent.whichPiece.currentUpperComponent} lowerComponent={this.state.currentComponent.whichPiece.currentLowerComponent} enableCapture={(snapshot) => this.snapshotMatch = snapshot} />
{this.renderDecision()}
</div>
);
}
function mapStateToProps(state){
const { UpperComponents, LowerComponents, contemplated_piece, extraTops, extraBottoms, standaloneBottoms, standaloneTops, suggestedBottoms, suggestedTops } = state.possibleMatches;
return {UpperComponents, LowerComponents, contemplated_piece, extraTops, extraBottoms, standaloneBottoms, standaloneTops, suggestedBottoms, suggestedTops };
}
export default connect(mapStateToProps, {defaultPieces, arrangePieces, getCorrespondingPieces, setEvaluatedPiece})(PossibleMatches)
Inside of my actions/index.js
export function defaultPieces(){
return function(dispatch){
fetch(`${API_URL}/possible_matches/setup_possible_matches`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then((res) => res.json())
.then((json) => {
console.log('defaultPieces: ', json);
dispatch(getInitialPieces(json))
})
}
}
export function getInitialPieces(request){
return {
type: INITIAL_PIECES,
payload: request
}
}
Inside of PossibleMatches reducer:
import {INITIAL_PIECES, GET_ANCILLARY_PIECES, ORGANIZE_PIECES, SET_CONTEMPLATED_PIECE} from '../actions/types';
const initialState = {
UpperComponents: [],
LowerComponents: [],
contemplated_piece: null,
extraTops: [],
extraBottoms: [],
standaloneTops: [],
standaloneBottoms: [],
suggestedTops: [],
suggestedBottoms: []
}
export default function(state = initialState, action){
switch(action.type){
case INITIAL_PIECES:
return {...state, {contemplated_piece: action.payload.contemplated_piece,
extraTops: action.payload.extra_tops,
extraBottoms: action.payload.extra_bottoms,
standaloneTops: action.payload.standalone_tops,
standaloneBottoms: action.payload.standalone_bottoms,
suggestedTops: action.payload.suggested_tops,
suggestedBottoms: action.payload.suggested_bottoms}
case GET_ANCILLARY_PIECES:
return {...state, extraTops: action.payload.extra_tops,
extraBottoms: action.payload.extra_bottoms,
standaloneTops: action.payload.standalone_tops,
standaloneBottoms: action.payload.standalone_bottoms,
suggestedTops: action.payload.suggested_tops,
suggestedBottoms: action.payload.suggested_bottoms}
case ORGANIZE_PIECES:
return {...state, UpperComponents: action.payload.UpperComponents,
LowerComponents: action.payload.LowerComponents}
case SET_CONTEMPLATED_PIECE:
return {...state, contemplated_piece: action.payload.contemplated_piece}
default:
return state;
}
}
Because defaultPieces is not a valid function to the PossibleMatches components, it interferes with the interpretation of the UpperComponents prop that comes from mapStateToProps function (denoted with an * above).
What is peculiar is the json logged out to the console from both the arrangePieces and defaultPieces methods:
It was a bizarre fix, but I basically needed to set conditions in ComponentDidMount to halt the program based on the state of UpperComponents relative to what got returned from this.props.arrangePieces().
if (UpperComponents.length === 0){
return;
} else {
this.setState({isFetched: true, isFetching: false});
}
Since the component rerenders, I thought it was idealistic to add a componentWillRecieveProps lifecycle method to deal with incoming props:
componentWillReceiveProps(nextProps){
if (nextProps.contemplated_piece !== this.props.contemplated_piece){
if (nextProps.contemplated_piece.merch_type === 'top'){
this.setState({currentLowerComponent: nextProps.suggestedBottoms[0],
currentUpperComponent: nextProps.contemplated_piece});
}
else if (nextProps.contemplated_piece.merch_type === 'bottom'){
this.setState({currentLowerComponent: nextProps.contemplated_piece,
currentUpperComponent: nextProps.suggestedTops[0]});
}
}
else {
return null;
}
}

Redux React Todo Application has no indication of error but doesn't work

I'm following egghead.io Redux course by Dan.
However I've no idea why my todo app is not working. It doesn't output any error and any warning, just doesn't work.
Can you please give me some hint.
This is my jsbin.
https://jsbin.com/cozecip/33/edit?js,output
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
};
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
id: state.id,
text: state.text,
completed: !state.completed
};
default:
return state;
}
};
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
);
default:
return state;
}
};
const visibilityFilter = (state = 'SHOW_ALL', action) =>
{
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
const { createStore } = Redux;
const store = createStore(todoApp);
const { Component } = React;
const FilterLink = ({
filter,
currentFilter,
children,
onClick
}) => {
if (filter === currentFilter){
return <span>{children}</span>
}
return (
<a href="#" onClick ={onClick}>{children}</a>
);
};
const Todo = ({
onClick,
completed,
text
}) => (
<li
onClick={onClick}
style={{
textDecoration:
completed ?
'line-through' :
'none'
}}
>
{text}
</li>
);
const TodoList = ({
todos,
onTodoClick
}) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const AddTodo = ({
onAddClick
}) => {
let input;
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
onAddClick(input.value)
input.value = '';
}}>
Add Todo
</button>
</div>
);
}
const Footer = ({
visibilityFilter,
onFilterClick
}) => (
<p>
Show:
{' '}
<FilterLink
filter='SHOW_ALL'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
All
</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
Active
</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
Completed
</FilterLink>
</p>
)
const getVisibleTodos = (todos, filter) => {
switch(filter){
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed);
default:
return todos;
}
}
let nextTodoId = 0;
const TodoApp = ({
todos,
visibilityFilter
}) => {
return (
<div>
<AddTodo
onAddClick={
text =>
store.dispatch({
type: 'ADD_TODO',
text: this.input.value,
id: nextTodoId++
})
}
/>
<TodoList
todos={
getVisibleTodos(
todos,
visibilityFilter
)
}
onTodoClick={id =>
store.dispatch({
type: 'TOGGLE_TODO',
id
})
}
/>
<Footer
visibilityFilter={visibilityFilter}
onFilterClick={filter =>
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter
})
}
/>
</div>
);
}
const render = () => {
ReactDOM.render(
// Render the TodoApp Component to the <div> with id 'root'
<TodoApp
{...store.getState()}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();
Make sure you have added the correct ID in index.js
it should look like this.
document.getElementById('your div id from index.html should go here')

Resources