I have the component below where I'm trying the build functionality to allow a user to update opening times of a store.
I'm passing the original opening times as a prop and creating some state using the props opening times for initial state. I want to use the new state to submit changes but if the user selects cancel the UI updates to reflect the original times.
I have most of the functionality working but for some reason my handler to update the state with the input change also seems to update the props value so it won't go back to the original value.
How can I stop the props updating and ensure only the allOpeningHours
state is changed?
const EditStudioHours: FC<{ studio: Studio }> = ({ studio }) => {
const { value: edit, toggle: toggleEdit } = useBoolean(false)
const { value: submitting, toggle: toggleSubmitting } = useBoolean(false)
const [allOpeningHours, setAllOpeningHours] = useState([
...studio.openingHours.regularDays,
])
return (
<Box>
<Typography variant='h6' mt={2} gutterBottom>
Set standard hours
</Typography>
<Typography fontWeight='light' fontSize={14}>
Configure the standard operating hours of this studio
</Typography>
<Stack mt={3} spacing={2}>
{studio.openingHours.regularDays.map((hours, i) => (
<DayOfWeek
dow={daysOfWeek[i]}
openingHours={hours}
edit={edit}
i={i}
setAllOpeningHours={setAllOpeningHours}
allOpeningHours={allOpeningHours}
/>
))}
</Stack>
<Button
variant={edit ? 'contained' : 'outlined'}
onClick={() => {
toggleEdit()
}}
fullWidth
sx={{ mt: 2 }}
disabled={!edit ? false : submitting}
>
{submitting ? (
<CircularProgress size={22} />
) : edit ? (
'Submit changes'
) : (
'Edit'
)}
</Button>
{edit && (
<Button
onClick={toggleEdit}
variant={'outlined'}
sx={{ mt: 1 }}
fullWidth
>
Cancel
</Button>
)}
</Box>
)
}
export default EditStudioHours
const DayOfWeek: FC<{
openingHours: { start: number; end: number }
dow: string
edit: boolean
i: number
setAllOpeningHours: any
allOpeningHours: any
}> = ({ openingHours, dow, edit, i, setAllOpeningHours, allOpeningHours }) => {
const [open, setOpen] = useState(openingHours.end !== openingHours.start)
const handleOpenClose = () => {
open &&
setAllOpeningHours((ps: any) => {
const newHours = [...ps]
newHours[i].start = 0
newHours[i].end = 0
return newHours
})
setOpen((ps) => !ps)
}
const handleStart = (e: any) => {
setAllOpeningHours((prevState: any) => {
const newHours = [...prevState]
newHours[i].start = e.target.value
return newHours
})
}
const handleEnd = (e: any) => {
setAllOpeningHours((ps: any) => {
const newHours = [...ps]
newHours[i].end = e.target.value
return newHours
})
}
return (
<Box display='flex' alignItems='center' justifyContent={'space-between'}>
<Box display={'flex'} alignItems='center'>
<Typography width={150}>{dow}</Typography>
<FormGroup>
<FormControlLabel
control={
<Switch
disabled={!edit}
checked={open}
onChange={handleOpenClose}
/>
}
label='Open'
/>
</FormGroup>
</Box>
{open && (
<Box display={'flex'} alignItems='center'>
<TextField
disabled={!edit}
id={`${i}open`}
select
label='Open'
value={edit ? allOpeningHours[i].start : openingHours.start}
type='number'
sx={{ minWidth: 120 }}
size='small'
onChange={handleStart}
>
{openingOptions.map((option: { value: number; label: string }) => (
<MenuItem dense key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<Typography mx={2}>TO</Typography>
<TextField
disabled={!edit}
id={`${i}close`}
select
label='Close'
value={edit ? allOpeningHours[i].end : openingHours.end}
type='number'
sx={{ minWidth: 120 }}
size='small'
onChange={handleEnd}
>
{openingOptions.map((option: { value: number; label: string }) => (
<MenuItem dense key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Box>
)}
</Box>
)
}
CodePudding user response:
The problem is that objects within the studio.openingHours.regularDays
array still share the same reference, even though you copied the array itself.
When you use something like
newHours[i].start = e.target.value
You're still updating the original objects from props.
You can use Array.prototype.splice() to remove the object at index i
and replace it with a new one
const day = newHours[i];
newHours.splice(i, 1, {
...day,
start: e.target.value,
});
Do this in each of your 3 handle functions.
Alternately, break all references when creating local state from props
const [allOpeningHours, setAllOpeningHours] = useState(
studio.openingHours.regularDays.map((day) => ({ ...day }))
);