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'));
Related
I've been trying to understand react reconciliation and am getting really confused by some of the details of how the diffing algorithm works. So far, I understand that whenever an update is made, we create a new react element tree and compare it with our previous react element tree. The diffing algorithm manages finding the difference between the new and old react element trees. The 2 assumptions of the algo. are that elements of the same level and type don't need to be unmounted and re-mounted and that keys provide a way of identifying child elements not by index.
The part that confuses me is how comparisons are made between 2 react instances. For example, when comparing <Comp1/> in the old react element tree and <Comp2/> in the new react element tree (assume that <Comp2> replaced <Comp1> in the creation of the new tree), does the diffing algorithm simply compare both react elements' "type" attributes? So if both have the same "type", then the diffing algorithm doesn't consider un-mounting and mounting into the DOM?
does the diffing algorithm simply compare both react elements' "type"
attributes?
Yes, from the docs:
Whenever the root elements have different types, React will tear down
the old tree and build the new tree from scratch. Going from <a> to
<img>, or from <Article> to <Comment>, or from <Button> to <div> - any
of those will lead to a full rebuild.
Another your question:
So if both have the same "type", then the diffing algorithm doesn't
consider un-mounting and mounting into the DOM?
Yes, in that case React just updates the existing instance. During component update instance remains the same, and state is maintained across renders.
You can see in below example:
The first place where we rendered A, it doesn't get unmounted when we replace it also with another A (because of same type).
The second place where we used A, as soon as we replace it with B, react unmounts A.
let A = (props) => {
React.useEffect(() => {
console.log('Mounted A', props);
return () => {
console.log('Unmounted A', props);
};
}, []);
return <div>This is A: {props.tmp}</div>;
};
let B = (props) => {
React.useEffect(() => {
console.log('Mounted B', props);
return () => {
console.log('Unmounted B', props);
};
}, []);
return <div>This is B</div>;
};
function App() {
let [tmp, setTemp] = React.useState(0);
return (
<div
onClick={() => {
setTemp(tmp + 1);
}}
>
{tmp % 2 == 0 ? <A id="first A"/> : <A id="second A"/>}
{tmp % 2 == 0 ? <A id="third A"/> : <B />}
<p>Start editing to see some magic happen :)</p>
</div>
);
}
ReactDOM.render(<App />,document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
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))
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
I'm scaffolding a component rebuild. It previously had 2 breakpoints but will now have 4. The 2 current breakpoints are piled into a single component with a grand-master ternary showing one or the other of full JSX... yeah, 2 sets of mostly similar JSX output, it's a big component using only 1 half or the other.
I'm going to break out the 2 existing breakpoints into sub components, with the 3rd and 4th sizes as well, and I'm trying to work out a system that will choose which of the 4 components to show.
Here's a copy paste of what I have concepted on stackblitz.com
export default function App() {
const chooser = 2;
const enumb = {
[0]: <Slap />,
[1]: <Bammy />,
[2]: <Weeds />,
};
const between = (x, min, max) => {
return x >= min && x <= max;
};
const show = !between(chooser, 0, 2) ? 0 : chooser;
return (
<div>
<h1>Hello StackBlitz!</h1>
{enumb[show]}
{show}
</div>
);
}
const Bammy = () => {
return <div>component Bammy</div>;
};
const Weeds = () => {
return <div>component Weeds</div>;
};
const Slap = () => {
return <div>component Slap</div>;
};
Note: Change the value of chooser to display the chosen component. show and between() are a sanity check limiter to fallback to a valid value.
So my main questions is about the {enumb[show]} down in the return statement:
Is it "bad practice" to make reference to (or include) child components outside of the return/render of the parent component? (While this doesn't look too bad right here, the props tree is pretty big enough to make it "complex" reading.)
Is there a performance/state/rerender issue with this method of rendering child components??
The 2ndary question is: what would be the best way to do the enumb reference/decision down in the return? (btw I'd like to avoid switch/case cuz it just seems verbose.)
There's no penalty for doing so. Best practices are determined by your co-workers.
Here's an example showing there's no performance hit
If I was to evaluate your code in code review (I know it's probably purely for demonstration purposes) then I would tell you to move the static stuff outside your render method, like this
const enumb = {
[0]: Slap,
[1]: Bammy,
[2]: Weeds
};
const between = (x, min, max) => {
return x >= min && x <= max;
};
export default function App() {
const chooser = 2; // let's pretend chooser is a prop passed into App
const show = !between(chooser, 0, 2) ? 0 : chooser;
const Cmp = enumb[show];
return (
<div>
<h1>Hello StackBlitz!</h1>
<Cmp />
{show}
</div>
);
}
I'm trying to create a Quiz component rendering one Question at time and changing it when the user chooses one of the alternatives.
However, every time it renders the next Question it has already the chosenOption variable set from the previous Question. This happens because before I change the Question, I set the new state of the current Question with that chosenOption and strangely(to me) this is already set when the next Question component is rendered.
For me, the setChosenOption would set only for the current Question and when the Quiz renders the next Question its chosenOption would be null initially. I may be missing something from how functional components render... So why is it happening?
Thanks in advance!
const Quiz = () => {
const [currentQuestion, setCurrentQuestion] = React.useState(0)
const [answers, updateAnswers] = React.useState({})
const numberOfQuestions = questions.length
const onChoosenOptionCallbackHandler = ({ hasChosenCorrectOption, chosenOption}) => {
updateAnswers(prevAnswers => ({...prevAnswers, [currentQuestion]: hasChosenCorrectOption }))
setCurrentQuestion(currentQuestion + 1)
}
return (
<QuizBackground>
<QuizContainer>
<Question
question={questions[currentQuestion]}
index={currentQuestion}
numberOfQuestions={numberOfQuestions}
onChoosenOptionCallback={onChoosenOptionCallbackHandler}
/>
</QuizContainer>
</QuizBackground>
)
}
Here, apart from the first Question, the 'Chosen Option: ' log always show the chosenOption from the previous Question rendered and not null.
const Question = ({ question, index, numberOfQuestions, onChoosenOptionCallback }) => {
const [chosenOption, setChosenOption] = React.useState(null)
console.log('Chosen Option: ', chosenOption)
const hasChosenCorrectOption = chosenOption !== null ? (chosenOption == answer) : false
const selectOption = (optionIndex) => {
setChosenOption(optionIndex)
console.log('SELECTED: ', optionIndex, hasChosenCorrectOption, chosenOption)
onChoosenOptionCallback({ hasChosenCorrectOption, optionIndex })
}
return (
{/* I removed other parts not relevant. The RadioOption goes inside a map() from the question alternatives */}
<RadioOption
questionName={questionName}
option={option}
chosen={chosenOption === index}
onSelect={() => selectOption(index)}
key={index}
/>
)
}
Your issue is a result of not assigning keys to your Question components, that are being rendered using a map function.
The omission of proper keys (i.e. a unique property of each element in the rendered array) results in all sorts of weird behaviours, such as what you were describing.
The reason for that is that React uses these indices to optimize, by re-rendering only the components whose props were changed. Without the keys the whole process isn't working properly.