SortableJS / react-sortablejs

React bindings for SortableJS
http://sortablejs.github.io/react-sortablejs/
MIT License
2.07k stars 211 forks source link

setList fires when drag starts and when drag ends #160

Open beauchette opened 4 years ago

beauchette commented 4 years ago

Describe the bug setList is supposed to help set the state. It is fired when drag ends, with the new state. Unfortunately it is also called when the Drag starts with the actual state, so basically, there is a setState for nothing at drag start.

To Reproduce

  1. create a new react app.
  2. add react-sortablejs
  3. use the first example (functional component in my case)
  4. create a setStateWrapper that just executes setState and add a console.log with the setState parameter
  5. notice in the console, how sometimes, setStateWrapper is called sometimes 3 times

Expected behavior setList being there to help state the state, should only fire once.

Information "react": "^16.9.0", "react-sortablejs": "^2.0.11",

though, when I created a new react app to test, it was: "react": "^16.13.1", "react-sortablejs": "^2.0.11",

corujoraphael commented 4 years ago

Same here

beauchette commented 4 years ago

Oh that's a problem for me, because I update a db via https each time, so, even I filter out when it's the same content, there's always gonna be the problem of making 2 requests instead of one, the first time.

So I ended up using onEnd but it's dirty, well, not the React way. I had to use custom attribute to set the id (as a dbid attribute) on the sortable elements, and then read them in the onEnd function

Camsteack commented 4 years ago

Any update on this ? It actually fires when the drag starts, but also twice when the drag ends on the source list with only the last call being genuine. It make it quite painful to work with.

Thanks for the library

beauchette commented 4 years ago

@Camsteack as I said in an earlier comment, setting state that often is not really a problem. What is a problem is to update your database through an http(s) request, so basically, I used the onEnd event, I had to add attributes to be able to get the database id from them, that's a little dirty but it worked

doug-stewart commented 4 years ago

I ran across the same issue and I got around it by using state to track whether or not I was actively in the process of sorting. Here's a rough example:

import { ReactSortable } from 'react-sortablejs';

const Sorter = () => {
  const [isSorting, setIsSorting] = useState(false);
  const [items, setItems] = useState(['A', 'B', 'C', 'D', 'E', 'F']);

  const updateOrder = (updatedList) => {
    if (!isSorting) return;
    setIsSorting(false);
    setItems(updatedList);
  };

  return (
    <ReactSortable
      list={items}
      onStart={() => setIsSorting(true)}
      setList={(updatedList) => updateOrder(updatedList)}>
      {items.map((item) => <div>{item}</div>)}
    </ReactSortable>
  );
};

export default Sorter;
anynines-johannchopin commented 4 years ago

Same here

lucaslz2020 commented 4 years ago

Same here

aquaductape commented 4 years ago

I'm not sure why but @doug-stewart solution doesn't work for me. When isSorting is true, updateOrder fires with old updatedList, so by the time updateOrder fires with the correct sorted updatedList, isSorting is false and exits before updating the itemsState.

Instead I allow the updateOrder after onUpdate is fired, and I use a ref for isSorting, since this state is not meant to rerender the component but to know whether Sortable is sorting or not.

import { ReactSortable } from 'react-sortablejs';

const Sorter = () => {
  const isSortingRef = useRef(false);
  const [items, setItems] = useState(['A', 'B', 'C', 'D', 'E', 'F']);

  const updateOrder = (updatedList) => {
    if (!isSortingRef.current) return;
    isSortingRef.current = false
    setItems(updatedList);
  };

  return (
    <ReactSortable
      list={items}
      onUpdate={() => isSortingRef.current = true}
      setList={(updatedList) => updateOrder(updatedList)}>
      {items.map((item) => <div>{item}</div>)}
    </ReactSortable>
  );
};

export default Sorter;
riazosama commented 3 years ago

@aquaductape Hi, I tried your way but for me onUpdate is not working at all. Any hints on how to make it work?

samieinejad commented 3 years ago

before set state check equality of new and old state by lodash. this is example code for class component. in my case items has 2 fields: id & name.

updateOrder = (newState) => {
    const prevState = this.state.list;
    newState = newState.map(item => _.pick(item, ['id', 'name']));

    if (_.isEqual(prevState, newState)) return;

    this.setState({
        list: newState,
    })
 }
vaynevayne commented 1 year ago

This is the easiest drag and drop library to use in the react ecosystem, but instead of onEnd, it uses buggy setList, which is really annoying

makis-x commented 1 year ago

Im just shocked such a great library still has this most fundamental of issues :(

tjthouhid commented 12 months ago

Working Solution :

import React, { FC, useState } from "react";
import { ReactSortable } from "react-sortablejs";

interface ItemType {
  id: number;
  name: string;
}

export const BasicFunction: FC = (props) => {
  const [state, setState] = useState<ItemType[]>([
    { id: 1, name: "shrek" },
    { id: 2, name: "fiona" },
  ]);

  // Use this custom function so that only update when changes on drag and drop
  function updateState(newState) {
   if (JSON.stringify(newState) !== JSON.stringify(state)) {
      setState(newState);
    }
  }

  return (
    <ReactSortable list={state} setList={updateState}>
      {state.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </ReactSortable>
  );
};
makis-x commented 6 months ago

Working Solution :

import React, { FC, useState } from "react";
import { ReactSortable } from "react-sortablejs";

interface ItemType {
  id: number;
  name: string;
}

export const BasicFunction: FC = (props) => {
  const [state, setState] = useState<ItemType[]>([
    { id: 1, name: "shrek" },
    { id: 2, name: "fiona" },
  ]);

  // Use this custom function so that only update when changes on drag and drop
  function updateState(newState) {
   if (JSON.stringify(newState) !== JSON.stringify(state)) {
      setState(newState);
    }
  }

  return (
    <ReactSortable list={state} setList={updateState}>
      {state.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </ReactSortable>
  );
};

There is a bizarre attribute that you have to map out called chosen in newState. This setList is really perplexing how not good it is.