scrollToIndex doesn't scroll the list in React-virtualized list - reactjs

With react-virtualized, when you scroll the list and set the same scrollToIndex, it doesn't scroll back to the same index.
Is there a way to reset the list back to the position?

That's because the prop hasn't changed. You can use the scrollToCell or scrollToRow method (depending on whether you're using Grid or List/Table) to re-scroll to the same index.

As #brianvaughn also said, you List doesn't scroll to the index back again because to the props to the react-virtualized list component haven't changed.
Since, By default all react-virtualized components use shallowCompare to avoid re-rendering unless props or state has changed. The components won't re-render until an update has happened.
The solution to this is to let react-virtualized know that something external has changed. This can be done a couple of different ways.
Also its mentioned in the documentation that you have two ways to let react-virtualized know that something has changed
Pass-thru props
The shallowCompare method will detect changes to any props, even if they aren't declared as propTypes. This means you can also pass through additional properties that affect cell rendering to ensure changes are detected. So you could pass additional props that change when you fire an event that for the virtualized list doesn't change anything.
<List
{...listProps}
sortBy={sortBy}
/>
Public methods
Grid and Collection components can be forcefully re-rendered using forceUpdate. For Table and List, you'll need to call forceUpdateGrid to ensure that the inner Grid is also updated.
So whenever you wish to update the scrollToIndex if, its value hasn't changed from the previous value you could call forceUpdateGrid on the List. This method can come in handy in a lot of other situations as well
Having said that, it may not be the best option always as there is another option to force scroll event if the scrollToIndex hasn't changed, which is the scrollToRow option. However it requires the row you want to scroll to be visible. Its its not, you won't be able to use it.
scrollToRow (index: number)
Ensure row is visible. This method can be used to safely scroll back
to a cell that a user has scrolled away from even if it was previously
scrolled to.

Related

Update only some context consumers ( or stop some of the context consumers from updating)

For some reason, I have to implement a global Tooltip. Basically we have a global Tooltip component on a high level in react tree and whenever someone hovers overe certain elements the tooltip with the content of the hovered element shows up.
I do that from a Wrapper Component for the item where I need the tooltip, with the use of context. I even tried to provide 2 contexts, one for coordinates and content (of the tooltip/element) and one for setCoordinates and setContent. The values will be used in the tooltip itself and the setters in the Wrapper Component. I do this in order to avoid updates on all consumers of a context, but
Problem is, currently we are talking about a table with a list of items. Each item is wrapped with that Wrapper Component, therefore is connected to context (that provides only setCoordinates and setContent. Is there a way to stop all the elements in the list update when I interact only with one? Setting the Tooltip is trigger on hover, but when I hover over one element, I see all the others in dev tools updating.
TL;DR
Context is consumed by each element of an array. When hovering over one element I set the data in the Context with that element's content, it updates, but all it's brothers (items in the array which also consumes the Context) render again once. How can I stop them from doing that?

react-window saves state of deleted row

here is my problem.
I am using react-window to render large tables. Each row has its own local state. After I delete a row, the next row moves up and gains the state of the deleted row (this how it looks in my app).
Is there a workaround for this problem? Can i have local state for each row with react-window?
codesandbox example | gif how it works
There is a fundamental issue here - react-window will dynamically render, unmount and rerender components. So if you want data persisted across re-renders - you must pass the count via props and a function to change it. Store the data with the item data itself.
I will try and show a working demo of that soon ,but look at this to understand yet another problem happening with this code: https://codesandbox.io/s/react-window-row-state-problem-mmukt
I have edited the size to be 150 instead of 50, this leads to the window getting a scrollbar. Now, try and click on "two" multiple times. This will increase the count. Next, scroll to bottom and go back up. You'll see that the count is lost.
This is because of the way react-window mounts and unmounts components.
To fix this in your original code, I made the following modifications:
1. Move count/active state to parent.
2. Pass the updateCount function to TableRow.
Essentially, the TableRow is now a pure component :)
Here is the link: https://codesandbox.io/s/react-window-row-state-problem-uzsdl
Hope this helps.
In a nutshell, you can't. The issue is that, when you delete an item, the same component that was used previously for it will be used to render the item after it (so if you delete the 2nd item, the 3rd item will use the Row from the old 2nd element). It will maintain that item's state. If you add a useEffect to detect that the item changed, then you break everything after it (since everything is bumped up one, all of the items after the deleted item will reset their state. You don't have access to the sibling component's state, so you have no way to propagate the state.
You have a few options here:
Add the selection to the properties of the item, and provide a way to update the item.
Make the selection state part of the App component's state, and pass it down to the component so it can render appropriately.
I'll also add that you probably don't want to map over your presumably large list of items every render like you are now. It looks like you are doing this to expose deleteItems. I would recommend something more like this:
<FixedSizeList
height={500}
width={300}
itemCount={items.length}
itemSize={50}
itemData={{items, deleteItem}}
>
{Row}
</FixedSizeList>
Then your Row component's data will have both the items array and the deleteItem function. If you maintain your selected rows state in App, you easily extend this to pass the selected state and modification functions down to your Row component.

Using shouldComponentUpdate in a child component appears to be breaking state in parent component?

I have adapted this example from the material-ui table component example from the docs. In the provided example, rows can be selected, in which case the checkbox for that row will be checked. The problem with the example implementation is that when a change is made to any row, all of the rows will be re-rendered, even though only one of them has changed. This works fine as long as the number of rows is quite small but quickly degrades when rendering more rows.
As an attempted fix for this, I tried to implement shouldComponentUpdate in my Row components, to check if the row data (via the 'name' property), or the isItemSelected prop has changed (I ignored the other props as the callback function props will always be considered to have changed on each render). For some reason, this totally breaks my selected state in the parent Table component.
Here is my example, highlighting the issue: table-bug-example. As is, this reflects the example provided in the docs. You can see the lag issues if you set the rows per page drop down to a higher number. You can uncomment my shouldComponentUpdate implementation in the Row Component, to see how the state breaks in the Table component, when using shouldComponentUpdate in the children.
Expected Flow:
parent state keeps track of selected rows
clicking on row updates parent state
parent passes isSelected prop to child rows
row component checks to see if isSelected has changed, and re-renders only if this prop has changed
I have been struggling with this for several hours and can't for the life of me figure out what is wrong. I've also tried using React.memo, as well as different implementations of the setSelected state in the table component (I was previously using a dictionary to keep track of the selected rows, but for now I've left the default implementation provided in the material-ui docs, to minimize any issues introduced in my code).
Might anyone happen to know what is going on here?
When you used your custom shouldComponentUpdate what happened you "saved" the old version of handleClick inside of your custom MyRow, and since handleClick is not a pure function(it is using previous selection to determine prev selection state) it was always reading the wrong state.
For example you have 2 rows (row1 and row2). On initial render selection is : [], and the both render with the version of handleClick that has captured selection as []. You click on row1, it will rerender since its isItemSelected prop has changed, but row2 will not since your shouldComponentUpdate returned false. That means when you click on row2 it will use the handleClick from the first render wich had the selection as [] and it will uncheck row1 selection. There is no magic mechanism that binds selection, it all uses javascript context capturing.
Thats why custom shouldComponent is very dangerous. I would recommend using MyRow as a PureComponent (no need to use React.memo, you can just extend PureComponent) and transform handleClick inside table so it doesnt change (doesnt depend on current selection). Something like this: https://codesandbox.io/embed/material-demo-i0yp2?fontsize=14&hidenavigation=1&theme=dark
Hope this helps.

Getting back to a page with Infinite scroll to the exact same point where the user left it

I used react-infinite-scroll-component it's working just fine.
However, I want to avoid making the user lose his scroll position when he leaves the page and clicks back?
Also please put on consideration Firefox and Safari.
Not 100% sure because I haven't used it - but since no one else has chimed in... the docs say the component has a prop named key that is described as:
the key for the current data set being shown, used when the same
component can show different data sets at different times,
default=undefined
Also, it has a prop named onScroll that is described as:
a function that will listen to the scroll event on the scrolling
container. Note that the scroll event is throttled, so you may not
receive as many events as you would expect.
... which I suspect one of the arguments of which will tell you which keys it loaded / scrolled through.
So my approach would be to use componentWillUnmount to save the last key it loaded into a parent property (or Redux store if you're using Redux)... and when the component loads, if the key exists in the parent (or Redux store if you're using Redux) then pass it that key.

Dealing with frequent updates to a large React component

I have a large React component with scrollbars. I'd like to make some small changes to a few components when it scrolls (basically to keep the left-most column of a table stuck to the left side of the screen).
Normally I'd pass props indicating the component's scroll position down to the components that need to update when it scrolls, but re-rendering the whole component when it scrolls makes the whole thing massively laggy. But the whole thing doesn't really need to be re-rendered -- only a few small parts of it to make sure they stay in the right part of the screen
Is there a good way to re-render only a small part of a component, or is there another way of dealing with frequent updates to large components like this?
Implementation might get a bit hairy but have you had a look into shouldComponentUpdate? (docs)
You could probably track the scrolling and use it to skip updating on the pieces you don't want to update - eg. pass an isScrolling prop to some children and skip update if it's true, or only update if the scoll distance has changed by a certain amount or a certain amount of time has passed.
Depending on the visual effect you want another option is to replace the content of the slow part with a placeholder which is more easily rendered when the isScrolling prop is set - we do something similar with an isLoading prop and loading GIFs, which works well.

Resources