Home > Back-end >  React props updating with useState?
React props updating with useState?

Time:09-07

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?

VIDEO: output screenshot

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 }))
);
  • Related