React performance issue with drag and drop - javascript

I'm struggling with react performance issue while adding an element to the box. For this, I use React Rnd library.
I have a map that renders items when there is a new item inside the array:
children.map((children, index) => (
<Box
key={children.id}
isPreview={false}
index={index}
slot={name}
{...children}
/>
)),
Box component is Rnd component from the library, and it is actually big one.
<Rnd
style={{
//2 lines off css
}}
minHeight={MIN_SIZE}
minWidth={MIN_SIZE}
enableResizing={isResizingEnabled}
disableDragging={condition}
size={size}
position={Position}
lockAspectRatio={isAspectRatioLocked}
onResizeStart={onResizeStart}
onDragStop={(e, newPosition) => {
onDragStop(newPosition)
}}
onResizeStop={(e, dir, ref, delta, newPosition) =>
onResizeStop(ref, newPosition)
}
resizeHandleComponent={createResizeHandles(isInCollision)}
dragGrid={grid}
resizeGrid={grid}
bounds="parent"
>
<StyledDiv
onClick={() => {
dispatch(actions.setEditMode({...properties}))
}}
isBeingCropped={isCroppingEnabled}
isPreview={isPreview}
isEditable={isEditable}
isInCollision={isInCollision}
isEditStartable={isEditStartable}
>
{children}
</StyledDiv>
</Rnd>
And the problem is when I add 4 elements to this box, it took sometimes 2-4 seconds...
Any idea how it could be solved?
Is there any simple solution to make it faster, or do I have to investigate each function/hook and optimize it with some useCallback, useMemo, or something?

Yes, you should start from avoiding unnecessary re-renders, then you can look into optimizing each function. As this is a drag and drop component, i believe most props will change often whiles you drag an element, as a result make sure that each re-render is important before actually re rendering the component.
Here are a few places to start from.
Pass only what is needed to the box
Instead of spreading the children to the Box, pass only what is need to it, this will avoid any unnecessary re renders in case there are props in there that the Box do not care about.
children.map((children, index) => (
<Box
key={children.id}
isPreview={false}
index={index}
slot={name}
/>
)),
Use Memoization to avoid re renders
For times when the objects haven't moved, you may want to avoid any re renders, as a result memoize the Components.
Use useCallback for functions
This will prevent the callback from being assigned everytime the parent is re rendered. The biggest culprit i see is the onClick handler which depends on the actions, as a result will render the box again and again when the action changes, even though the box doesn't depend on all the actions
const { setEditMode } = dispatch;
const handleDragStop = useCallback(
(e, newPosition) => {
onDragStop(newPosition);
},
[onDragStop]
);
const handleResizeStop = useCallback(
(e, dir, ref, delta, newPosition) => {
onResizeStop(ref, newPosition);
},
[onResizeStop]
);
const handleOnClick = useCallback(() => {
dispatch(setEditMode({ ...properties }));
}, [onResizeStop, setEditMode, properties]);
<Rnd
style={
{
//2 lines off css
}
}
minHeight={MIN_SIZE}
minWidth={MIN_SIZE}
enableResizing={isResizingEnabled}
disableDragging={condition}
size={size}
position={Position}
lockAspectRatio={isAspectRatioLocked}
onResizeStart={onResizeStart}
onDragStop={handleDragStop}
onResizeStop={handleResizeStop}
resizeHandleComponent={createResizeHandles(isInCollision)}
dragGrid={grid}
resizeGrid={grid}
bounds="parent"
>
<StyledDiv
onClick={handleClick}
isBeingCropped={isCroppingEnabled}
isPreview={isPreview}
isEditable={isEditable}
isInCollision={isInCollision}
isEditStartable={isEditStartable}
>
{children}
</StyledDiv>
</Rnd>;
Checkout this article I wrote here if you need any further explanation

Related

How do i add elements from a map, inside another map?

I'm making a json map, and sending it to another component, to assemble several Cards at the same time:
{search.map((element, index) => {
return(
<div key={index}>
<CardItem key={index} data={element}></CardItem>
</div>
)
})}
And then I made an entry that when I put the name of the card title, it only shows the cards with those titles. Basically a search filter in React
const [value, setValue] = useState('')
const searchLowerCase = value.toLowerCase()
const search = job.filter(element => element.company.toLowerCase().includes(searchLowerCase) || element.role.toLowerCase().includes(searchLowerCase) || element.level.toLowerCase().includes(searchLowerCase) )
const handleInput = (e) => {
setValue(e.target.value)
}
The problem is that inside my json, there is an array that brings other elements, that is, I need to make another map. I'm making this other map directly in the JSX of the cards component
{data.languages.map((element, index) => {
return(
{element}
)
})}
So I need to make my input also look for these other elements that are inside the map that is being performed in the other component. For when I write the name of these elements in the input, show only the card on the screen that has these selections. Any tips would be greatly appreciated
I'm thinking of some way to add this map made in my other component, inside this map that is doing the searches, so when I type inside the input, all the elements that are written, regardless of which map it is, are shown on the screen

Good practice when organizing React functional component code (functions, splitting...)

Is there a better solution when it comes to splitting code into sub components, and to writing functions called in render ? For example would it be better to have
const CustomRow = ({ row = {}, ...props }) => {
const cells = [];
for (const value of Object.values(row)) {
cells.push(value);
}
return (
<TableRow>
{/* Some content */}
{cells.map((cell, index) => (
<TableCell key={index++}>{cell}</TableCell>
))}
{/* More content */}
</TableRow>
);
};
Or
const CustomRow = ({ row = {}, ...props }) => {
const getCells = () => {
const cells = [];
let index = 0;
for (const value of Object.values(row)) {
cells.push(<TableCell key={index++}>{cell}</TableCell>);
}
return cells;
}
return (
<TableRow>
{/* Some content */}
{getCells()}
{/* More content */}
</TableRow>
);
};
And more generally, how good or bad is it to split such components ? Assuming I have a bigger CustomTable component, which I've broken down in CustomRow, CustomHeaders, ...
I'm not really sure to know the reasonable limit to start exploding big components into small chunks; I've started splitting methods and Component as soon as they tend to become long enough to cause confusion (which can be quick), but I don't know if there are strong opinions that go against this ?
Splitting code seems to give more readability and a clear overview, but I don't have many experienced colleagues regarding React, so I'm unsure about what others do. I know there is a lot of freedom regarding code organization but I want to know more about others habits

index with lodash times method

I need to replicate a component "n" times. To do that I used the lodash method times. The problem is that I need an index as a key for the components generated and It doesn't look like it has one.
I have the following code:
export const MyComponent: React.FC<{times: number}> = ({ times }) => {
return (
<>
{_.times(times, () => (
//I need a key={index} in this div
<div className="bg-white border-4 border-white md:rounded-md md:p-2 content-center my-4 shadow w-full">
</div>
))}
</>
);
};
This will return the component that is inside n times.
I tried to do a method that returns the component and set an index with useState, but it goes in an infinite loop. I thought to put a big random number as a key so it is extremely difficult to get the same, but I don't like that solution. I'd like to use this method because it is clean.
So what do you think I could do to give a to the component?
It's passed to you as a function parameter:
_.times(times, (index) => (blabla))

In my React application, how can I add a newline after each item in this map function?

I have this redux selector I've been working on in my React application. I've made good progress but I've hit a wall with this last issue i'm working on and I feel like it shouldn't be that difficult to solve but I'm struggling.
What I'm trying to achieve is after each item has been mapped, the next item must go to a new line.
export const vLineRejectionSelector = createSelector(
selectedVIdSelector,
linesSelector,
(id, lines) =>
lines
.filter(line => line.id === id)
.map(
(rejectString, index) =>
`Line: ${index + 1} ${rejectString.rejectReason}`
)
);
The only relevant code to look at in this is the map function. I want each item to go to a new line as its being mapped.
The output should look something like:
Line 1: Reject Reason One
Line 2: Reject Reason Two
Instead the output looks like:
Line1: Reject ReasonOneLine2: Reject Reason Two
This is being rendered in JSX as well
The value of this is passed around as a prop and gets rendered in the JSX like:
<Typography variant="body2">
{rejectReason}
{reasons && reasons.join(', ')}
</Typography>
Its value is {rejectReason}.
I would advise against composing JSX in your selector and rather just return the lines as an array as you are doing currently, but then map it to either a list or a simple <br /> joined list in the render() function. This keeps your selector more easily testable and also doesn't mix state selection concerns with presentational concerns.
E.g:
in your container
const mapStateToProps = (state) => {
return {
rejectReason: vLineRejectionSelector(state)
}
}
const SomeComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SomeComponent)
export default SomeComponentContainer
and then in your SomeComponents render function:
<Typography variant="body2">
{this.props.rejectReason.map((rejectReason) => <>Line: {index + 1} {rejectReason}<br /></>)}
</Typography>
Try to use <br /> tag at the end of each line.
Like Line: ${index + 1} ${rejectString.rejectReason}<br />

React renders again the same div elements - Reconciliation question

In React documentation I was reading about reconciliation. The following interesting scenario just came to my mind.
In my examples the code uses a button element to toggle a boolean value on click event. Based on that the code decides with ternary operator which element should React render.
Let's have two components to represent my example:
const First = () => {
return <div>element - no difference</div>
}
const Second = () => {
return <div>element - no difference</div>
}
There are no difference in the rendered elements at the end.
First example
Have First and Second functional components in the first example as the following:
const YourComponent = () => {
const [renderFirst, setRenderFirst] = useState(true);
return <>
<button onClick={() => setRenderFirst(!renderFirst)}>Toggle</button>
{renderFirst ? <First /> : <Second /> }
</>
}
Second example
In the second example just using div elements but ending with the same results:
const Contact = () => {
const [renderFirst, setRenderFirst] = useState(true);
return <>
<button onClick={() => setRenderFirst(!renderFirst)}>Toggle</button>
{renderFirst ? <div>element - no difference</div> : <div>element - no difference</div> }
</>
}
Question
My understanding as per the documentation states:
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch.
At the end in either way the rendered result will end up <div>element - no difference</div>. The Second example is not rendering again the DOM element obviously.
Why is React rendering the First example then? Are those considered different types in this case?
Thank you!
By rendering I assume you mean changes to be "rendered" aka committed to the DOM. The reconciliation process will still be triggered in both examples.
The answer is simple. In your first example you are returning a React component (<First /> or <Second />) whereas in the second example you are returning a React element (one of the two div's).
React cannot know beforehand what each of your two components will do (they could have their own logic), so in the latter case, React will just see that you want to replace First with Second and just re-render. In the former case, you are only returning elements which can be objectively compared.
In addition to #Chris answers, I made a small test approving the answer.
My main consideration was if JSX will generate a new instance although the components may unmounted due to the condition.
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
// You can check either
const Component = <div>element - no difference</div>;
const Contact = () => {
const [renderFirst, setRenderFirst] = useState(true);
const componentRef = useRef();
const first = useRef();
useEffect(() => {
console.log(componentRef.current);
const [child] = componentRef.current.children;
if (!first.current) {
first.current = child;
}
}, []);
useEffect(() => {
const [child] = componentRef.current.children;
console.log(child !== first.current ? 'different' : 'same');
});
return (
<>
<button onClick={() => setRenderFirst(prev => !prev)}>Toggle</button>
<div ref={componentRef}>
{renderFirst ? (
<div>element - no difference</div>
) : (
<div>element - no difference</div>
)}
{/* {renderFirst ? Component : Component} */}
</div>
</>
);
};
ReactDOM.render(<Contact />, document.getElementById('root'));

Categories