0

So I have been trying to figure out a solution for big lists in react. and I almost have the perfect solution - but I can't seem to figure out how to get this one detail to work.

I have a list of cards that I want to render quickly - I'm using the react-window package

import{VariableSizeList as List} from 'react-window';

I have a component called MaterialCard, and a component that contains lists of MaterialCards:

On the MaterialCard if you click on an inspect button - it opens up the innards of the card revealing a form input section.

const [cardOpen, setCardOpen] = useState(false);
const cardInnards = //a bunch of jsx.

//in the return
<button className="btn-inspect" onClick={()=>setCardOpen(!cardOpen)}>inspect/edit</button>
{cardOpen?cardInnards:<div></div>}

In the multi-list component container - I have this code.

const materialCardsNew = materialsNew.map((x,i)=><MaterialCard props={x} key ={`${i}matsnew`} />);
//react-window function to make a row:
const Row = array=> ({ index }) => array[index] //is MaterialCard

//in the return
<List
  height={755}
  itemCount={materialCardsNew.length-1}
  itemSize={()=>130} // this is where I'm having trouble.
  width={634}
>
{Row(materialCardsNew)}
</List>

Currently the itemSize is what I'm trying to fix...

What happens currently is the item has a fixed size area it appears in - and (thanks to z-index) the innards appear over other items in the list.

What I want to happen is the item size of the MaterialCard that is open - to be of a larger size: such that it doesn't cover other cards - I want it to expand and I don't want it to cover other cards at all.

My trouble is I don't know how to read the component's internal state from the top - how do I determine in the list container component which card is open. I understand I require a function for this... but: well...

the pseudocode I've come up with is this: this of course does not work - but it is more or less what I want to do.


//resize function
const getHeight = (arr)=>(index)=>arr[index].state.cardOpen?500:100; //bigger size if the card is open

//jsx
itemSize={getHeight(materialCardsNew)}
        
//I've also tried arr[index].style.height,
//and searched through the arr[index] properties in the console to see 
//if there was anything I could use... no luck.

My first ideas are bunk... and I'm not really sure how to approach this... I'm pretty sure I shouldn't need a massive additional array for each array of material cards (there are a few categories) to keep track of which cards are open... but I can't seem to find the correct way of going about this.

How do I accomplish this?

altruios
  • 986
  • 1
  • 10
  • 29

1 Answers1

0

For this issue:

My trouble is I don't know how to read the component's internal state from the top

Lift state up. So in the containing component, you can use a hook like in the top level component:

const [activeCard, setActiveCard] = useState()

And in the card, pass in the function:

<Card setActiveCard={setActiveCard} key={someKey} {...otherProps}/>

And, in the implementation of the card, you can have something like:

useEffect(() => setActiveCard(key), [key])

And the top level component will have the 'active' card information.

Not sure I completely was clear on the issue, but that is one mechanism for sending child information to the parent.

And, if I am understanding the issue, you could have some logic in the child component to check if the active card is equal to the card:

<Card setActiveCard={setActiveCard} activeCard={activeCard} key={someKey} {...otherProps} />

useEffect(() => activeCard === key ? setComponentSize(activeSize) : setComponentSize(defaultSize), [{dependency array}])

Of course, the setComponentSize would be in the top level component, and passed in a similar fashion to setting the card index in the top level. And if everything is set in the containing (parent component), you could just check the index vs the activeCard.

Finally, just make sure however you are checking for the active card, you cleanup and call setActiveCard(-1), or whatever the default parameter you might want to use when the active card's state changes.

Austin Coose
  • 256
  • 4
  • 6
  • multiple card should be able to be opened at once.... but i think that's just a useEffect change to have the set active card be a function instead of just passing in the key, and well... in other words... you are saying to make a UseState array for each list... to keep track of a bool for each card? do I understand you correctly? so for four lists of 2000 items... i need 4 additional arrays in useState at the top level to keep track of a single bool for each card... pass that down as a prop into the card... at that point i might as well use redux to work do that behind the scenes? – altruios Feb 03 '21 at 18:36
  • you could also use and array structure: const [activeCards, useActiveCards] = useState([]) then, to determine what is open, or even a Set, so, const activeSet = new Set() [activeCards, useActiveCards] = useState(activeSet) then to check if the card is in the set activeSet.has(key), etc. and since Set has delete, add, clear, a little easier than doing array manipulations with indexOf, slice, or filter, etc. – Austin Coose Feb 03 '21 at 19:05
  • set is an interesting idea. but if all keys are unique anyway... is there any benefit? is it easier to remove things from a set than an array? - it's all got to be immutable anyway? – altruios Feb 03 '21 at 19:10
  • right, Set would just be a an extra guard from having duplicate keys (which like you said, would be a bad practice anyhow!) – Austin Coose Feb 03 '21 at 19:14
  • I don't think you would need redux, if you are setting the activeCards and passing that down to the 'Row' component, you can check if active and pass that back up to the containing component (or set size in the child) ... I took a look at react-window, and looks you pass could set the size passing everything down from the parent, I haven't used it myself, so not sure of any side-effects, but when in doubt, lift state up :) – Austin Coose Feb 03 '21 at 19:17