Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to implement reverse list like 'flex-direction: column-revserse'? #27

Closed
hitbear518 opened this issue May 18, 2020 · 18 comments
Closed

Comments

@hitbear518
Copy link

how to implement reserve list, like chat app?

@uncvrd
Copy link

uncvrd commented Dec 30, 2020

I would be interested in hearing if anyone has figured out an implementation using this library as a chat app. I can't seem to find a virtual-scroll react library that implements such a feature without some hacking, which seems odd since so many apps implement a chat style app! Any suggestions are welcome...

@raphaelbadia
Copy link

Chiming in to say that as @uncvrd said, I too never found a way to get a chat app working with virtualized list :(

@pukingrainbows
Copy link

Seem like not even react-windows has a solution for it

List (previously DynamicSizeList)
This list should be used for dynamically sized content (e.g. chat, newsfeed). It requires the ResizeObserver API (or polyfill).

bvaughn/react-window#302

And most likely will not happen :(

@tannerlinsley
Copy link
Collaborator

This issue has been automatically closed due to inactivity. If this issue needs more attention, please comment or open a new issue with updated information. Thanks!

@bradley
Copy link

bradley commented Oct 5, 2022

I solved this. Here is a custom version of react-virtual in a sandbox showing reverse scroll using column-reverse: https://codesandbox.io/s/react-virtual-reverse-scroll-infinite-example-pf73zb.

I wasn't able to figure out how to import my branch into the sandbox so just hardcoded my version therein, see the react-virtual folder. It is a 1:1 copy of the important and current react-virtual files where the only update is to the core.js file (virtual-core in this repo) to add reverse support.

The critical details for the end user are to add reverse: true as a prop to useVirtualizer and the various CSS adjustments to the standard setup, which you can see in the sandbox. The user needs to use all the flex properties to the CSS settings shown and to use bottom instead of top for the absolute positioning on the list items.

I've ensured this works well for infinite scroll and with scrollToIndex and anything else I could see that may be in question.

Again, see the sandbox.

@tannerlinsley if this looks right to you Im happy to provide a PR. I did not add tests as, well, time...

Blessings. To my knowledge this may be the first working version of virtual lists that actually works with column-reverse for any of the projects.

#400

@bradley
Copy link

bradley commented Oct 5, 2022

Oh, I should add, there is one bit of styling in there that was specific to a chat use case (e.g.; if there is less content than can fill the parent that content sits at the top until the content fills the parent and then sticks to the bottom so content loaded above goes above the fold). This can be adjusted by changing the CSS in that file (remove margin-bottom: auto). Easy to change the tinker with the CSS.

@elijahiuu
Copy link

I tried it out on the bigest reverse scroll killer - iOS, and it's got a glitch :( It starts off in the wrong place (not at the bottom) and nothing is painted to the screen.

Screen.Recording.2022-10-13.at.3.15.17.PM.mov

@intergalacticspacehighway
Copy link

intergalacticspacehighway commented May 4, 2023

Reverse infinite scroll list example - https://codesandbox.io/p/sandbox/immutable-silence-76pwko?welcome=true

It's transforming the list and items (scaleY -1) and inverting the mousewheel event.

Screen.Recording.2023-05-04.at.5.26.17.PM.mov

@tannerlinsley Happy to send an example PR if this looks good to you!

@samchouse
Copy link

Would love to see this feature merged into the repo!

@Zerebokep
Copy link

any word on this @tannerlinsley?

@0-don
Copy link

0-don commented Jul 11, 2024

would love to have this reverse scroll with dynamic sizes

@gcoakleyjr
Copy link

Please an example of reverse with dynamic

@Zerebokep
Copy link

Imho react-virtual should have full column-reverse support, it's basically the first thing devs are looking for when doing a chat-like app, and since AI stackoverflow has seen a huge increase in devs asking for a virtualization library which has support for it.

I'm currently using the sample from @piecyk (props to him) which basically inverts the virtual items, it works, but it is still not as good as using column-reverse. @tannerlinsley

@brightsidedeveloper
Copy link

I need this bad for dynamic lists, do I full send and try to make a PR today 😅

@0-don
Copy link

0-don commented Sep 16, 2024

I hope this helps someone this works with dynamic images in my case, I am using tailwindcss with virtual and react query

import { useVirtualizer } from "@tanstack/react-virtual";
import { useInfiniteQuery } from "@tanstack/react-query";
import { useEffect, useRef } from "react";
import Image from "next/image";

const reverseScroll = (e) => {
  e.preventDefault();
  e.currentTarget.scrollTop -= e.deltaY;
};

export const PaymentMessagesChat = () => {
  const parentRef = useRef(null);
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
    queryKey: ['paymentMessages'],
    queryFn: fetchPaymentMessages,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });

  const messages = data?.pages.flatMap((page) => page.messages) || [];

  const rowVirtualizer = useVirtualizer({
    count: hasNextPage ? messages.length + 1 : messages.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 100,
  });

  useEffect(() => {
    const lastItem = rowVirtualizer.getVirtualItems().at(-1);
    if (lastItem?.index >= messages.length - 1 && hasNextPage && !isFetchingNextPage) {
      fetchNextPage();
    }
  }, [rowVirtualizer.getVirtualItems(), messages, hasNextPage, isFetchingNextPage, fetchNextPage]);

  useEffect(() => {
    const current = parentRef.current;
    current?.addEventListener("wheel", reverseScroll, { passive: false });
    return () => void current?.removeEventListener("wheel", reverseScroll);
  }, []);

  return (
    <div ref={parentRef} className="relative my-4 h-[27rem] scale-y-[-1] overflow-y-auto">
      <div style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
        {rowVirtualizer.getVirtualItems().map((virtualRow) => {
          const message = messages[virtualRow.index];
          const isLoaderRow = virtualRow.index > messages.length - 1;
          return (
            <div
              key={virtualRow.key}
              data-index={virtualRow.index}
              ref={rowVirtualizer.measureElement}
              style={{
                transform: `translateY(${virtualRow.start}px) scaleY(-1)`,
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
              }}
              className="flex gap-4 p-2"
            >
              {!isLoaderRow && <MessageItem message={message} />}
            </div>
          );
        })}
      </div>
    </div>
  );
};

const MessageItem = ({ message }) => (
  <>
    <UserAvatar user={message.user} />
    <div className="max-w-[80%] rounded-lg p-3 bg-primary text-primary-foreground">
      <p>{message.message}</p>
      {message.image && (
        <Image
          src={message.image}
          alt={message.message}
          className="object-contain my-2 max-w-full"
          width={0}
          height={0}
          sizes="100vw"
          style={{ width: '100%', height: 'auto' }}
        />
      )}
      <div className="flex items-center space-x-5 text-sm opacity-70">
        <span>@{message.user.username}</span>
        <span>{new Date(message.createdAt).toLocaleString()}</span>
      </div>
    </div>
  </>
);

const UserAvatar = ({ user }) => (
  <div className="h-8 w-8 rounded-full bg-gray-300 flex items-center justify-center">
    {user.avatar ? (
      <img src={user.avatar} alt={user.username} className="h-full w-full rounded-full" />
    ) : (
      <span>{user.username.slice(0, 2).toUpperCase()}</span>
    )}
  </div>
);

@piecyk
Copy link
Collaborator

piecyk commented Sep 17, 2024

I'm currently using the sample from @piecyk (props to him) which basically inverts the virtual items, it works, but it is still not as good as using column-reverse. @tannerlinsley

@Zerebokep why not as good?

@Zerebokep
Copy link

I'm currently using the sample from @piecyk (props to him) which basically inverts the virtual items, it works, but it is still not as good as using column-reverse. @tannerlinsley

@Zerebokep why not as good?

@piecyk my main issue is that it is slower and you have to adjust the scroll position on multiple occurrences (e.g. scroll to bottom on load, scroll to bottom on chat submit, when an element expands below the list or list item), which also causes the scrollbar to show up (I know there are workaround).

From a developer experience when switching from column-reverse to an inversed virtual list it really feels that you have implement a dozen workarounds to get to the same ux you had with column-reverse.

Please don't get me wrong, I really love react-virtual, imo it is the best virtualization library out there (all our frontend teams are using it), but something like @bradley suggested would be the killer feature which many devs (+ myself) out there are currently looking for.

@a-creation
Copy link

+1 ^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests