I am utterly confused by this. I have a select box component where I have a selected prop. when true I show a checkmark in the box, if false, not. Now the issue that I have is that after three times clicking it doesn't toggle anymore.
The strangest thing is that I do pass a true boolean to the component's 'selected' prop (see logs), but when I make a log of the 'selected' prop in that child component, it says it is false.
Anyone has a clue why this could be different?
Result of the logs seen below
Parent component: Services.tsx
import React, { useReducer } from "react";
import { makeStyles } from "@material-ui/core";
import { ToggleBox } from "components/ToggleBox";
const useStyles = makeStyles((theme) => ({
container: {
display: "flex",
},
}));
const servicesReducer = (state, action) => {
switch (action.type) {
case "toggle option":
const isCurrentlySelected = state.selectedOptions.includes(
action.payload.name
);
let newSelectedOptions = state.selectedOptions;
if (isCurrentlySelected) {
newSelectedOptions = newSelectedOptions.filter(
(item) => item !== action.payload.name
);
} else {
newSelectedOptions.push(action.payload.name);
}
return {
...state,
selectedOptions: newSelectedOptions,
};
case "add options":
return {
...state,
};
}
};
export const Services = () => {
const classes = useStyles();
const [state, dispatch] = useReducer(servicesReducer, {
financialPlanning: {
description: "",
minHours: null,
maxHours: null,
minPrice: null,
maxPrice: null,
},
selectedOptions: [],
});
const check = state.selectedOptions.includes("financialPlanning");
console.log("check", check);
console.log("check2", state);
return (
<div className={classes.container}>
<ToggleBox
selected={check}
onClick={() => {
console.log("click");
dispatch({
type: "toggle option",
payload: { name: 'financialPlanning' },
});
}}
title="Financiële planning"
>
Hey
</ToggleBox>
</div>
);
};
child component: ToggleBox.tsx
import React from 'react';
import { Box, Card, CardContent, Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked';
import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline';
import { responsivePadding } from 'helpers';
export interface ToggleBoxProps {
title: string;
description?: string;
rightIcon?: React.ReactElement;
selected: boolean;
focused?: boolean;
children?: React.ReactNode;
onClick?: () => void;
}
const useStyles = makeStyles(theme => ({
root: ({ selected, focused }: ToggleBoxProps) => {
let borderColor = theme.palette.grey[300];
if (focused) {
borderColor = theme.palette.primary.main;
} else if (selected) {
// eslint-disable-next-line prefer-destructuring
borderColor = theme.palette.grey[500];
}
return {
border: `1px solid ${borderColor}`,
height: '100%',
};
},
content: {
height: '90%',
display: 'flex',
flexDirection: 'column',
},
header: {
display: 'flex',
cursor: 'pointer',
flexDirection: 'row',
justifyContent: 'space-between',
paddingBottom: theme.spacing(2),
marginBottom: theme.spacing(2),
borderBottom: `1px solid ${theme.palette.divider}`,
color: theme.palette.text.secondary,
},
title: {
flex: 1,
marginLeft: theme.spacing(2),
},
}));
export const ToggleBox: React.FC<ToggleBoxProps> = (
props: ToggleBoxProps,
) => {
console.log('props toggleBox', props);
const { title, description, rightIcon, selected, children, onClick } = props;
console.log('selected check prop Togglebox', selected);
const classes = useStyles(props);
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<Box className={classes.header} onClick={onClick}>
{selected ? <CheckCircleOutlineIcon /> : <RadioButtonUncheckedIcon />}
<Typography className={classes.title} color='textSecondary'>
{title}
</Typography>
{rightIcon}
</Box>
<Typography variant='body2' color='textSecondary'>
{description}
</Typography>
{selected && children}
</CardContent>
</Card>
);
};
CodePudding user response:
You seem to be mutating your state when adding a value to the selectedOptions
array. .push
mutates an existing array in-place.
case "toggle option":
const isCurrentlySelected = state.selectedOptions.includes(
action.payload.name
);
let newSelectedOptions = state.selectedOptions; // <-- saved reference to state
if (isCurrentlySelected) {
newSelectedOptions = newSelectedOptions.filter(
(item) => item !== action.payload.name
);
} else {
newSelectedOptions.push(action.payload.name); // <-- mutation!
}
return {
...state,
selectedOptions: newSelectedOptions,
};
In either case of adding or removing you necessarily need to return a new array reference. You can use Array.prototype.concat
to add a value to an array and return a new array reference.
case "toggle option":
const isCurrentlySelected = state.selectedOptions.includes(
action.payload.name
);
let newSelectedOptions = state.selectedOptions;
if (isCurrentlySelected) {
newSelectedOptions = newSelectedOptions.filter(
(item) => item !== action.payload.name
);
} else {
newSelectedOptions.concat(action.payload.name); // <-- add to and return new array
}
return {
...state,
selectedOptions: newSelectedOptions,
};