Why setting state to a constant triggers rerender two times? - reactjs

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

Related

why components state data are gone in conditional rendering

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.

React useState handler called TWICE

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?

Can react hooks be used inside JSX?

I've recently come across the following code:
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import UserContext from 'UserContext';
const useUserName = () => {
const context = useContext(UserContext);
return context.userName;
}
function App() {
return (
<div className="App">
{useUserName()}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useUserName returns user name as a string. It seems instinctively strange and wrong that react hook useUserName is used inside JSX that is inside the "render" function. Is this valid usage? I couldn't find any reference which forbids such usage.
It seems instinctively strange and wrong that react hook useUserName is used inside JSX that is inside the "render" function.
The entire component is nothing but a render() function.
Is this valid usage?
What's the difference between that and
function App() {
const userName = useUserName();
return (
<div className="App">
{userName}
</div>
);
}

Is there a way to force multiple Context Consumers to share state?

I'm using the React Context API with the main intent of avoiding prop drilling. Right now my Context includes a useState and various functions that update the state - these are put into a const object that is passed as the value prop of ActionsContext.Provider. This is an abstraction of my current component hierarchy:
Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)
where Header and ContentContainer are sibling elements and NavPanel and ContentContainer are their respective children.
I initially put the Context.Consumer in Content because the other elements did not need it. However I'm building a feature now where NavPanel needs to know about the state that's managed by the Context. So I put another Consumer in NavPanel, only to find that a separate Consumer means a separate instance of the state.
Is there any smart workaround that gives NavPanel and Content access to the same state, that doesn't involve putting the Consumer in the parent component of Header and Content? That would result in a lot of prop drilling with the way my app is currently structured.
Codesandbox example of multiple instances: https://codesandbox.io/s/context-multiple-consumers-v2wte
Several things:
You should have only one provider for every state you want to share.
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
It is better to split your context in several contexts so you pass values instead of objects. This way when you update your state React will detect it is different instead of comparing the same object.
Your input should be a controlled component https://reactjs.org/docs/forms.html
Consider using the useContext API for better ergonomics if you are using React 16.8 instead of ContextConsumer.
With these changes, your code would be:
MyContext.js
import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();
const ContextProvider = props => {
const [itemBeingEdited, setItemBeingEdited] = useState("");
const handleKey = event => {
if (event.key === "Enter") {
setItemBeingEdited("skittles");
} else if (event.key === "K") {
setItemBeingEdited("kilimanjaro");
} else {
setItemBeingEdited("");
}
};
const editFunctions = {
itemBeingEdited,
setItemBeingEdited,
handleKey
};
return (
<MyItemContext.Provider value={itemBeingEdited}>
<MyHandleKeyContext.Provider value={handleKey}>
<MySetItemContext.Provider value={setItemBeingEdited}>
{props.children}
</MySetItemContext.Provider>
</MyHandleKeyContext.Provider>
</MyItemContext.Provider>
);
};
export default ContextProvider;
PartOne.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
placeholder="Type in me"
onKeyDown={handleKey}
value={itemBeingEdited}
onChange={e => setItem(e.target.value)}
/>
</React.Fragment>
);
};
export default PartOne;
PartTwo.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
value={itemBeingEdited}
type="text"
placeholder="Type in me"
onChange={e => setItem(e.target.value)}
onKeyDown={handleKey}
/>
</React.Fragment>
);
};
export default PartTwo;
index.js
import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";
function App() {
return (
<div className="App">
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14

How to share stateSetter functions using the useContext hook?

I have the following code high up in the component hierarchy:
import VisibilityContext from '../visibilityContext';
...
const [showEditModal, setEditModalVisibility] = useState(false);
...
<VisibilityContext.Provider value={{setEditModalVisibility}}>
<EditModal/>
</VisibilityContext.Provider>
And within the EditModal I have this piece of code:
import VisibilityContext from '../visibilityContext';
...
const {setEditModalVisibility} = useContext(VisibilityContext);
But the function setEditModalVisibility is empty when I console it out in the EditModal component. If I pass showEditModal instead of setEditModalVisibility, useContext gives me its correct value. I even tried putting setEditModalVisibility in the state using another useState, and passed it down, in case useContext required references to be stored in a state.
I just want components down the tree to be able to call the setEditModalVisibility function. And I want to be able to share this function without having to pass it down the tree as a prop.
Here is a sample code how you can effectively manage your state using Context.
import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const VisibilityContext = createContext();
const Provider = props => {
const [visible, setVisible] = useState(false);
const value = { state: { visible }, actions: { setVisible } };
return (
<VisibilityContext.Provider value={value}>
{props.children}
</VisibilityContext.Provider>
);
};
function App() {
const { state, actions } = useContext(VisibilityContext);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => actions.setVisible(!state.visible)}>
{state.visible ? "ON" : "OFF"}
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider>
<App />
</Provider>,
rootElement
);

Resources