How to cleanup "react-beautiful-dnd" Draggable after removal? - reactjs

I'm using this awesome react-beautiful-dnd library to let users reorder a list of items by "drag and drop". It works just fine. The only problem I've encountered is about when a user removes one of the items in the list. It seems that the "Draggable" component doesn't cleanup itself after getting unmounted:
const [items, setItems] = useState(initialItems);
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{items.map((item, index) => {
return (
<Draggable
key={item.id}
draggableId={item.id.toString()}
index={index}
>
{(provided) => (
<div
{...provided.dragHandleProps}
{...provided.draggableProps}
ref={provided.innerRef}
>
<DraggableItem
item={item}
setItems={setItems}
/>
</div>
)}
</Draggable>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
As you can see, "setItems" is passed to "DraggableItem" component so that it can update state after removing an item.
It correctly updates state and everything is just fine but I get this "Warning" on my browsers console:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
How am I supposed to cleanup after updating items by calling "setItems"?

Related

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 toggle button based on API return

I am currently working on a React application.
As you can see in the following code, there are two buttons. I would like to show the grey button if the user hasn't created a Request (default). If the user has created a Request the button should be green.
Problem:
The main issue is, that the map function (API) no returning any value for "no Requests" so I am not able to identify "no Requests". That means that .isEmpty,.length, .indexOf,... and also the "if-else" is not working because there is nothing to validate.
const greenButton = (
<Button color="green" onClick={e => DeleteRequest(e, rest.id)}>Request</Button>
);
const greyButton = (
<Button color="grey" onClick={e => CreateRequest(e, props.reservationID)}>Request</Button>
);
return (
<div>
{greyButton}
{requests.map((rest, i) => (
<div key={i}>
{rest.requester === username
? <div>{greenButton}{rest.requester} <i
aria-hidden="true"
className="delete icon"
onClick={e => DeleteRequest(e, rest.id)} />
</div> : <div />}
</div>
))}
</div>
);
Result in the UI:
API:
Any Ideas? (If you need more information, I am happy to provide more details)
conditional rendering
Use a ternary on requests being a defined/truthy object and has a truthy length property. In the true branch map the requests, grey button in false branch. This covers requests being either initially (or returned from the API) undefined or an empty array [].
return (
<div>
{requests && requests.length ? requests.map((rest, i) => (
<div key={i}>
{rest.requester === username
? <div>{greenButton}{rest.requester} <i
aria-hidden="true"
className="delete icon"
onClick={e => DeleteRequest(e, rest.id)} />
</div> : <div />}
</div>
)) : (
{greyButton}
)}
</div>
);

Exclude An item from mapping

I want to explicitly exclude imgvid from mapping and pass it to PostContainer Component.It Causes Rerendering of Component.I want imgvid to pass from PostList to PostContainer.But without this double mappings (in postContainer and postList) Iam unable to correctly fetch my data in the .jsx page.But this double mapping causes rerendering of the .jsx page and it causes doubling of my content in the front page.
<React.Fragment>
<div style={{ marginTop: "10px" }}>
{posts.map(item => (
<PostContainer
isStylefeed={isStylefeed}
likeAccess={likeAccess}
commentAccess={commentAccess}
wowAccess={wowAccess}
yayAccess={yayAccess}
post={item}
imgvid={imgvid}
key={item[ID]}
loadPosts={loadPosts}
currentCommunity={currentCommunity}
currentCommunityID={currentCommunityID}
handlePosts={handlePosts}
getOutfits={getOutfits}
isMember={isMember}
/>
))}
</div>
</React.Fragment>
//PostContainer.jsx
return (
<React.Fragment>
<div>
{imgvid
&& imgvid.map(item => (
<Posts
likeAccess={likeAccess}
commentAccess={commentAccess}
wowAccess={wowAccess}
isStylefeed={isStylefeed}
yayAccess={yayAccess}
handleRedirectOutfitPage={this.handleRedirectOutfitPage}
currentCommunity={currentCommunity}
toggleFlagModal={this.toggleFlagModal}
handleFlaggedPostReasonChange={this.handleFlaggedPostReasonChange}
deletePost={this.deletePost}
handledeleteComment={this.handledeleteComment}
getCurrentProfile={this.getCurrentProfile}
onReadOnlyProfile={this.onReadOnlyProfile}
updatePost={this.updatePost}
handleUpdatePost={this.handleUpdatePost}
toggleUpdate={this.toggleUpdate}
toggleOptions={this.toggleOptions}
handleFlagPost={this.handleFlagPost}
flagModal={flagModal}
post={post}
likesProfile={likesProfile}
followersProfile={followersProfile}
outfits={outfits}
readOnlyProfileModal={readOnlyProfileModal}
modalUpdate={modalUpdate}
postText={text}
postOptions={postOptions}
getAllCommentsByPostId={this.getAllCommentsByPostId}
currentCommunityID={currentCommunityID}
outfitChanged={outfitChanged}
getLatestPost={this.getLatestPost}
toggleMessageModal={this.toggleMessageModal}
messageModal={messageModal}
refData={refData}
showflagModal={showflagModal}
checkFlaggedOrNot={this.checkFlaggedOrNot}
flagged={flagged}
isMember={isMember}
imgvid={item}
email={email}
/>
))}
</div>
</React.Fragment>
);

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>
}

React-virtualized List selected item styling only fire when scroll up

I'm using React-virtualized to show the data as list.
I add selected styling for list item, it supposes highlight the item once it got click.
The current problem is onClick is fired, but selcted styling only shows when scroll up the list.
List component
<div className={styles.autoSizerContainer}>
<AutoSizer>
{({width, height}) => {
// Selected customer styling only fire after scroll
return (
<List
width={width}
height={height}
rowHeight={50}
rowRenderer={this.renderRow}
rowCount={rowCount}
overscanRowCount={3}
className={styles.list}
/>
)
}}
</AutoSizer>
</div>
List item
private renderRow = ({index, key, style}: ListRowProps) => {
const data = this.props.dataList[index];
return (
<div style={style} key={data.id}>
<div className={styles.listItem}>
<div>data.name</div>
<Item key={data.id}
isDataSelected={this.state.selectedId === data.id}
/> //return true will show selected styling
</div>
</div>
)
};
"react-virtualized": "^9.21.0",
"react": "^16.8.4"
Any ideas are welcome!
Thanks!!!
React-Virtualized will only re-render your component when one of the props provided by ListRowProps changes. It doesn't know that your render method uses this.props.dataList and this.state.selectedId internally. You will need to do one of two things.
Explicitly tell the List to redraw when this.state.selectedId changes. The List exposes an api for this purpose.
Use something like React's Context api to provide the necessary data in a way such that changes can be detected. Something like this should work:
const {Provider, Consumer} = React.createContext<number | null>(null);
<div className={styles.autoSizerContainer}>
<AutoSizer>
{({width, height}) => {
// Selected customer styling only fire after scroll
return (
<Provider value={this.state.selectedId}>
<List
width={width}
height={height}
rowHeight={50}
rowRenderer={this.renderRow}
rowCount={rowCount}
overscanRowCount={3}
className={styles.list}
/>
</Provider>
)
}}
</AutoSizer>
</div>
private renderRow = ({index, key, style}: ListRowProps) => {
const data = this.props.dataList[index];
return (
<Consumer>
{(selectedId) =>
<div style={style} key={data.id}>
<div className={styles.listItem}>
<div>data.name</div>
<Item key={data.id}
isDataSelected={selectedId === data.id}
/> //return true will show selected styling
</div>
</div>
}
</Consumer>
)
};

Resources