javascriptreactjstypescriptchakra-ui

onChange never gets triggered in input component that is not supposed to be uncontrolled


Can anyone point help me to find out why onChange is never being executed?

Basically I'm extending ChakraUI's Input component to load a value from localStorage if such value exists.

The component is correctly loading the localStorage value, but if I try to set the value property from the value state, the component becomes read-only, which means it's an uncontrolled component & etc, but defaultValue is supposed to circumvent this. What is the matter here?

I even tried a hack using an useEffect hook to read localStorage and then directly update the input using classic JavaScript DOM manipulation. It correctly updates the value but still doesn't trigger onChange.

I also tried using onBlur instead to avoid any potential problems from re-renders, which wasn't being triggered as well.

What am I doing wrong?

import { ChangeEvent, useState, forwardRef } from 'react';
import { Input, InputProps } from '@chakra-ui/react';

interface StoredInputProps {
  name: string;
  defaultValue?: string;
  placeholder?: string ;
  autoCapitalize?: string;
}

type CombinedInputProps = StoredInputProps & InputProps;

const StoredInput= forwardRef<HTMLInputElement, CombinedInputProps>(
  ({ name, defaultValue = '', placeholder = '', autoCapitalize = 'none', ...props }, ref) => {
  const [value, setValue] = useState<string>(() => {
    const storedValue = localStorage.getItem(name);
    return storedValue !== null ? storedValue : defaultValue;
  });

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    console.log("Handle Change")
    const newValue = event.target.value;
    // Immediately update localStorage when the value changes
    localStorage.setItem(name, newValue);
  };

  return (
    <>
      <Input
        id={name}
        name={name}
        defaultValue={value}
        onChange={(event) => handleChange(event)}
        placeholder={placeholder}
        autoCapitalize={autoCapitalize}
        {...props}
      />
    </>
  );
});

export default StoredInput;

Solution

  • The {...props} destructuring at the end of the component was overwriting my own value for onChange. Moving it to the top declaration solved the issue.