Home > Blockchain >  event.target.value omitting last character
event.target.value omitting last character

Time:09-30

i've been solving this problem without any progress for the pas 2 hours or so, here is code:

export const useFetchAll = () => {
  const [searchResult, setSearchResult] = useState([]);
  const [loading, setLoading] = useState(false);
  const [searchItem, setSearchItem] = useState("");
  const [listToDisplay, setListToDisplay] = useState([]);

  // const debouncedSearch = useDebounce(searchItem, 300);

  const handleChange = (e) => {
    setSearchItem(e.target.value);

    if (searchItem === "") {
      setListToDisplay([]);
    } else {
      setListToDisplay(
        searchResult.filter((item) => {
          return item.name.toLowerCase().includes(searchItem.toLowerCase());
        })
      );
    }
    console.log(searchItem);
  };

  useEffect(() => {
    const searchRepo = async () => {
      setLoading(true);

      const { data } = await axios.get("https://api.github.com/repositories");
      setSearchResult(data);

      setLoading(false);
    };

    if (searchItem) searchRepo();
  }, [searchItem]);

the problem is that when i enter characters in input and set state to event.target.value it doesn't pick up last character. here is an image: enter image description here

BTW this is a custom hook, i return the onchange function here:


const HomePage = () => {
  const { searchResult, loading, searchItem, handleChange, listToDisplay } =
    useFetchAll();

and then pass it as a prop to a component like so:

        <Stack spacing={2}>
          <Search searchItem={searchItem} handleChange={handleChange} />
        </Stack>
      </Container>

any help? thanks in advance.

CodePudding user response:

You are handling the searchItem and searchResult state variables as if their state change was synchronous (via setSearchItem and setSearchResult) but it isn't! React state setters are asynchronous.

The useEffect callback has a dependency on the searchItem state variable. Now every time the user types something, the state will change, that change will trigger a re-rendering of the Component and after that render finishes, the side-effect (the useEffect callback) will be executed due to the Components' lifecycle.

In our case, we don't want to initiate the fetch request on the next render, but right at the moment that the user enters something on the search input field, that is when the handleChange gets triggered.

In order to make the code work as expected, we need some a more structural refactoring.

You can get rid of the useEffect and handle the flow through the handleChange method:

export const useFetchAll = () => {

  const [ loading, setLoading ] = useState( false );
  const [ searchItem, setSearchItem ] = useState( "" );
  const [ listToDisplay, setListToDisplay ] = useState( [] );

  const handleChange = async ( e ) => {

    const { value } = e.target;
    // Return early if the input is an empty string:
    setSearchItem( value );
    if ( value === "" ) {
      return setListToDisplay( [] );
    }
    setLoading( true );
    const { data } = await axios.get( "https://api.github.com/repositories" );
    setLoading( false );
    const valueLowercase = value.toLowerCase(); // Tiny optimization so that we don't run the toLowerCase operation on each iteration of the filter process below
    setListToDisplay(
        data.filter(({ name }) => name.toLowerCase().includes(valueLowercase))
    );
  };

  return {
      searchItem,
      handleChange,
      loading,
      listToDisplay,
  };
};

CodePudding user response:

function used for updating state value is asynchronous that why your state variable is showing previous value and not the updated value. I have made some change you can try running the below code .

    const [searchResult, setSearchResult] = useState([]);
    const [loading, setLoading] = useState(false);
    const [searchItem, setSearchItem] = useState("");
    const [listToDisplay, setListToDisplay] = useState([]);

    // const debouncedSearch = useDebounce(searchItem, 300);

    const handleChange = (e) => {
      setSearchItem(e.target.value); // this  sets value asyncronously   
      console.log("e.target.value  :"    e.target.value); // event.target.value does not    omitting last character
      console.log("searchItem  :"   searchItem);    // if we check the value then it is not set. it will update asyncronously  
    
    };

    const setList = async () => {
      if (searchItem === "") {
        setListToDisplay([]);
      } else {
        setListToDisplay(
          searchResult.filter((item) => {
            return item.name.toLowerCase().includes(searchItem.toLowerCase());
          })
        );
      }
    };


    const searchRepo = async () => { 
      const { data } = await axios.get("https://api.github.com/repositories");
      setSearchResult(data);
      setLoading(false);
    };

    // this useeffect execute its call back when searchItem  changes a
    useEffect(() => {
      setList();  // called here to use previous value  stored in 'searchResult'   and display something ( uncomment it if you want to display only updated value )
      if (searchItem) searchRepo();
    }, [searchItem]);


    // this useeffect execute when axios set fetched data in 'searchResult' 
    useEffect(() => {
      setList();   
    }, [searchResult]);


    // this useeffect execute when   data is updated  in 'listToDisplay'
    useEffect(() => {
      console.log("filtered Data") // final  'listToDisplay'  will be availble here
      console.log(listToDisplay)
    }, [listToDisplay]);
  • Related