react-native-draggable-flatlist icon indicating copy to clipboard operation
react-native-draggable-flatlist copied to clipboard

List blinking for fraction of second when rearranging the items.

Open rifad4u opened this issue 2 years ago • 29 comments

List blinking for fraction of second when rearranging the items. please find the attached video . this issue is not happening in version3.1.2 and happening in version 4.0.0.

https://user-images.githubusercontent.com/37132763/206464911-c575affb-8acb-408b-9b64-cfa960472a94.MOV

rifad4u avatar Dec 08 '22 13:12 rifad4u

any update on this? @@

LuanNguyen-HnL avatar Dec 13 '22 05:12 LuanNguyen-HnL

The same issue...

IlliaSedzin avatar Dec 15 '22 16:12 IlliaSedzin

Can you reproduce this in a shareable repo? I had the issue and noticed that I was passing a custom useState prop that was changing some style and thus causing a second rerender after the item was dropped.

Tried to create a reproducible sample, but couldn't get it to work in a smaller environment

Svarto avatar Dec 15 '22 20:12 Svarto

~I'm seeing this as well. @Svarto This is also noticeable in the sample iOS snack shown in the README.md of this project.~

EDIT: Running the Expo example on my phone showed it does not flicker. I believe this is an issue on my end.

dchhetri avatar Dec 27 '22 22:12 dchhetri

For me, I found that if I was firing a server request within onDragEnd it would cause this flash.

I solved the issue by putting that request inside of a setTimeout.

onDragEnd={({ data }) => {
    // Update client state

    setTimeout(() => {
        // Update server state
    }, 0);
}}

Kieraano avatar Dec 30 '22 21:12 Kieraano

replace keyExtractor={(item, index) => index.toString()} to keyExtractor={(item, index) => item.xxxKey}

MonkZL avatar Jan 30 '23 02:01 MonkZL

replace keyExtractor={(item, index) => index.toString()} to keyExtractor={(item, index) => item.xxxKey}

^ Not using the index as the keyExtractor fixed this on my end. Which makes sense since you are changing the index of the data on drag end and it needs to rerender and the dragged item will have a different index before its position is changed in the array of items. Use something instead that doesnt change regardless of how your items are rendered.

drewbietron avatar Feb 15 '23 14:02 drewbietron

For me, I found that if I was firing a server request within onDragEnd it would cause this flash.

I solved the issue by putting that request inside of a setTimeout.

onDragEnd={({ data }) => {
    // Update client state

    setTimeout(() => {
        // Update server state
    }, 0);
}}

This was my issue as well. It was resolved by putting a timeout around the server update (network call). Thank you 👏

patricksimpson avatar Mar 04 '23 15:03 patricksimpson

When update react-native-reanimated to 3.0.1 It seems to have solved this problem

dppo avatar Mar 05 '23 14:03 dppo

I'm using RealmsDB and it's happening to me. My data is get like this:

const banks = useQuery(AppBank).sorted('order');
const bankAccounts = Array.from(banks);

This are my functions:

const handleDragEnd = useCallback((data: AppBank[]) => {
    realm.write(() => {
      data.forEach((item, index) => {
        const realmItem = realm.objectForPrimaryKey(AppBank, item._id);
        if (realmItem) {
          realmItem.order = index;
        } else {
          throw "Item shouldn't be null";
        }
      });
    });
  }, []);

  const handleRemoveItem = useCallback((appBank: AppBank) => {
    realm.write(() => {
      const itemToDelete = realm.objectForPrimaryKey(AppBank, appBank._id);
      if (itemToDelete) {
        realm
          .objects(AppBank)
          .filtered(`order > ${itemToDelete.order}`)
          .forEach(item => {
            item.order -= 1;
          });
        realm.delete(itemToDelete);
      }
    });
  }, []);

Callback or not, the list will flicker for a second if dragging.

A strange thing happens also when I delete an Item. Every item in Realm DB has a unique key, but for some reason the list will render "old" objects and then the new ones. So one of the items got deleted, hence not valid, but my keyExtractor function still sees it and will throw an exception. This is my workaround (but still the flicker persist):

      <DraggableFlatList
        data={bankAccounts}
        keyExtractor={item => (item.isValid() ? item._id.toHexString() : 'DELETE')}
        onDragEnd={({ data }) => handleDragEnd(data)}
        // Passing also ITEM with handleRemoveItem...
       ...

I have only one re-render happening in my screen when updating. I made a console.log and it happens only once for every delete of the item. I'm using Reanimated 3.0.1. I tired the "setTimeout" thing but it's not working.

A TOTAL workaround that I'm using right now is like this. It's working and not causing the flicker. But it's not how I wanted to manage my data.

const realm = useRealm();
const [bankAccounts, setBankAccounts] = useState<AppBank[]>([]);

  useEffect(() => {
    setBankAccounts(Array.from(realm.objects(AppBank).sorted('order')));
  }, []);
  
  const handleDragEnd = useCallback((data: AppBank[]) => {
    realm.write(() => {
      data.forEach((item, index) => {
        const realmItem = realm.objectForPrimaryKey(AppBank, item._id);
        if (realmItem) {
          realmItem.order = index;
        } else {
          throw "Item shouldn't be null";
        }
      });
    });
    // FORCING UPDATE ONLY AFTER ALL (same for the other function)
    setBankAccounts(Array.from(realm.objects(AppBank).sorted('order')));
  }, []);
  
  ...
  
    <DraggableFlatList
        data={bankAccounts}
        // Key problem still persist and filtering the array is not working :(
        keyExtractor={item => (item.isValid() ? item._id.toHexString() : 'DELETE')}
        onDragEnd={({ data }) => handleDragEnd(data)}
        // Passing also ITEM with handleRemoveItem...

Martinocom avatar Mar 11 '23 18:03 Martinocom

@rifad4u this still occurs, why did you close this issue?

Svarto avatar Mar 13 '23 05:03 Svarto

Hi I am also facing same issue i am not making any network call I am simply updating setting state (data) .

saurabhg138 avatar Mar 29 '23 12:03 saurabhg138

Hi I am also facing same issue i am not making any network call I am simply updating setting state (data) .

Try this keyExtractor={(item, index) => JSON.stringify(item)}

rifad4u avatar Mar 29 '23 12:03 rifad4u

I have item.id as unique key I am using that as key extractor. @rifad4u Can you please explain me what JSON.stringify(item) will do different?

saurabhg138 avatar Mar 29 '23 12:03 saurabhg138

I have item.id as unique key I am using that as key extractor. @rifad4u Can you please explain me what JSON.stringify(item) will do different?

Can u show your keyExtractor line

rifad4u avatar Mar 29 '23 12:03 rifad4u

@rifad4u keyExtractor={(item) => item.meterId} Where meterId is unique for all items. This is of type string also.

saurabhg138 avatar Mar 29 '23 12:03 saurabhg138

Fixed this on my local project today. It can happen for multiple reasons:

  • Using index as key
    • Personally, I never encountered this issue despite using the index as the key, however it was reported by others in the thread so no harm in refactoring if possible.
    • Just change it to the item.id or some other value from the object that is not dependent on the index
  • Performing too many actions in the onDragEnd
    • This turned out to be my issue.
    • You basically just need to refactor your onDragEnd from something like this:
      onDragEnd={({data}) => {
          setList(data);
          // a bunch of other actions
      }}
      
      to something like this:
      import { InteractionManager } from 'react-native';
      ....
      onDragEnd={({data}) => {
          setList(data);
      
          InteractionManager.runAfterInteractions(() => {
              // a bunch of other actions
          });
      }}
      
      InteractionManager waits until all animations are complete before running basically

KrisLau avatar Mar 30 '23 20:03 KrisLau

Hello, I have the same issue happening, but only in a web browser on desktop. On mobile it works fine. I am not performing any other action in the onDragEnd just updating my state and the keyExtractor has a unique id for each item in my list.

Any suggestions how to fix this?

Sbstnklt avatar Dec 05 '23 09:12 Sbstnklt

@Sbstnklt you would need to provide more information like code. A bit hard to help without context

KrisLau avatar Dec 07 '23 05:12 KrisLau

I have nearly the exact same implementation as described in https://github.com/computerjazz/react-native-draggable-flatlist/issues/434#issuecomment-1464981428. The only difference is that my array is an embedded realm array/list. I've tried every solution offering in this thread and nothing works. I suspect the problem is that inside onDragEnd() I'm updating realm via realm.write() by setting the 'order' property of each item in a loop (which takes time). In the mean time the component is rendering on the drop using the old (pre-drag) state.

const reorderActions = (data: Realm.List<ChecklistAction> | Omit<ChecklistAction, keyof Realm.Object>[]) => {
  if (checklistTemplate) {
    realm.write(() => {
      for (let i = 0; i < data.length; i++) {
        data[i].ordinal = i;
      };
    });
  }
};

Having long running sync work inside onDragEnd() is really the problem - but not sure anything can really be done about it. The UI can't wait of the UX will be awful (list item freezes before it drops). The realm writes clearly take too long.

It seems the only solution is to copy the list item data out of the realm object and use a simple (low overhead) setState() and then later update realm by copying from state into a realm.write(). Can it get any uglier?

ajp8164 avatar Jan 12 '24 18:01 ajp8164

@ajp8164

Having long running sync work inside onDragEnd() is really the problem - but not sure anything can really be done about it. The UI can't wait of the UX will be awful (list item freezes before it drops). The realm writes clearly take too long.

https://github.com/computerjazz/react-native-draggable-flatlist/issues/434#issuecomment-1490929663 Use InteractionManager.runAfterInteractions

KrisLau avatar Jan 14 '24 10:01 KrisLau

Thanks for the response - I tried this but I had the drag list items bound to the realm array directly so using InteractionManager doesn't fix it. I had to move the items to a state variable and put the realm write in a useEffect.

ajp8164 avatar Jan 14 '24 13:01 ajp8164

I have the same issue when setting data with TanstackQuery. If I use a simple useState then I am good but then I don't get the benefit of using TanstackQuery's cache and update managements.

Monkhai avatar Apr 05 '24 15:04 Monkhai

I have the same issue on web.

the following is my code. simply update the local data at onDragEnd callback and use unique strings as the keys

import { useCallback, useState } from 'react';
import { Pressable, Text } from 'react-native';
import DraggableFlatList, {
  RenderItem,
  ScaleDecorator,
  DragEndParams,
} from 'react-native-draggable-flatlist';

export default () => {
  const [ids, setIds] = useState<string[]>(['A', 'B', 'C', 'D']);

  const handleDragEnd = useCallback(({ data }: DragEndParams<string>) => {
    setIds(data);
  }, []);

  const renderItem = useCallback<RenderItem<string>>(({ item, drag, isActive }) => {
    return (
      <ScaleDecorator activeScale={1.05}>
        <Pressable onLongPress={drag} disabled={isActive}>
          <Text>{item}</Text>
        </Pressable>
      </ScaleDecorator>
    );
  }, []);

  return (
    <DraggableFlatList
      data={ids}
      keyExtractor={item => item}
      renderItem={renderItem}
      onDragEnd={handleDragEnd}
    />
  );
};

library version: "react": "18.2.0", "react-native": "0.72.6", "react-native-draggable-flatlist": "^4.0.1", "react-native-gesture-handler": "~2.12.0", "react-native-reanimated": "~3.3.0", "react-native-web": "~0.19.6",

azurechen avatar Apr 19 '24 02:04 azurechen