#useEffect not triggered when data change

38 messages · Page 1 of 1 (latest)

exotic jasper
#

I have this :

  const { data: playlistItems } = usePlaylistItems(playlist?.id);
  const { data: isAllowedToEdit } = usePlaylistIsAllowedToEdit(playlist?.id);
  console.log('in table playlist items', playlistItems);
  React.useEffect(() => {
    console.log('new playlist items', playlistItems);
  }, [playlistItems]);

When my playlistItems change I can see the first console.log but the one in useEffect isnt triggered.. why ? I check that because I have a react table showing playlistItems but the table isnt updated when fresh data arrived...
Thx for your help !

lean thunder
#

It's because of the way useEffect works. It doesn't work if you pass an array as the dependency

wet escarp
exotic jasper
exotic jasper
# wet escarp Hi, can you show us `usePlaylistItems` please?

TO prevet making multiple requests I do this for getting the playlist :

export const usePlaylistFull = (playlistId: number) => {
    const queryClient = useQueryClient();
    const supabase = useSupabaseClient();
    return useQuery({
        queryKey: playlistKeys.detail(playlistId),
        queryFn: async () => {
            if (!playlistId) throw Error('Missing playlist id');
            const { data, error } = await supabase
                .from('playlist')
                .select(`
                    *,
                    user(*),
                    items:playlist_item(
                        *,
                        movie(*)
                    ),
                    guests:playlist_guest(
                        *,
                        user:user(*)
                    )
                `)
                .eq('id', playlistId)
                .order('rank', { ascending: true, referencedTable: 'playlist_item' })
                .returns<Playlist[]>()
                .single();
            if (error || !data) throw error;
            
            // Set the playlist items and guests in the queryClient
            queryClient.setQueryData(playlistKeys.items(playlistId), data.items);
            queryClient.setQueryData(playlistKeys.guests(playlistId), data.guests);
            const { items, guests, ...playlistData } = data;
            return playlistData;    
            // return data;
        },
        enabled: !!playlistId,
    });
}

SO here I manually set Query data for playlistItems for making only one requst. But the usePlayItems do this for being invalidate (only items) :

export const usePlaylistItems = (playlistId?: number) => {
    const supabase = useSupabaseClient();
    return useQuery({
        queryKey: playlistKeys.items(playlistId as number),
        queryFn: async () => {
            if (!playlistId) throw Error('Missing playlist id');
            const { data, error } = await supabase
                .from('playlist_item')
                .select(`
                    *,
                    movie(*)
                `)
                .eq('playlist_id', playlistId)
                .order('rank', { ascending: true })
            if (error) throw error;
            return data;
        },
        enabled: !!playlistId,
    });
}```
#

So my problem seems to be with React Table

#

Because with this :

interface DataTableProps {
  playlist: Playlist;
  playlistItems: PlaylistItem[];
}

export default function PlaylistTable({
  playlist,
  playlistItems,
}: DataTableProps) {

  const table = useReactTable({
    data: playlistItems,
    columns,
    initialState: {
      pagination: {
        pageSize: 1001,
      },
    },
    state: {
      sorting,
      columnVisibility,
      rowSelection,
      columnFilters,
      globalFilter,
    },

PLaylistItems is updated in realtime, but it seems when playlistItems change, the table dont change. I dont really know what to do for updating the table

wet escarp
exotic jasper
#

Items are associated to a playlist id

wet escarp
#

Try to set structuralSharing: false on usePlaylistItems and see if it changes anything.

exotic jasper
#

But do using structuralSharing: false can cause issue ?

wet escarp
# exotic jasper Ohh its working now

It's an optimization mechanism that combines oldData and newData and only changes items that are different. If you have a deeply nested object or array, this may sometimes lead to unpredicted behavior.
Frankly speaking, it's not a necessity, it's only an optimization technique, so it shouldn't break anything if you turn it off.

exotic jasper
exotic jasper
# wet escarp It's an optimization mechanism that combines oldData and newData and only change...

There is a way to optmise speed when updating react query data ?
Because doing this :

try {
        queryClient.setQueryData(playlistKeys.items(playlist?.id as number), (data: PlaylistItem[]) => {
          if (!data) return null;
          const oldIndex = items.indexOf(active.id);
          const newIndex = items.indexOf(over.id);
          return arrayMove(data, oldIndex, newIndex);
        });
        
        await updatePlaylistItem({
          id: active.id,
          rank: over?.data.current?.sortable.index + 1
        })
      } catch (error) {
        queryClient.setQueryData(playlistKeys.items(playlist?.id as number), (data: PlaylistItem[]) => {
          if (!data) return null;
          const oldIndex = items.indexOf(active.id);
          const newIndex = items.indexOf(over.id);
          return arrayMove(data, newIndex, oldIndex);
        });
        toast.error("Une erreur s\'est produite");
      }

Is not smouth :

#

Doing this instead with local state :

try {
        setPlaylistItems((data) => {
          const oldIndex = items.indexOf(active.id);
          const newIndex = items.indexOf(over.id);
          return arrayMove(data, oldIndex, newIndex);
        });
        await updatePlaylistItem({
          id: active.id,
          rank: over?.data.current?.sortable.index + 1
        })
      } catch (error) {
        setPlaylistItems((data) => {
          const oldIndex = items.indexOf(active.id);
          const newIndex = items.indexOf(over.id);
          return arrayMove(data, newIndex, oldIndex);
        });
        toast.error("Une erreur s\'est produite");
      }
wet escarp
# exotic jasper There is a way to optmise speed when updating react query data ? Because doing t...

It's hard to say. Optimization techniques will differ depending on your code structure. There's no one solution to this, every case is different and requires a closer look. You need to profile your app and see bottlenecks, then address them one by one. Maybe too many components are dependent on PlaylistItems and they all rerender at the same time, maybe you need to move use playlist items closer to where you need them, to not cause rerenders higher up in the component tree, like you did with a local state.

exotic jasper
wet escarp
exotic jasper
# wet escarp

I only use this for being able to invalidate items when I add one from another page 😅

wet escarp
exotic jasper
#

Or u means invalidate directly ['playlist', id]

wet escarp
#

This article explains the difference between the two and when you should use one over the other.

exotic jasper
wet escarp
# exotic jasper Well when I dont need to query the database, like change the rank of an item, I ...

That's what I'm talking about. You are doing it manually with a setQueryData to save a few requests. Most of the time you want to use invalidateQueries with a key. It doesn't make an immediate request if no component is watching this query. It will fire a new request when components that use this query mount on the page. setQueryData sets data directly to the cache, but it doesn't prevent the request from firing if you don't configure staleTime. So you don't save any requests in this case. Of course, I don't see the full picture, maybe you set staleTime, and requests actually don't fire after setQueryData, but in general, this method should be used in very specific cases. Not only it take more code to write setQueryData than invalidateQuery, but it also does not provide any benefits if done wrong.

exotic jasper
# wet escarp That's what I'm talking about. You are doing it manually with a `setQueryData` t...

Tell me if Im wrong but in my case, when I use setQueryData that means the element is already show in page. For example in playlist page where items are rendered. I have a button for example to delete an item. This button gonna make a query to my db to delete this item. And for not refetch the entire playlist, I use setQueryData to delete manually in my list of items the concerned one. If I use invalidateQueries its gonna refetch the entire playlist right ?

wet escarp
exotic jasper
exotic jasper
# wet escarp Yes. What you describe is a good case for optimistic update. But don't worry to ...

For the changing rank, I find something, using queryCache for items but also local state for the rendering:

  useEffect(() => {
    if (playlistItems) {
      setPlaylistItemsRender(playlistItems);
    }
  }, [playlistItems]);

  if (!playlist) return null;

  return (
    <>
      <PlaylistHeader playlist={playlist} totalRuntime={playlistItems?.reduce((total: number, item: PlaylistItem) => total + (item?.movie?.runtime ?? 0), 0)} />
      <div className="p-4">
        {playlistItemsRender ? <PlaylistTable playlist={playlist} playlistItems={playlistItemsRender} setPlaylistItems={setPlaylistItemsRender} /> : null}
      </div>
    </>
  );
#

Its working ahah

wet escarp
# exotic jasper For the changing rank, I find something, using queryCache for items but also loc...

Don't get me wrong, there's no right or wrong here. I'm just giving you another perspective on a matter. It's all trade-offs. If you see that in your case using setQueryData is beneficial then go for it. I'm just saying that maybe you should try to consider a different approach. Maybe adding some pagination to playlistItem to get data in smaller portions to not overload your DB, or optimizing the data structure will help you get the best of both worlds 🙂