Search code examples
javascriptreactjseventsreact-state

get a value of useState inside of listener


I want to display a list of coordinates that I got from click event on DIV. I want the listener works only when a button was clicked.

import { createRoot } from "react-dom/client";
import React, {useCallback, useEffect, useState} from 'react'

const AppRestoration = () => {
  const [active, setActive] = useState(false);
  const [coordinates, setCoordinates] = useState([]);

  const handleClick = useCallback((event) => {
    console.log(coordinates) // is empty array
    setCoordinates((prevCoordinates) => [...prevCoordinates, event.clientX]
    );
  }, [setCoordinates]);

  useEffect(() => {
    console.log("Coordinates:", ...coordinates); // When they update
  }, [coordinates]);

  return (
    <div>
      <div
        style={{ width: "200px", height: "200px", backgroundColor: "red" }}
        onClick={handleClick}
      >
        ici
      </div>
      <button onClick={() => setActive((prev) => !prev)}>
        {active ? "clicked" : "not"}
      </button>
      <SecondComponent secondCParam={coordinates} />
    </div>
  );
};

const SecondComponent = ({ secondCParam }) => {
  return (
    <ul>
      {secondCParam.map((coord) => (
        <li key={crypto.randomUUID()}>{coord}</li>
      ))}
    </ul>
  );
};

createRoot(document.getElementById("root")).render(<AppRestoration />)

enter image description here

The problem is : in handleClick(event) I can not filter "coordinates", it is always an empty array. To refresh it, I have to click on the button, so handleClick one more time. How to avoid it?


Solution

  • Why not add the click handler to the element, and do it the React way?

    <div
      style={{ width: "200px", height: "200px", backgroundColor: "red" }}
      onClick={handleClick}
    >
    
    const handleClick = useCallback(({ clientX }) => {
      setCoordinates((prevCoordinates) =>
        !prevCoordinates.includes(clientX)
          ? [...prevCoordinates, clientX]
          : prevCoordinates
      );
    }, [setCoordinates]);
    

    Also, a state called state is ambiguous. You should rename it to a more appropriate name. For the time being, I named it active, since the button toggles.

    const { useCallback, useEffect, useState } = React;
    
    const AppRestoration = () => {
      const [active, setActive] = useState(false);
      const [coordinates, setCoordinates] = useState([]);
    
      const handleClick = useCallback((event) => {
        setCoordinates((prevCoordinates) =>
          !prevCoordinates.includes(event.clientX)
            ? [...prevCoordinates, event.clientX]
            : prevCoordinates
        );
      }, [setCoordinates]);
    
      useEffect(() => {
        console.log("Coordinates:", ...coordinates); // When they update
      }, [coordinates]);
    
      return (
        <div>
          <div
            style={{ width: "200px", height: "200px", backgroundColor: "red" }}
            onClick={handleClick}
          >
            ici
          </div>
          <button onClick={() => setActive((prev) => !prev)}>
            {active ? "clicked" : "not"}
          </button>
          <SecondComponent secondCParam={coordinates} />
        </div>
      );
    };
    
    const SecondComponent = ({ secondCParam }) => {
      return (
        <ul>
          {secondCParam.map((coord) => (
            <li key={crypto.randomUUID()}>{coord}</li>
          ))}
        </ul>
      );
    };
    
    ReactDOM.createRoot(document.getElementById("root")).render(<AppRestoration />);
    .as-console-wrapper { max-height: 4rem !important; }
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>