Unable to update useRef value in React hooks

Viewed 162

What I am currently developing

I am using React hook to develop the function to reposition the DOM by drag and drop.

The code up to the halfway point can be found at this link.

https://codesandbox.io/s/exciting-williams-z4ngdz?file=/src/App.js

The code in question

I want to update the value of useRef() in React onMouseDown event, but I cannot update it and it remains at the initial value.

import axios from 'axios';
import React, { useEffect, useState, useRef } from 'react';
import './App.css';


const baseURL = new URL('https://api.ks-portfolio.site/skills');

/**
 * @return {Promise<{Object}>}
 */
const apiGet = async () => await axios.get(baseURL);

/**
 * @param {MouseEvent} event
 * @param {HTMLElement} element
 * @return {boolean}
 */
const isHover = (event, element) => {

  const clientX = event.clientX;
  const clientY = event.clientY;

  const rect = element.getBoundingClientRect();

  return clientY < rect.bottom && clientY > rect.top && clientX < rect.right && clientX > rect.left;
};

function App() {
  const [blocks, setBlocks] = useState([]);

  // Manage status with ref.
  const state = useRef({
    dndItems: [],
    keys: new Map(),
    dragElement: null,
    canCheckHovered: true,
    pointerPosition: { x: 0, y: 0 },
    sortIds: [],
  }).current;


  const sortBlocks = (sortItems) => {
    const canCheckTime = 300;

    /**
     * @param {MouseEvent} event
     * @returns
     */
    const onMouseMove = (event) => {
      const { clientX, clientY } = event;
      const { dndItems, dragElement, pointerPosition } = state;

      if (!dragElement) return;

      const x = clientX - pointerPosition.x;
      const y = clientY - pointerPosition.y;

      const dragStyle = dragElement.element.style;
      dragStyle.zIndex = '100';
      dragStyle.cursor = 'grabbing';
      dragStyle.transform = `translate(${x}px,${y}px)`;

      if (!state.canCheckHovered) return;

      state.canCheckHovered = false;

      setTimeout(() => state.canCheckHovered = true, canCheckTime);

      const dragIndex = dndItems.findIndex(({ key }) => key === dragElement.key);

      const hoveredIndex = dndItems.findIndex(({ element }, index) => index !== dragIndex && isHover(event, element));

      if (hoveredIndex !== -1) {
        state.pointerPosition.x = clientX;
        state.pointerPosition.y = clientY;

        dndItems.splice(dragIndex, 1);
        dndItems.splice(hoveredIndex, 0, dragElement);

        const { left: x, top: y } = dragElement.element.getBoundingClientRect();

        dragElement.position = { x, y };

        setBlocks(dndItems.map((item) => item.value));
      }
    };

    /**
     * @return {void}
     */
    const onMouseUp = () => {
      const { dragElement } = state;

      if (!dragElement) return;

      const dragStyle = dragElement.element.style;

      dragStyle.zIndex = '';
      dragStyle.cursor = '';
      dragStyle.transform = '';

      state.dragElement = null;

      window.removeEventListener('mouseup', onMouseUp);
      window.removeEventListener('mousemove', onMouseMove);
    };

    return sortItems.map((value) => {

      const key = state.keys.get(`item_${value.language_id}`) || Math.random().toString(16);

      state.keys.set(`item_${value.language_id}`, key);

      if (!state.sortIds.includes(value.language_id)) {
        state.sortIds.push(value.language_id);
      }

      return {
        value,

        key,

        events: {
          ref: (element) => {
            const { dndItems, dragElement, pointerPosition, sortIds } = state;

            if (!element || !dragElement) return;

            const block = element.closest('.block');

            block.style.transform = '';

            const { left: x, top: y } = block.getBoundingClientRect();
            const position = { x, y };

            const itemIndex = dndItems.findIndex((item) => item.key === key);

            if (itemIndex === -1 && sortIds.length > dndItems.length) {
              return dndItems.push({ key, value, element: block, position });
            }

            if (dragElement.key === key) {
              const dragX = dragElement.position.x - position.x;
              const dragY = dragElement.position.y - position.y;

              block.style.transform = `translate(${dragX}px,${dragY}px)`;

              pointerPosition.x -= dragX;
              pointerPosition.y -= dragY;
            }

            if (dragElement.key !== key) {
              const item = dndItems[itemIndex];

              const x = item.position.x - position.x;
              const y = item.position.y - position.y;

              block.style.transition = '';
              block.style.transform = `translate(${x}px,${y}px)`;

              requestAnimationFrame(() => {
                block.style.transform = '';
                block.style.transition = `all ${canCheckTime}ms`;
              });
            }

            state.dndItems[itemIndex] = { key, value, element: block, position };
          },

          /**
           * @param {React.MouseEvent<HTMLElement>} event
           * @return {void}
           */
          onMouseDown: (event) => {

            const element = event.currentTarget.closest('.block');

            state.pointerPosition.x = event.clientX;
            state.pointerPosition.y = event.clientY;

            element.style.transition = '';
            element.style.cursor = 'grabbing';

            const { left: x, top: y } = element.getBoundingClientRect();
            const position = { x, y };

            state.dragElement =  { key, value, element, position };

            console.log('No value is stored in dragElement.')
            console.log('onMouseDown', {key, value, element, position});
            console.log('state', state);

            window.addEventListener('mouseup', onMouseUp);
            window.addEventListener('mousemove', onMouseMove);
          },
        },
      };
    });
  };

  useEffect(() => {
    async function fetchData() {
      const response = await apiGet();
      setBlocks(response.data[0].programming);
    }
    fetchData();
  }, []);

  return (
    <ul className="App">
      {sortBlocks(blocks).map((block,i) => {

      const { language } = block.value;

      return (
      <li key={i} className="block">
          <div className="panel-body" {...block.events}>
            {language}
          </div>
        </li>
      )
      })}
    </ul>
  );
}

export default App;

Corresponding code

The dragElement on line 158 is null.

return sortItems.map((value) => {
           events: {
          ref: (element) => {
            const { dndItems, dragElement, pointerPosition, sortIds } = state;

            console.log("events", dragElement); // dragElement = null
}

}

Please let me know what the problem is and how to deal with it.

0 Answers
Related