Is a component always re-rendered in the React reconciliation process? - reactjs

I'm trying to understand how React "displays and update" the code below, assuming I've understood the differences and the vocabulary explained here
https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html
import React from "react";
export default function App() {
console.log("App is rerendered")
const [time, setTime] = React.useState(0)
React.useEffect(() => {
const lol = setTimeout(() => setTime(prev => prev + 1), 100)
return () => clearTimeout(lol)
}, [time]
)
function ShowTime() {
console.log("ShowTime is rerended")
return (
<div> Time : {time / 10} sec</div>
)
}
function ShowButton() {
console.log("ShowButton is rerended")
return (
<button
onClick={() => console.log("I'm hard to click cuz rerendered the whole time :/")}>
Button created with ShowButton component
</button>
)
}
return (
<main>
<ShowTime />
<ShowButton />
</main>
)
}
React create the virtual dom with the App element, the ShowTime element, and the ShowButton element inside
It's the first render so React renders everything, creating an instance of App, containing a main DOM element, containing one instance of ShowTime and one instance of ShowButton
After 100ms, time state in App changed !
React update the virtual dom again taking account time state has changed
It's rerendering, so there is reconciliation
https://reactjs.org/docs/reconciliation.html#component-elements-of-the-same-type
says "When a component updates, the instance stays the same (...). Next, the render() method is called (...)"
React does't care if App changed or not. It's a component, and when he encounters a component in the virtual dom, when commiting, the instance stays the same, and React runs App.render()
In this case it's nice, because time state has changed.
Recursing process of reconciliation on children
In the same way, React does't care if ShowTime and ShowButton changed or not. They're components, so React keeps their instance, runs ShowTime.render() and ShowButton.render()
My two questions :
Is my understanding of the reconciliation process (concerning the components part) is right ?
So a component inside a component that has to be rendered will be rendered, whatever if it is concerned about any props or state changes or not ? (it's the case of my ShowButton component)
That's weird no ? Because of that it's very hard to click it !

The declaration of ShowButton is right inside the App render function. So React not only rerender it but inserts a new DOM button element 10 times a second. That's why it is hard to click. Move ShowButton out of App.
Reconciliation happens after all rendering.
In some cases React doesn't rerender components. We use React.memo, PureComponent and shouldComponentUpdate for the optimization. For more information please read this answer.
There are ShowTime.render() and ShowButton.render() in your text but functional components doesn't have methods.

In the same way, React does't care if ShowTime and ShowButton changed or not. They're components, so React keeps their instance, runs ShowTime.render() and ShowButton.render()
React doesn't care, as long as it's the same component. The problem is that ShowTime and ShowButton are a new component with the same name, created every time App is rerendered. It's like saying
const ShowTime = () => {
// ... (btw, don't do this either)
}
So while the ultimate structure is the same, React sees new components every rerender of App.
To solve this problem, pull the components out of App:
function ShowTime() { ... }
function ShowButton() { ... }
function App() { ... }

Related

remix run - how to reload all child components of a component loaded through an <Outlet/>

I have a component that gets loaded through it's parent's .
This component renders its own components - ActionsBar and Collection.
When the URL changes and the Outlet gets rerendered - my assumption would be that everything within the comonent would get rerendered, however it appears to only rerender the child components if they have changed.
export default function ComponentLoadedInOutlet() {
const collectionData = useLoaderData();
return (
<div>
<ActionsBar />
<Collection data={collectionData} />
</div>
)
}
In the above snippet, when the outlet gets reloaded when the URL changes, it is only the Collection component that is being re-rendered because the collectionData has changed.
I would like to know if there's a way to force all the Child components within the outlet to reload, so that I don't have to mess around with resetting lots of state.
Your assumption is correct, everything within the component is re-rendered when the url changes. However there is a difference between re-rendering and re-mounting.
When navigating from one url to another which routes to the same component (i.e. a $dynamic.tsx segment), React will merely re-render the component, rather than unmount / mount the component.
This can be useful if you want to preserve state, however this isn't always desirable.
It's therefore common in React to add a key prop to reset a component hierarchy.
// /products/$slug.tsx
export const loader = ({ request, params }: LoaderArgs) =>
json({ slug: params.slug });
export default function ProductDetail() {
const { slug } = useLoaderData<typeof loader>();
return (
// NOTE: `key={slug}`
<div key={slug}>
<ActionsBar />
</div>
);
}
const ActionsBar = () => {
useEffect(() => {
// This will re-mount whenever the `slug` changes
alert("<ActionBar /> mounted");
}, []);
return <div>ActionsBar</div>;
};
This will tell React to avoid diffing the new tree with the old tree and instead just blow away the old one, creating a brand new tree & therefore mount cycle.

Reactjs: don't let Modal trigger a re-render

In my Reactjs project I have a component that contains a Modal that has its own states and when 1 (or more) of these states change, they trigger a re-render of that component:
import React from "react";
import CustomModalComponent from "./CustomModalComponent";
const MainComponent = () => {
const [isModalOpen,setIsModalOpen] = React.useState(false);
console.log("main component");
return(
<React.Fragment>
<section>Some UI here that can also turn modal state to true</section>
<CustomModalComponent open={isModalOpen} toggleOpen={() => setIsModalOpen(!isModalOpen)} />
</React.Fragment>
);
}
export default MainComponent;
As I said whenever a state changes inside that custom modal component, it triggers a re-render in my main component which is due to the fact that I have a state that changes, but I was wondering if there is a way to change this "behavior" since if my main component is a big one, re-renders will take away from the performance.
You don't need to worry about rerenders until they are a problem. It is a common beginner's error to optimize for reducing rerenders. Sadly, a lot of false material exists online around this that suggests you need to be thinking about this from day 1. Rerenders are usually extremely cheap and simply don't matter.
Keep in mind a "rerender" doesn't mean it loads all the HTML up again in the DOM. Internally React diffs over the state, meaning it only makes small edits when the state changes.

How does react know which components have been updated?

import React from 'react';
const App = () => {
console.log('app render');
return (
<div>
<Children />
</div>
);
};
const Children = () => {
const [update, setUpdate] = React.useState(false);
console.log('children render');
return (
<div>
<button onClick={() => setUpdate(!update)}>update</button>
</div>
);
};
export default App;
Above code, at first, two messages are printed out 'app render' and 'children render.'
Then, when I click the update button, a message is printed 'children render'.
I learned the following rules when I started to learn React at beginning.
when props are updated
when states are updated
when the parent component is updated
I've been working on React Projects well since then.
Today, somehow, I got a question.
How does react know which components have been updated?
setUpdate tells React that Children is updated?
I think I can understand that React can notice the props changing.
because, if render functions are called, it means they can know whether props of their children are changed or not.
But I don't understand how react recognizes state-changing.
The components form a tree. In the first render, the React framework renders from the root node. Any component functions that call useState will have the set state function registered and associated with the component instance. In the future, if you call a set state function, the React framework finds the associated component instance to re-render the sub-tree.

How to pass callback as a prop to lower components in React without creating a new callback each time?

I am coming form angular and getting used to react. I was reading the docs on event handling and stumbled upon this:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
the docs then say:
"
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem."
I don't have access to constructor as I am using hooks. What should I do?
I prepared a quick demo. The structure is App > [CompA > Comp B]. There is a button in component B that increments count and the state of the count is kept in App. Through props the callback is delegated from App through Comp A to comp B. Whenever I click the count button in Comp B, it re-renders the entire Comp A. This can be verified by the random number generation. It's fine for this component but wouldn't this be a problem in a large project?
Now imagine I have a quiz app and I keep score in the main App. If someone selects right answer inside a Question component I would like to keep a count of that in the main App. Doing it this way would re-render any intermediate components. What's the pattern I should follow?
Use the useCallback hook. It'll return the same function as long as its dependencies remain the same.
const callback = useCallback(()=> { /* do something */ }, [/* dependencies */])
The dependencies work the same as the useEffect hook.
Edit
From your demo, <CompA onCount={() => setCount(count + 1)} /> is perfectly fine, Dan Abramov said as much.
Parent re-renders will render their children, so if the count state is in the parent, the children will be re-rendered. That's perfectly fine. React is built to do that very quickly.

Component unmounts when parent state changes

I'm using React 16.8.2, and I'm having a problem with children of my component unmounting whenever state is changed in the app component.
Here's the scenario:
I have App.jsx (a functional component) with a number of state variables (useState)
The setters for some of these state variables are passed down the tree through a Context provider (useContext in the descendent)
I have a menu component (descendent of app), that invokes these setters to (for example) show a modal dialog
I have a modal dialog component (child of App), that uses the state variable as a property to determine whether it is open or not -- standard React stuff I think.
My problem: when any state variables in App are changed (through hooks of course), the children of App are unmounted and remounted- even if they have no connection to the state being changed. They aren't just re-rendered - the children are unmounted and their state is re-initialized. So the fields are cleared on my dialog, when they shouldn't be, for example.
This is already a fairly complex application, so I've spent a lot of time today isolating the problem. I then set up a simple create-react-app to try to replicate this behavior there - but this test app behaves like it should. Changing parent state, whether through a prop callback, or through a context-provided callback from the child - re-renders but does not unmount/remount and child state remains intact.
But in my real app, the components re-mount and child state gets re-initialized.
I've simplified it down to the barest that I can - I'm setting a fake state variable "foo" with "setFoo" through the Context from the child. Even though foo is not used by any component, changing the value of foo causes the children of App to unmount/remount.
In App.jsx:
const App = props => {
const [foo, setFoo] = useState(false);
// ...
const appControl = {
toggleFoo: () => setFoo(!foo);
};
// ...
return (
<AppContext.Provider value={appControl}>
... a bunch of stuff not using foo anywhere
... including, deep down:
<Menu />
</AppContext.Provider>
);
};
In Menu.jsx:
const Menu = props => {
const appControl = useContext(AppContext);
// ...
return (
... super simplified for test
<div onClick={appControl.toggleFoo}>
Toggle Foo
</div>
);
};
If I understand state properly, I do believe that changing state should result in children being re-rendered, but not re-mounted. This is what I'm seeing in my simple create-react-app test, but not in my real app.
I do see that I'm not on the latest React - perhaps upgrading will fix this?
Thanks for any insight on what I may be doing wrong, or misunderstanding here.
Solved. This is an interesting one. Here's what happened.
In my App component, I had a fairly deep tree of HOC's. Due to some dubious decisions on my part, I ended up breaking App into two components. App and AppCore. I had a reason for it, and it seemed to make sense at 3am. But to be both quick and dirty, I stuck AppCore as a const, inside my App function. I remember thinking to myself "I wonder what problems this will cause?" Now I know. Perhaps a React expert can fully explain this one to me though, as I don't see the difference between JSX assigned to a constant, and JSX returned directly. But there clearly is, and this is simple to reproduce.
To reproduce, create-react-app a test app:
create-react-app test
cd test
Then replace the contents of App.js with:
import React, { useState, useEffect } from "react";
const Menu = props => <div onClick={props.click}>Toggle Foo</div>;
const Test = props => {
useEffect(() => {
console.log("mounted");
return () => console.log("unmounted");
}, []);
return null;
};
const App = props => {
const [foo, setFoo] = useState(false);
// this is the root of the problem
// move this line outside of the function body
// and it mounts/unmounts correctly
const AppCore = props => <Test />;
return (
<>
<Menu click={() => setFoo(!foo)} />
<AppCore />
</>
);
};
export default App;
Then npm start, and when you click on "Toggle Foo" you'll see that the Test component is unmounted/remounted.
The solution here, is to simply move AppCore out of the function body. In my real app, this means I have some refactoring to do.
I wonder if this would be considered a React issue?

Resources