Virtualize huge items list in react-select ValueContainer - virtualization

I have Select contol from react-select library with many items (over 30000).
So, when we select many items it starts lagging, we cannot scroll, remove items or it takes very long time.
I tried to use List from react-virtualized, but it does not make list to be virtual...
We customize ValueContainer this way:
import { List } from 'react-virtualized';
const ValueContainer = (props: ElementConfig<typeof components.ValueContainer>) => {
return (
<components.ValueContainer {...props}>
<List
width={200}
height={200}
rowCount={(props.children && props.children.length) || 0}
rowHeight={60}
rowRenderer={({ key, index, style }) => {
const option = props.children[index];
return (
<div key={key} style={style}>
{option}
</div>
);
}}
/>
</components.ValueContainer>
);
};
How we can virtualize the list of the items in ValueContainer component?
Long List
Thanks in advance for any ideas!

Related

Material UI select dropdown arrow keys navigation not working

I added a textfield inside mui multiple select component , now keyboard arrow up and down list navigation is not working instead,on arrow onkeydown it focuses on the div itself (scrolling on arrow keydown).
Any help is appreciated (AutoComplete is not an option)
Have tried adding autoFocus on MenuItem itself , but that starts from the last list
import * as React from "react";
import { Theme, useTheme } from "#mui/material/styles";
import MenuItem from "#mui/material/MenuItem";
import TextField from "#mui/material/TextField";
import FormControl from "#mui/material/FormControl";
import Select, { SelectChangeEvent } from "#mui/material/Select";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
}
};
const names = [
"Oliver Hansen",
"Van Henry",
"April Tucker",
"Ralph Hubbard",
"Omar Alexander",
"Carlos Abbott",
"Miriam Wagner",
"Bradley Wilkerson",
"Virginia Andrews",
"Kelly Snyder"
];
function getStyles(name: string, personName: string[], theme: Theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium
};
}
export default function MultipleSelect() {
const theme = useTheme();
const [personName, setPersonName] = React.useState<string[]>([]);
const handleChange = (event: SelectChangeEvent<typeof personName>) => {
const {
target: { value }
} = event;
setPersonName(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value
);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<Select
multiple
value={personName}
onChange={handleChange}
MenuProps={MenuProps}
onKeyDown={(e) => {
if (e.key !== "Escape") {
e.stopPropagation();
}
}}
>
<div style={{ textAlign: "center", margin: "20px 0px" }}>
<TextField placeholder="type..." />
</div>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
It seems the issue is that the TextField element is a child of the Select element and is intercepting the keydown events that would normally be used to navigate the list of options. One way to fix this would be to use the onKeyDown prop on the TextField element to prevent it from intercepting the keydown events. Instead of calling e.stopPropagation(), you can call e.preventDefault() which will prevent the event from being handled by the TextField element and allow it to be handled by the Select element.
<TextField placeholder="type..." onKeyDown={e => e.preventDefault()}/>
Also to make sure that focus is on the first element in the list after the user opens the list you can use autoFocus prop on the first element of the list.
<MenuItem
key={names[0]}
value={names[0]}
style={getStyles(names[0], personName, theme)}
autoFocus
>
{names[0]}
</MenuItem>
This should allow the arrow keys to navigate the list as expected, while still allowing the user to type into the TextField.

svelte-tiny-virtual-list + svelte-infinite-loading crashing on page load

I have used svelte-infinite-loading, and it worked fine at first,
but as the list got very long, my web app started using substantial amounts of memory, using as much as 2gb.
So, I needed to virtualize this infinite list.
I used svelte-tiny-virtual-list as recommended by svelte-infinite-loading's author:
<script>
....
function onInfinite({ detail }) {
const skip = items !== undefined ? items.length : 0;
fetchItems(skip).then((data) => {
if (data.length === 0) {
items = [];
detail.complete();
return;
}
if (items === undefined) items = data;
else items = [...items, ...data];
detail.loaded();
});
}
onMount(() => {
fetchItems(0).then((data) => {
Items = data;
});
});
</script>
{#if items !== undefined}
{#if items.length === 0}
<p><i>No items found</i></p>
{:else}
<VirtualList
itemCount={items.length}
itemSize={200}
height="100%">
<div slot="item" let:index>
<Item
item={items[index]} />
</div>
<div slot="footer">
<InfiniteLoading on:infinite={onInfinite} />
</div>
</VirtualList>
{/if}
{/if}
The problem comes when the page loads:
The first few items are fetched and displayed correctly, but then the page grows to abnormal lengths, then the list disappears and I get the following error:
InfiniteLoading.svelte:103 executed the callback function more than 10 times for a short time, it looks like searched a wrong scroll wrapper that doest not has fixed height or maximum height, please check it.
What have I done wrong?
A VirtualList creates items until the height of the list exceed the height of the parent. It then fakes a scrollbar to select which items it should render.
Apparently, you have placed the VirtualList in a container without height/max-height and it can't determine how many items it should create.
You have to set a max-height or a height on the parent element.

How to handle state in Material UI Data Grid's components

I'm attempting to add search functionality to a data grid component. In order to achieve this, I'm adding an input element to the table using the components.header prop as follows (I've omitted irrelevant code):
const Table = () => {
const filterRows = (rows) => {
return rows;
};
const [searchTerm, setSearchTerm] = useState("");
const Header = () => (
<input
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
/>
);
return (
<div style={{ height: 500 }}>
<DataGrid
rows={searchTerm ? filterRows(orders) : orders}
columns={orderColumns}
components={{
header: () => <Header />
}}
/>
</div>
);
};
The issue I'm having is that the input element loses focus each time a character is entered into the input in the header. Presumably, this is because it updates state, which triggers a re-render. This makes it impossible to share and access state of anything contained inside the Data Grid's components because it requires a React.FC argument and won't accept a ReactElement so the input is always re-rendered.
Am I missing something or is this actually not possible with Material UI's Data Grid? It seems like a pretty expected use-case to have something stateful in the header that we'd want to access like a controlled input component in order to use it as a kind of "Tool bar" as mentioned in the Material UI docs.
I've created a code sandbox to replicate the issue here: https://codesandbox.io/s/compassionate-keldysh-z995k?file=/src/App.js:246-726.
Cheers.

Select: Limit number of selected options

I'm using ANT Design's Select component in multiple select mode. After two options are selected (see screenshot) I'd like to prevent any more from being selected. The field should not be disabled, so that you can deselect an option and select another.
I've tried the onFocus event, but it doesn't provide an event that I could use to preventDefault or otherwise keep from opening the dropdown. I've also tried adding a ref and calling blur() whenever the onFocus event is called. This closes the dropdown, but it's still visible for a second.
Does anyone know of a way to accomplish this?
If 3 or more options selected then with a simple condition you can disable other options.
Store selected options in state and while displaying options disable them based on condition.
https://codesandbox.io/s/happy-leftpad-lu84g
Sample code
import React, { useState } from "react";
import { Select } from "antd";
const { Option } = Select;
const opts = ["a11", "b12", "c13", "d14", "e15"];
const Selectmultiple = () => {
const [optionsSelected, setOptionsSelected] = useState([]);
const handleChange = value => {
console.log(`selected ${value}`);
setOptionsSelected(value);
};
return (
<div>
<Select
mode="multiple"
style={{ width: "100%" }}
placeholder="Please select"
onChange={handleChange}
>
{opts.map(item => (
<Option
disabled={
optionsSelected.length > 1
? optionsSelected.includes(item)
? false
: true
: false
}
key={item}
>
{item}
</Option>
))}
</Select>
</div>
);
};
I solved this problem using "open" prop:
const isMaxValues = value.length === limit;
<Select
mode="multiple"
disabled={false}
{...(isMaxValues && { open: false, onDropdownVisibleChange: handleShowError })}
>
{renderOptions()}
</Select>
So you still able to remove/deselect some options
Also you can provide isMaxValues option to renderOptions method and disable Options to be selected(if you need dropdown to be visible)

Clickable listitem in Scrollable Material Ui List while using array in list

I am trying to implement List in material-ui. And i am trying to display randomly generated array of elements, they are being displayed with scrollbar list as i want. But i want to select that particular List Item which is the issue as i m unable to select it, even by using on click. So can anyone help me in this.
Here is my code:
var MuiListElement = React.createClass(
{
handleClick() {
console.log("secondList clicked")
},
render()
{
let faker = require('faker')
let myItems = []
for(let i = 0; i < 5000; i++)
{
let name = faker.Name.findName()
myItems.push(<ListItem onClick={this.handleClick()} key={i.toString()}>{name}</ListItem>)
}
return(
<div style={{width:'400px'}}>
<Paper style={{maxHeight: 200, overflow: 'auto'}}>
<List selectable='true'>
{myItems}
</List>
</Paper>
</div>
)
}
}
)
this.handleClick() calls handleClick function during your render, you should call it when the user clicks the list item. You wan't to pass the function to onClick, not call the function.
myItems.push(
<ListItem
onClick={this.handleClick} //pass function, don't call function
key={i}> //note: you can pass key as number
{name}
</ListItem>)
Now let me predict the future, you would like to know what item was clicked. You can use bind.
myItems.push(
<ListItem
onClick={this.handleClick.bind(this, i)} //bind returns a function
key={i}>
{name}
</ListItem>)
now you know what was passed
handleClick(i) {
console.log("secondList clicked", i)
},
However, every time you render, you are creating a new function and will slightly degrade performance.
See this discussion,
React js onClick can't pass value to method
Let me know if you don't understand.