0

Okay so I have the following array in _app.js:

this.state = {
    assets: [
        { id: uuidv4(), selected: false, text: 'Sorte', url: 'https://images-cdn.bridgemanimages.com/api/1.0/image/600wm.XXX.42211270.7055475/7208045.jpg', title: 'Drumhop Second Time', description: 'Power & Strife - The disuasion of the middle class', keywords: 'lolly;pop;ping;bop' },
        { id: uuidv4(), selected: false, text: 'optical_dillusion2322.jpg', url: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQfarL6oOr3NRwYXEJHK7AWPYzimZimjrjlh0Vm5LYOrHCoKBKmj0MAkjK2pJQBRmlkJLQ&usqp=CAU', title: 'Optimal Popsical', description: 'Optical Illusions for the masses', keywords: 'optical;illusion' },
    ],
}

This gets passed down to the pagination and displayed in Asset.jsx:

export default function Asset(props) {

    const {
        asset,
        index,
        onHandleAssetIDChange,
        onHandleAddAssetToWithdrawList,
    } = props

    return(
        <div className={`rounded overflow-hidden shadow-lg ${asset.selected === true ? "border-4 border-primary" : false}`} >
            <div className="flex flex-col relative group">
                <img
                    className="w-full h-48 object-cover"
                    src={asset.url}
                    alt={asset.description}
                />
                <div className="absolute top-2 left-2 text-primary flex gap-2 bg-white/70 px-3 py-2 rounded group-hover:opacity-100 opacity-0 duration-100">
                    <FiEdit className='text-2xl cursor-pointer' onClick={(e) => {
                        onHandleAssetIDChange(asset.id)
                    }}/>
                    <BsCircle className='text-2xl cursor-pointer' onClick={(e) => {
                        onHandleAddAssetToWithdrawList(asset.id)
                    }}/>
                </div>
                <div className="flex-1 p-6">
                    <div className="">{asset.text}</div>
                </div>
            </div>
        </div>
    )
}

As you see from the above, I have the line ${asset.selected === true ? "border-4 border-primary" : false} which updates the border if the asset has the selected flag set to true.

However for some reason, when I change the state of a 'selected' in the assets list, it doesnt update the element with the border. Here's how I change the state:

handleAddAssetToWithdrawList(assetID) {
    const newAssets = this.state['assets'].map(prevState =>
        prevState.id === assetID ? { ...prevState, selected: !prevState.selected } : prevState
    );
    this.setState({ assets : newAssets });
    console.log(newAssets)
}

So If I click the checkbox, change the page in the pagination, then go back to the original page, the border does change. But only once I move around the pagination and come back. It doesnt update straight away.

Just note, the 'path' that the assets array takes to get to the final element on the page is this:

_app.js -> <Component {...this.state} /> to manage.js -> <PaginatedItems items={assets} /> to PaginatedItems.jsx -> <Items currentItems={currentItems} /> to PaginatedItems.jsx function Items() -> <Asset asset={item} /> then finally in Asset.jsx the code you see above.

Also, the only time that useEffect is used (which I have a feeling is the answer for this) is in PaginatedItems.jsx and it currently looks like this:

    useEffect(() => {
        // Fetch items from another resources.
        const endOffset = itemOffset + itemsPerPage;
        console.log(`Loading items from ${itemOffset} to ${endOffset}`);
        setCurrentItems(items.slice(itemOffset, endOffset));
        setPageCount(Math.ceil(items.length / itemsPerPage));
    }, [itemOffset, itemsPerPage]);

How do I get this element to update? The path that it is buried in is quite deep and I'm not even sure where the problem lies.

What am I doing wrong?

Richard Bridge
  • 318
  • 1
  • 3
  • 10

2 Answers2

0

You can use your condition before constructing your className, this way it will be re-rendered once the state changes. For having a cleaner JSX, you can assign the common part of your class to some variable. Something like this:

const borderlessClass = "rounded overflow-hidden shadow-lg"

<div className={asset.selected === true ? `${borderlessClass} border-4 border-primary` : borderlessClass} >
Enes Toraman
  • 261
  • 2
  • 8
  • This seems to produce exactly the same results, i dont see how putting the classes into a variable forces it to re-render? – Richard Bridge Jul 12 '22 at 12:48
  • Assigning to a variable is just for cleaner JSX, actually. I think when you put your condition inside back-ticks and make it a string literal, it loses its dynamic structure. This is why when component gets unmounted and mounted again (going back and forth between pages), you get the updated value. Have you tried putting it like above? – Enes Toraman Jul 12 '22 at 13:05
  • yes I have, it doesnt seem to update the element still. – Richard Bridge Jul 12 '22 at 13:13
0

It is necessary to add your dependency to dependency array of useEffect:

useEffect(() => { console.log(assets) }, [assets])

As this beautiful answer says:

Also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created.

StepUp
  • 36,391
  • 15
  • 88
  • 148
  • Where should I put the useEffect? and why does simply logging it to console update the element on the page? I updated my question with the 'path' of files that the assets array travels down to get the to page if that helps at all. – Richard Bridge Jul 12 '22 at 12:59
  • @richard_b it is necessary to use “useEffect” where you are using “setState” – StepUp Jul 12 '22 at 15:50