Is there a way to add headings separating data in react-virtualized - reactjs

I have a <List /> inside an <InfiniteLoader />, inside an <AutoSizer />, also <WindowScroller /> and <WindowScroller /> 😁 (wow, so much hocs there) but for simplicity, I think my question could fit the same with a simple <List /> Component.
I'm not sure if there is a way to render some kind of separator or heading like a title for the section (piece of data) rendered below.
Since each item have a prop that allow to group the data in chunks, and I am receiving this data ordered and grouped like:
[
{
item: 1,
name: 'Banana',
kind: 'fruits',
},
{
item: 2,
name: 'Apple',
kind: 'fruits',
},
{
item: 3,
name: 'Watermelon',
kind: 'fruits',
},
{
item: 4,
name: 'Dog',
kind: 'animals',
},
{
item: 5,
name: 'Cat',
kind: 'animals',
},
{
item: 6,
name: 'Horse',
kind: 'animals',
},
//...
]
the idea is to render something like:
<ul>
<li className="fullWidth">
<h3>Fruits</h3>
</li>
<li>Banana</li>
<li>Apple</li>
<li>Watermelon</li>
<li className="fullWidth">
<h3>Animals</h3>
</li>
<li>Dog</li>
<li>Cat</li>
<li>Horse</li>
</ul>
Making some calculation in rowRenderer method?
Or, since I am wrapping the <List /> in an <InfiniteLoader /> I could pass an argument when fire loadMoreRows, but anyway I think I have to do some calculation in rowRenderer, the responsible for the final render.

This is possible, although much easier if your data is pre-sorted so that groups aren't interleaved. You basically have 2 options:
Pre-flatten your data into an array of 2 types of items. One type is a header and the other type is a child. If you want headers to be a different size than regular rows you'll need to also provide a rowHeight getter function that's able to distinguish between a header item and a child and return a different value. Your rowRenderer will also need to do the same.
Compare the current datum's "kind" to the one that came before it and include a header with the rendered row if they are different. This approach also requires a custom rowHeight getter but does not require any data flattening so it may be easier/faster. The only downside is that your header and row item will be within the same div (or li) if you approach this way.
Here's an example of option 2: https://plnkr.co/edit/l22Ir7?p=preview
And here is the relevant code bits:
function includeHeader(index) {
return (
index === 0 ||
list[index].kind !== list[index - 1].kind
);
}
function rowHeight({ index }) {
return includeHeader(index)
? ROW_HEIGHT_WITH_HEADER
: ROW_HEIGHT;
}
function rowRenderer({ index, isScrolling, key, style }) {
const datum = list[index];
return (
<div
className='Row'
key={key}
style={style}
>
{includeHeader(index) && (
<h3>{...}</h3>
)}
<div>{...}</div>
</div>
);
}

Related

How to exclude some options from React Select options

I have around 50 options to be shown in the react select options. But I want to exclude some of the options with logic to already posted values.
The purpose is, that we have a form where we add values from the drop-down list. if one item is been added then that should not have to be shown in the dropdown list.
refactored code:
export default function App() {
const choices = [
{
value: 0,
label: "Container empty to shipper"
},
{
value: 1,
label: "Container pickup at shipper"
},
{
value: 2,
label: "Container arrival at first POL (Gate in)"
},
{
value: 3,
label: "Container loaded at first POL"
}
];
const postedList = [
"Container empty to shipper",
"Container pickup at shipper"
];
return (
<div className="App">
<h1>Select Box</h1>
<Select
isClearable={false}
// here the choices should be 2 eliminating the other 2 whose labels are matching to postedlist
options={choices}
defaultValue={choices[0]}
onChange={(choice) => console.log(choice.value)}
/>
</div>
);
}
Currently, it's rendering all 4 choices available but I want to return only 2 of them whose labels are not matching to postedlist
I also have created Codesandbox. If you want to see it there.
You can use Array.prototype.filter() and Array.prototype.includes() to filter out already posted items. Then use the filteredList as input to the Select component as below.
const filteredList = choices.filter(({ label }) =>
!postedList.includes(label)
);
return (
<div className="App">
<h1>Select Box</h1>
<Select
isClearable={false}
options={filteredList}
defaultValue={filteredList[0]}
onChange={(choice) => console.log(choice.value)}
/>
</div>
);
You can dynamically filter items and exclude them with the includes method.
<Select
options = {choices.filter((choice) => !postedList.includes(choice.label))}
...
/>

How to map through an Array of object with the same data on some object and get it displayed once and also get other properties

I have an array of products like this coming from firebase,
products: [
{
id: "product_1",
drinkName: "Chivita",
category: "Juice",
description: "The best drink ever"
},
{
id: "product_2",
drinkName: "5 Alive",
category: "Juice",
description: "The best drink ever"
},
{
id: "product_3",
drinkName: "Cocacola",
category: "Others",
description: "The best drink ever"
}
];
What I want is to loop through the array and get the category displayed only once, I know each drink can have the same category, but I want it displayed only once and also get its Id for each product, and also get the drinkName display under each category. The id will be used to navigate to the Product Details page.
I already have something like this:
const items = products?.reduce((prev, current) => {
if (!(current?.data()?.category in prev)) {
prev[current?.data()?.category] = [];
}
prev[current?.data()?.category].push({
drinkName: current?.data()?.drinkName,
id: current.id,
});
return prev;
}, {});
Error 1: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'.
This Error shows where prev[current?.data()?.category]
Now time to loop
{Object.keys(items).map((key: any) => {
return (
<AccordionItem key={key.id}>
<h2>
<AccordionButton fontSize={"sm"} fontWeight={"medium"}>
<Box flex="1" textAlign="left">
{key}
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel px={0} pb={4}>
{items.map((drinkName: any, idx: number) => {
return (
<Flex key={idx}>
<Button
size={"sm"}
variant={"ghost"}
fontWeight={"light"}
>
{drinkName}
</Button>
</Flex>
);
})};
So, when I loop through the first time, grab the category and id, if the category exists already, display it once because multiple drinks can have the same category.
The second time, grab the drinkName and pass the id to the button when clicked navigate to the product details [which is simple].
Error 2:
{items.map((drinkName: any, idx: number) => {
return (
<Flex key={idx}>
<Button
size={"sm"}
variant={"ghost"}
fontWeight={"light"}
>
{drinkName}
</Button>
</Flex>
)
Objects are not valid as a React child. If you meant to render a collection of children, use an array instead
When I do
console.log(items)
When I do
{Object.keys(items[key]).map((drinkObject: any, idx: number) => {
console.log(drinkObject);
// Prints the indexes on the drink: e.g if Juice is a category, I will have 0, 1, 2 based on the items there
})}
You have a couple issues, so let me break it down a bit.
So I ran down and relooked over the code, I understand you're doing things a bit differently than expected now. So, the same as before, the type of }, {}); is an empty object. TS cannot understand that you're trying to access [current?.data().category] at runtime. You would need to make it either strongly typed, or get away with using {unknown}, which could make sense and might suppress the issue.
I suspect you could use this instead:
return prev
},
({} as {[key: string]: Array<{category?: string, drinkName: string, id: string}>}))
In the second issue, the same that I said before this edit is true. You are using items.map on an object. You probably want it Object.keys like above.

dynamic import of images react

I am trying to dynamically render images from a local folder within my project
The object structure looks like this
{
id: 2,
text: 'How would you describe your level of influence in management of the farm?',
type: 'cardChoice',
choices: [
{
id: 1,
text: 'I am the primary decision maker',
questionId: 'Hm?',
value: 'I am the primary decision maker',
image: '1.jpg',
},
{
id: 2,
text: 'I hent',
questionId: 'Hrm?',
value: 'I',
image: '2.jpg',
},
{
id: 3,
text: 'I arm',
questionId: '?',
value: 'Irm',
image: '3.jpg',
},
],
},
In my component I select the folder that contains the images const baseUrl = '../../assets/images/CNA'
After that, in my return I try to render the images
<img src={`${baseUrl}'${questionChoice.image}'`} alt={questionChoice.text} />
The page renders, but my image isn't loading and it's showing my alt instead
Heres my full component
const CardChoiceQuestions = ({ cardChoiceArray, currentAnswer, updateCurrent, submitAnswer }) => {
const { id, value } = currentAnswer
const baseUrl = '../../assets/images/CNA'
return (
<ButtonContainer>
{cardChoiceArray.map(questionChoice => {
return (
<Button
active={questionChoice.id === id}
type="button"
key={questionChoice.id}
onClick={() => {
const answer = { id: questionChoice.id, value: questionChoice.value }
updateCurrent(answer)
submitAnswer()
}}
>
<p>{questionChoice.text}</p>
<img src={`${baseUrl}${questionChoice.image}`} alt={questionChoice.text} />
</Button>
)
})}
</ButtonContainer>
)
}
I don't have my laptop in front of me but a few things I noticed. Do you need a slash "/" after your base url? Also, the string concatenation should be completed in one set of brackets after the $ sign. Not sure if that's the issue try a few console.log(string path) amd verify it is going where you think it is. It looks like the path may be wrong. You may be better off conditional rendering images as opposed to building a dynamic url but either way it should render on change.
<img src={`${baseUrl}/${questionChoice.image}`} alt={questionChoice.text} />
use like this
Leaving this here in case someone comes across this...
<img src={require(`../../assets/images/CNA/${questionChoice.image}`)} alt={questionChoice.text} />
Not sure why I can't use ${baseUrl} but this works for now.

How do I render a list or table of my own elements with react-window?

I have an array of thousands of objects, like
const people = [
{ name: "Alan", age: 36, score: 103428 },
{ name: "Belinda", age: 40, score: 1482822 },
{ name: "Carol", age: 51, score: 78492391 },
...
];
I want to display a table where each person object is a row, but there are so many of them and their score field updates every few seconds, and it bogs the user's machine down.
This seems the perfect opportunity to try out react-window. Only say 20 of these objects will be onscreen at any moment, so I'll just display and re-render the ones that are in the viewport right now.
But I just can't make sense of the react-window examples or documentation. Here's the example it gives for a fixed-size grid:
const Cell = ({ columnIndex, rowIndex, style }) => (
<div style={style}>
Item {rowIndex},{columnIndex}
</div>
);
const Example = () => (
<Grid
columnCount={1000}
columnWidth={100}
height={150}
rowCount={1000}
rowHeight={35}
width={300}
>
{Cell}
</Grid>
);
There's some kind of magic happening with how a FixedSizeGrid renders its children but I don't know what it is, and I don't know how I could replace Cell with a component of my own. Where are the style, columnIndex and rowIndex props coming from? I'm reading the source code and can't figure that out. It's not mentioned anywhere that you can give an array/collection to one of the react-window components so how do you map an array to one? The only way I can see to do it is by indexing into my people array, but that feels... wrong? Wouldn't that lead to sorting becoming very expensive for example?

How to reuse subresource data for referenced inputs in React-admin?

In react-admin documentation the use of ReferenceArrayInput is for this kind of data structure:
{
id: 1,
groups: [1, 2, 3]
}
And then:
<ReferenceArrayInput source="groups" reference="groups" allowEmpty>
<SelectArrayInput optionText="name"/>
</ReferenceArrayInput>
Using a custom json data provider, it will be make this request:
https://example.com/api/groups/?ids=[1,2,3]
or if the API doesn't support WHERE IN, it will be do individual calls for each id:
https://example.com/api/groups/1
https://example.com/api/groups/2
https://example.com/api/groups/3
But if I have the following data structure:
{
id: 1,
name: "Pepito Perez",
groups: [
{ id: 1, name: "HR"},
{ id: 2, name: "IT"},
{ id: 3, name: "FINANCE"}
]
}
I have the name field already, so I don't want make additional requests.
When I go to the edit view react-admin performs almost 70 requests unnecessary.
How can I avoid that? there's a way to reuse the data?
Also is tricky use ReferenceArrayInput component with an array of objects, I have to add a nonsense format prop to make it works: format={v => (v ? v.map(i => (i.id ? i.id : i)) : [])}
Guess it's related to the first problem.
Thanks in advance!
If the choices is not meant to be fetched, ReferenceInput is not what you want. You need only a SelectInput with programmatic setted choices. You can achieve that with FormDataConsumer:
<FormDataConsumer>
{({ formData, ...rest }) =>
<SelectArrayInput
source="selectedGroups"
optionText="name"
choices={formData.groups}
{...rest}
/>
}
</FormDataConsumer>
Note a different source, probably setting as groups, equal to choices "source", after first selected group, would result in a re-render, letting choices values equal to the single selected group.
That's almost exactly the use case of FormDataConsumer in documentation:
https://marmelab.com/react-admin/Inputs.html#linking-two-inputs

Resources