Using conditions in AntD List Actions - reactjs

I am trying to use conditions for the actions array in the AntD List Component. The following action should only be added, if the user is an admin and the count is >0. But it seems, that I cannot provide conditions like this inside the actions.
<List dataSource={items}
itemLayout="horizontal"
renderItem={item => (
<List.Item
actions={[
{isAdmin && item.count > 0 && <Button onClick={this.moveItemUp(item.key)}>up</Button>,}
<a key="list-loadmore-more">more</a>]}
>
...
</List.Item>
)}
Any ideas how to solve?

React automatically ignores any null or false values in render, so you are safe to leave the values as it exists:
<List dataSource={items}
itemLayout="horizontal"
renderItem={item => (
<List.Item
actions={[
isAdmin && item.count > 0 && <Button onClick={this.moveItemUp(item.key)}>up</Button>,
<a key="list-loadmore-more">more</a>]}
>
...
</List.Item>
)}
></List>
Stackblitz example
EDIT: If you want to avoid passing the item altogether, you can use the spread operator to do so:
actions={[
<a key="list-loadmore-edit">edit</a>,
...(condition ? [<a>Another</a>] : []),
]}

You can move the isAdmin and item.count > 0 condition outside of return and pass dynamically created array to like shown below
import React from 'react';
export default class App extends React.Component {
render() {
let actionList = [<a key="list-loadmore-more">more</a>];
if(isAdmin && items.length > 0) actionList.push(<Button onClick={this.moveItemUp(item.key)}>up</Button>)
return (
# your code
<List dataSource={items}
itemLayout="horizontal"
renderItem={item => (
<List.Item
actions={actionList}
>
...
</List.Item>
)}
# your code
)
}
}

Related

An element is not removed from the array, how to fix it?

I have data that I get from api and through the map() method I display these "cards", each card has an image when clicked on which this image should receive an additional class. I implemented this by adding the index of the card to an array and now I can assign new classes to them, but I can't delete them
P.S. I have strict mode enabled, if it is disabled it removes extra classes on all other cards except the one I clicked on
//On the first click, it adds to the array, on the second click, it must delete it (index is written to the array)
function toggleFavoriteChanel(index) {
setFavorite(prevState => {
let returnArray = prevState;
if(prevState.includes(index)){
console.log(prevState)
console.log(index)
return returnArray.splice(prevState.indexOf(index), 1)
}else{
// here are 3 dots
return [..returnArray, index]
}
})
}
// <img src={star} alt="star".../>
{Array.isArray(props.visibleData) ? props.visibleData.map((chanel, index) => {
return (
<>
<div className="chanel__item" key={index}>
<img src={star} alt="star" onClick={() => props.toggleFavoriteChanel(index)} id={index} className={`star ${props.favorite.includes(index) ? 'active' : ''}`} />
<NavLink
onClick={() => props.updateData(index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{index + 1}. </div>{chanel.name_ru}</div>
</NavLink>
</div>
</>
)
}) : null}
The issue is that you are setting favorite to the return value of splice, which is an array containing the deleted elements (from MDN docs on splice). What you want instead is to return returnArray after calling splice on it.
Just change this line in toggleFavoriteChanel:
return returnArray.splice(prevState.indexOf(index), 1)
to:
returnArray.splice(prevState.indexOf(index), 1);
return returnArray;
While the above should fix your issue, I would recommend approaching this problem in a different way if you are just trying to toggle a CSS class in response to clicking (assuming you don't need a list of the favorited cards at a higher level).
The approach is to define a component for the card and hold the isFavorite (clicked) state locally rather than in an array in an ancestral component.
Here's a rough example:
function Card(props) {
const [isFavorite, setIsFavorite] = React.useState(false);
return (
<div className="chanel__item">
<img
src={star}
alt="star"
onClick={() => setIsFavorite(prev => !prev)}
id={props.index}
className={`star ${isFavorite ? 'active' : ''}`}
/>
<NavLink
onClick={() => props.updateData(props.index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{props.index + 1}. </div>{props.chanel.name_ru}</div>
</NavLink>
</div>
)
}

react-beautiful-dnd pull out the anonymous function from React Draggable component

I need to pull out the anonymous function from Draggable into a callback function using the useCallback(..., []) hook supplying the dependencies provided, snapshot if possible.
Upon investigation we have found that the 3rd party component Draggable is using an anonymous function that essentially recreates the function on the stack for each step in the list. When this happens for a route with 500 steps the stack trace gets increased by 500 bringing us closer to a stack overflow.
React has a virtual dom, and if the child component has not changed it will not re-render it. But anonymous functions make it re-render every time, but if the anonymous function is pushed down one more layer it won't see it as changed.
Here is my implemented code.
const StepContainer = ({
step,
index,
draggedDescription,
isSelected,
selectedTaskIdsCount
}: Props) => {
return (
<Box data-testid="stepContainer" className={stepContainer}>
<Box className={'hoverstyle'}>
<Draggable draggableId={step.key} index={index} key={step.key}>
{(
provided: DraggableProvided,
snapshot: DraggableStateSnapshot
) => (
<div
data-testid="clickHandler"
{...provided.draggableProps}
ref={provided.innerRef}
data-is-dragging={
snapshot.isDragging && !snapshot.isDropAnimating
}
className={snapshot.isDragging ? `${dragging}` : ''}
>
{snapshot.isDragging ? (
<DraggedStepAsset
draggedDescription={draggedDescription}
stepCount={
selectedTaskIdsCount > 1
? selectedTaskIdsCount
: 1
}
/>
) : (
<AccordionSteps
isSelected={isSelected}
provided={provided}
step={step}
/>
)}
</div>
)}
</Draggable>
</Box>
</Box>
);
};
The idea is to pull out the anonymous function into a callback function using the useCallback(..., []) hook supplying the dependecies provided, snapshot if possible. Then we can wrap the useCallback function with a useMemo() to ensure React memoizes it, this could potentially enable react to reuse the same function on the stack for every step-component instead of adding it for each step-component. Making our code much more optimized.
Please help me to resolve this issue.
Hope the below snippet works for you.
const StepContainer = ({
step,
index,
draggedDescription,
isSelected,
selectedTaskIdsCount
}: Props) => {
return (
<Box data-testid="stepContainer" className={stepContainer}>
<Box className={'hoverstyle'}>
<Draggable draggableId={step.key} index={index} key={step.key}>
{DraggableContentCallback}
</Draggable>
</Box>
</Box>
);
};
const DraggableContent = (
provided: DraggableProvided,
snapshot: DraggableStateSnapshot
) => (
<div
data-testid="clickHandler"
{...provided.draggableProps}
ref={provided.innerRef}
data-is-dragging={
snapshot.isDragging && !snapshot.isDropAnimating
}
className={snapshot.isDragging ? `${dragging}` : ''}
>
{snapshot.isDragging ? (
<DraggedStepAsset
draggedDescription={draggedDescription}
stepCount={
selectedTaskIdsCount > 1
? selectedTaskIdsCount
: 1
}
/>
) : (
<AccordionSteps
isSelected={isSelected}
provided={provided}
step={step}
/>
)}
</div>
);
const DraggableContentCallback = useCallback(DraggableContent, []);

“react-infinite-scroll-component” Stopped working after one call (loadMore only gets called once)

I use the react-infinite-scroll-component library for pagination,but even though hasMore is true, loadMore is called once.
<InfiniteScroll
dataLength={100}
pullDownToRefreshThreshold={50}
next={loadMoreConversation}
scrollableTarget="scrollableDiv"
hasMore={true}
loader={<h4>Loading...</h4>}
>
<ChatItemList
chatItems={chatItems}
isInDeleteMode={deleteActive}
onBottomDrawerHandler={onBottomDrawerHandler}
/>
</InfiniteScroll>
Please help me with this, What am I doing wrong?
I had this issue. It turns out I was using dataLength in the wrong way. I thought it was supposed to be the total length of items that could be displayed, but instead, it seems it should be the length of items that are currently displayed, so the correct way is should be something like this:
<InfiniteScroll
dataLength={page * 10}
pullDownToRefreshThreshold={50}
next={loadMoreConversation}
scrollableTarget="scrollableDiv"
hasMore={true}
loader={<h4>Loading...</h4>}
>
<ChatItemList
chatItems={chatItems}
isInDeleteMode={deleteActive}
onBottomDrawerHandler={onBottomDrawerHandler}
/>
</InfiniteScroll>
in this case, I load 10 items per page. I hope this helps you because I searched a lot and did not find an answer until I reached it with all the effort and error.
This is a React Query useInfiniteQuery() example, assuming you have:
data: {
pages: [
{/*a page*/
results:[
{/*an item*/
/*...*/
}
]
}
]
}
Set your dataLength this way:
const {isSuccess,
isLoading,
isError,
data,
error,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery(
path /*your query path here*/,
fetchFn /*your fetch function here*/);
return <div>
{isError && <div>Error! {error?.message}</div>}
{isLoading && <div>Loading...</div>}
{isSuccess && data?.pages && (
<div>
<ListHeader isFetching={isFetchingNextPage} data={data}/>
<InfiniteScroll
dataLength={data.pages.reduce((acc, page) => acc + page.results.length, 0)}
next={fetchNextPage}
hasMore={hasNextPage}
loader={<div>Loading...</div>}
endMessage={<p>- You have seen it all -</p>}
>
{data.pages.map((page, i)=> (
<Fragment key={i+''}>
{page.results.map((item, j)=> (
<ListItem key={i+'.'+j} path={path} item={item}/>
))}
</Fragment>
))}
</InfiniteScroll>
</div>
)}
</div>
Where ListHeader and ListItem are components of your own.

How to avoid React Hook UseState to share the states?

I may have a bad title for this question, but here's my situation.
I use a chunk of json to render a list. The list item can be expanded and showed the sub list if it has children property. The json structure includes two arrays and each array contains more sub-arrays. I use tabs to switch arrays.
I use useState to manage the value isExpanded of each individual sub-array component. but it seems like the state isExpaned is shared for all tabs.
The state isExpanded remains same even if I switch to another tab. In other words, why the sub-list keep expanded when I switch to another tab?
In addition, why the expanded sub-list of each tab overlaps each other. They should keep 'close' when I switch to another tab because I set the initial state to false already. (const [isExpand, setIsExpand] = useState(false))
const ListItem = ({name, children}) => {
const [subList, setSubList] = useState(null)
const [isExpand, setIsExpand] = useState(false)
const handleItemClick = () => {
children && setIsExpand(!isExpand)
console.log(isExpand)
}
useEffect(() => {
isExpand && children && setSubList(children)
}, [isExpand, children])
return (
<div className='list-wrapper'>
<div className='list-item'>
{name}
{
children &&
<span
className='expand'
onClick={() => handleItemClick()}>
{isExpand ? '-' : '+'}
</span>
}
</div>
<div className='list-children'>
{
isExpand && subList && subList.map((item, index) =>
<ListItem key={index} name={item} />
)
}
</div>
</div>
)
}
Here's the codesanbox, anyone helps?
It seems like React is confused due to index being used as ListeItem key.
(React will try to "share" isExpanded state as they look the same according to the key you specified)
You could change the key from key={index}
<div className="contents">
{contents &&
contents.children &&
contents.children.map((item, index) => (
<ListItem
...... 👇 ....
key={index}
name={item.name}
children={item.children}
/>
))}
</div>
to use more distinct key, item.name
<div className="contents">
{contents &&
contents.children &&
contents.children.map(item => (
<ListItem
...... 👇 ....
key={item.name}
name={item.name}
children={item.children}
/>
))}
</div>
Check out the forked sandbox.
https://codesandbox.io/s/soanswer57212032-9ggzj

React Reveal not working for array of data

Can't use React Reveal on array of data with .map() to produce effect from documentation.
https://www.react-reveal.com/examples/common/
Their documentation gives a nice example
<Fade left cascade>
<div>
<h2>React Reveal</h2>
<h2>React Reveal</h2>
<h2>React Reveal</h2>
</div>
</Fade>
I want to produce the same CASCADE effect with my data
<React.Fragment>
{projects.filter(project => project.category === category)
.map((project, index) => {
return (
<ProjectThumb key={index} project={project}
showDetails={showDetails}/>
)
})}
</React.Fragment>
The effect I'm getting is that the entire ProjectThumb component list fades in in one group, I need them to fade in individually and as i scroll. Thanks in advance.
Pass react-reveal props to your React component. It will work.
<Fade left cascade>
<div>
{
projects
.filter(project => project.category === category)
.map((project, index) => (
<ProjectThumb key={index} project={project} showDetails={showDetails} />
))
}
</div>
</Fade>
In your ProjectThumb.js
const ProjectThumb = props => {
return <Whatever {...props}>{...}</Whatever>
}

Resources