Having such a simple React App:
import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
console.log("App")
const [inputValue, setInputValue] = useState(0);
return (
<>
<input
type="text"
onChange={(e) => setInputValue("e.target.value")}
/>
<button onClick={(e) => setInputValue(1)}>
Click me
</button>
<h1>Render Count: {inputValue}</h1>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I'm getting initially (after the App loads):
App
AND
App
App
on interaction - when I click the button or type sth. in the input field.
My question is WHY does the App is printed TWICE in the second case (interaction)!? It's strange because the inputValue is ONLY changed ONCE on the first click/input but in the second interaction it stays the same, not changing anymore!
P.S.
It's NOT the case of the react strict mode! - In the index.js I have:
...
ReactDOM.render(<App />, rootElement);
...
so I'm NOT using react strict mode!
When the component mounts, there is always one render.
Then when the state changes from 0 -> 1 there is another render.
When using the useState hook, it re-renders whenever a state value changes, not necessarily every time you call setState (which was the old behaviour). So every time you click after the first time, the state goes from 1 -> 1 and you get no re render because the value is the same.
React setState causes re-render.
You can learn more about this behavior here: How does React.useState triggers re-render?
Related
I am new to react and come from a background of functional component only.
In my react project,
When I conditionally rendering , ie from false to true, the data inside child component will be gone.
Then I wonder why is that.
Then I heard a concept called unmounting. It means, when my condition change from true to false, the component will get unmounting. And in unmounting, the state inside will gone.
But then, it doesn't add up.
Q: Whenever we re-render any other components, just like the normal situation, we will also unmount component in order to do re-rendering. And our state value would not be gone.
Why this problem was happened especially on having conditional statement in react?
Edit:
My emphsis is not on how to avoid state loss. My question is that why data will be gone in conditional rendering. And why unmounts will cause such problem, but re rendering would not cause such ( both also involves unmounting)
Here is my code
In parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 />
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
In child:
import React, { useEffect, useState } from "react";
function Child1() {
useEffect(() => {
console.log("useeffect in child");
console.log("newMessage: " + newMessage);
});
const [newMessage, setNewMessage] = useState("");
return (
<div>
<input onChange={(event) => setNewMessage(event.target.value)} />
</div>
);
}
export default Child1;
Add some picture to illurste what I mean by data lose in conidtional rendering
enter
https://i.stack.imgur.com/UrIhT.png
click to not show it
https://i.stack.imgur.com/0OC87.png
click to show again
https://i.stack.imgur.com/4zlWk.png
Try moving all state management to the parent component and leave the child component 'dumb'. You can pass the setMessage and any other state variables to the child as props.
Parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 setMessage={setMessage}/>
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Child:
import React from "react";
function Child1({setMessage}) {
return (
<div>
<input onChange={(event) => setMessage(event.target.value)} />
</div>
);
}
export default Child1;
The answer for your question is very simple, While unmounting you are removing the component itself from react-dom. The state, props and all the data's handled inside your component will be active only If the component is inside the react-dom. If the particular component is unmounted, all the states and props that was created and processed will also be removed from react-dom along with the component. And the fresh component, state and props will be mounted in the react-dom if you conditionally render the component again.
I have been using react-modal recently, and the pattern to show a modal is something like:
const [modalIsOpen, setModalIsOpen] = useState(false);
return (<ModalDialog isOpen={modalIsOpen}><div>some content</div></ModalDialog>)
...which feels very React-y, but overly verbose. Is there a way to enable something where I can - within a click handler - simply call:
showModal(MyComponent)
and have the state be handled automatically? I've thought about creating a Context around the app that allows showModal to grab state associated with MyComponent, in which case this would itself be a hook, so it would have to follow certain rules (i.e., no conditional calling), so I'm not sure I could make it work as cleanly as I'd like.
Is there a way to do this within the React ecosystem? Or should I just give up and use the existing mechanism?
Yes! You can do it by using the "render" function.
Unfortunately it doesn't work in CodeSandBox, but you can fork it from here:
https://github.com/BHVampire/modal
You close the modal by clicking outside.
This is how I did it:
At the end you'll be able to open a modal like this from any part of your application:
createModal(<h1>Success</h1>)
First, you add a container for your modals outside the App component, so you'll never have problems with the z-index:
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.scss'
import App from './App.jsx'
ReactDOM.render(
<React.StrictMode>
<div id="modal-container"></div> <- This is the container
<App />
</React.StrictMode>,
document.getElementById('root')
)
app.jsx
import createModal from "./components/Modal"
const App = () => {
return <button onClick={() => createModal(<h1>Success</h1>)}>Open Modal</button>
}
export default App
Then I have 2 main files for the modal inside a Modal folder + the style.
Modal/index.js
The state hook is unavoidable for the modal, but you'll never see it again.
import { Fragment, useState } from 'react'
import './Modal.scss'
const Modal = ({ content }) => {
const [isOpen, setIsOpen] = useState(true)
return isOpen
? <Fragment>
<div onClick={() => setIsOpen(false)} className="modal-background" />
<div className="modal" >
{content}
</div>
</Fragment>
: ''
}
export default Modal
I have created a component, App, based on reactjs's example of the state hook useState. Here is my code:
import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
console.log("before");
const [count, setCount] = useState(0);
console.log("after");
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count+1)}>
Click me
</button>
</div>
);
}
export default App;
When I run this using node, App renders a button on http://localhost:3000/:
Based on the explanation accompanying the reactjs example (link above), I would expect that clicking the button would cause App to be re-rendered one time. However, clicking (once) actually causes the re-render to occur twice:
Why does this example lead to the re-render occurring twice?
How can I correct this so that does not occur more than necessary?
Note: With each click, two new lines of each 'before' and 'after' are printed to the console, so the double-rendering appears to occur with each click, not due to the initial render.
What you're seeing is the first 'before' and 'after' is from the initial render and the second pair is from when you click the button and state is updated.
It seems the offending code comes from <React.StrictMode>
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);
Removing the <React.StrictMode> gets rid of this issue.
ReactDOM.render(<App />, document.getElementById('root'));
Further Reading: React Components rendered twice — any way to fix this?
I made a simple components that number state 'a' is increased when the button is clicked.
and I wrote console.log() inside component to check when it is rendered. I expected the console.log is executed once when the button is clicked because component's state is changed.
But, I was wrong and console.log() is executed twice.
Is something wrong? or Is it correct? What i missed?
here is my code
A.jsx
import React, {useState} from 'react';
const A = () => {
const [a, setA] = useState(0);
const onClick = () => setA(a + 1);
console.log('render')
return (
<div>
<p>a : { a}</p>
<button onClick = {onClick}>button</button>
</div>
)
}
export default A;
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import A from './components/A';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<A />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
This app is created by CRA with typescript .
That's all.
Thanks.
****PLUS******
I checked React dev tools Profiler to check the component is really rendered twice when a button is clicked and state is changed. It show me result below
I think there was only one render. If the component was really rendered once, why the console.log exected twice?
I think this is because of React.StrictMode this only happens in development. If you remove React.StrictMode you will get only 1 log.
For more details, check this thread on react repo:
https://github.com/facebook/react/issues/15074
On reading further I found this on React docs as well: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
Hope this helps!
Demonstration:
https://codesandbox.io/s/2zkxyk31oy
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("render");
let [val, setVal] = useState(0);
return <button onClick={() => setVal(1)}>go</button>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
What I expect to see is two render calls: the initial one, and the one caused by the state change.
The third setState call should have no effect, since react bails out of rendering if the state hasn't changed. https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
What exactly is happening?
The doc link you provided states:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper”
It appears the behaviour is consistent with what the doc says. In order to test that, you can check if child components are being rendered. Something like this:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("render");
let [val, setVal] = useState(0);
return <button onClick={() => setVal(1)}><Child/></button>;
}
function Child() {
console.log('render the child')
return <span>go</span>
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The result in the console now is:
render
render the child
render
render the child
render