I'm a newbie on frontend development and learning React. Now I'm trying to build a hello-world project.
After executing npx create-react-app myapp, I got an initial React project and I just coded in the file App.js.
import React, {useState} from 'react';
var counter = 0;
function App() {
const [counter2, setCount] = useState(0);
const increment = () => {
setCount(counter2 + 1);
};
return(
<div>
<button onClick= {increment}>Increment</button>
<h1>{counter++}</h1> // 1, 3, 5, 7... WHY???
<h1>{counter2}</h1> // 0, 1, 2, 3...
</div>
);
}
export default App;
After executing npm start, I got my index page, which contains three parts: a button and two numbers.
To my surprise, when I click the button, counter2 is increased as expected, but counter is increased twice. Meaning that keeping clicking the button gives me the result as below:
1 0, 3 1, 5 2...
Why is the global variable counter increased two by two, instead of one by one?
Furthermore, what is the difference between React State and common global variable?
When you wrap a component in <React.StrictMode>, it will run certain functions twice, one of these being the function body of your functional component:
This is done by intentionally double-invoking the following functions:
... Function component bodies
- React docs
This is only done in dev mode, and the idea behind doing it is to help you catch side-effects within your project.
It may seem as though your component is only being executed once though, as putting a console.log() inside of your functional component will only run once per state change. This is because, as of React 17, they have updated the console.log method to not log on the second invocation of your function:
Starting with React 17, React automatically modifies the console
methods like console.log() to silence the logs in the second call to
lifecycle functions
- React docs
However, there is a workaround to this by saving a reference to the console.log method, and using that to perform your logs. Doing this will allow you to see that your component is being executed twice:
const log = console.log;
function App() {
const [counter2, setCount] = useState(0);
const increment = () => {
setCount(counter2 + 1);
};
log("Rendering, counter is:", counter);
return(
<div>
<button onClick= {increment}>Increment</button>
<h1>{counter++}</h1>
<h1>{counter2}</h1>
</div>
);
}
The above will output the following when the component mounts, showing that the function body is running twice:
Rendering, counter is: 0
Rendering, counter is: 1
If you remove the <React.StrictMode> component then counter will increase by one each render, as React will no longer double-invoke your functional component body, and your component body will only be called once:
ReactDOM.render(<App />, document.getElementById('root'));
In terms of global variables vs state, the main difference has been pointed out in a comment above. That is that when you update your state with setMethodName(), you'll cause your component body to rerender, which doesn't happen when you update a normal variable as React won't be aware of the changes made to it.
Related
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() { ... }
I test some code and make me confused
There are two reproduction,
use setState function set State to same previous state value
const [state, setState] = useState(1)
setState(() => 1)
use setState function set State to same value(always same ex: 1, true, something else), but different initial state
const [state, setState] = useState(1)
setState(() => 2)
This is reproduction sample 1
always set state to same value
open codesandbox console first and clear it first.
what happened
click, state is already 1, setState function set state to 1,
component function does not re-run
// console show
test
test
test
This is reproduction sample 2
set state to certain value
I always set state to 2.
First click, state is change from 1 to 2, so component function re-run
// console
test
app
Second click, state already is 2, but why component function still re-run
// console
test
app <-------------- here component function re-run
Third click, state already is 2 , component function does not re-run
// console
test
The Problem is Here
In Sample 2, second click button, state is same as
previous, but is still re-run component.
And we go back to see Sample 1 and third click in Sample 2, these two state change step is same as Sample 2 second click, they are all same set same state compare to previous state, but how they output is different
As I know, state change will cause component function re-run.
What I expect is Sample 2 second click, component function is not re-run
Let's focus only on Simple 2 as Simple 1 flow is what a developer would expect.
Simple 2 logs are as follow:
1. app // first render
2. test // button click, log from the callback
3. app // state change from '1' to '2', second render
4. test // second button click
5. app // no state change, why the log?
6. test // 3rd button click
7. test // 4th button click etc.
So the main question is why there is a 5th log of 'app'.
The reasoning behind it hides somewhere in React docs under Bailing out of a state update:
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” into the tree.
In simple words, its just an edge case where React needs another render cycle but it doesn't goes into the Nodes tree (i.e it doesn't run the return statement, in your example it's a React.Fragment <></>)
A more concrete example, notice the addition log of "A":
const App = () => {
const [state, setState] = React.useState(0);
useEffect(() => {
console.log("B");
});
console.log("A");
return (
<>
<h1>{state}</h1>
<button onClick={() => setState(42)}>Click</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
To be honest, I'm struggling to come up with a way to phrase this question besides "What is happening here?" Take the following React code designed to add incremental items to a list:
import React, { useState } from "react";
import "./styles.css";
let counter = 0;
export default function App() {
const [list, setList] = useState([]);
console.info("Render:", counter, list.join());
return (
<div className="App">
{list.join()}
<button
onClick={() => {
setList((prevList) => {
console.info("Pre-push:", counter, prevList.join());
const newList = [...prevList, "X" + ++counter];
console.info("Post-push:", counter, newList.join());
return newList;
});
}}
>
Push
</button>
</div>
);
}
If you run that code with https://codesandbox.io/s/amazing-sea-6ww68?file=/src/App.js and click the "Push" button four times, I would expect to see "X1" then "X1,X2" then "X1,X2,X3", then "X1,X2,X3,X4". Simple enough right? Instead, it renders "X1" then "X1,X3" then "X1,X3,X5" then "X1,X3,X5,X7".
Now I thought, "huh, perhaps the function that increments counter is being called twice?", so I added the console logging you see, which only mystified me more. In the console, I see:
Render: 0 ""
Pre-push: 0 ""
Post-push: 1 X1
Render: 1 X1
Pre-push: 1 X1
Post-push: 2 X1,X2
Render: 2 X1,X2
Pre-push: 3 X1,X3
Post-push: 4 X1,X3,X4
Render: 4 X1,X3,X4
Pre-push: 5 X1,X3,X5
Post-push: 6 X1,X3,X5,X6
Render: 6 X1,X3,X5,X6
Note that the joined list in the console doesn't match the joined list rendered by React, there is no record of how counter gets bumped from 2 -> 3 and 4 -> 5, and the third item of the list mysteriously changes, despite the fact that I only ever append to the list.
Notably, if I move the ++counter out of the setList delegate, it works as expected:
import React, { useState } from "react";
import "./styles.css";
let counter = 0;
export default function App() {
const [list, setList] = useState([]);
console.info("Render:", counter, list.join());
return (
<div className="App">
{list.join()}
<button
onClick={() => {
++counter;
setList((prevList) => {
console.info("Pre-push:", counter, prevList.join());
const newList = [...prevList, "X" + counter];
console.info("Post-push:", counter, newList.join());
return newList;
});
}}
>
Push
</button>
</div>
);
}
What on earth is going on here? I suspect this is related to the internal implementation of React fibers and useState, but I'm still at a total lost to how counter could be incremented without the console logs right before it and after it showing evidence of such, unless React is actually overwriting console so that it can selectively suppress logs, which seems like madness...
It seems like it's getting invoked twice because it is.
When running in strict mode, React intentionally invokes the following methods twice when running in development mode:
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
Not sure what happens to the console.log calls, but I bet this problem goes away if you switch to production mode.
I am simply trying to change the heading and content states using React Hooks but I get a number shown on the page, a little google search showed up a bunch of stuff related to how setInterval and Timeout generate a key or value but I have no idea why they're showing up on the page.I can hide it using an empty div but I am curious if I am doing anything wrong, also if I use a class instead of a function the value rapidly increases and my CPU maxes out.
function MyComponent (){
const [heading, setHeading] = useState('React(Loading)')
const [content, setContent] = useState('Loading...')
return(
<main>
<h1>{heading}</h1>
<p>{content}</p>
{
setTimeout(() =>{
setHeading('React(Loaded)')
setContent('Loaded')
}, 2000)} // should change the values, which it does with addition of a number
</main>
);
}
The resulting page is that renders is here
Also on a side note I tried using a onload function to do the same thing but nothing happens.
setTimeout returns a number, which is used to identify the timeout when you use clearTimeout. That is why you see the number below the content.
To hide the number, you should move the setTimeout to be outside of the return function. Also, you should use as little JS as possible in the return statement and just use JSX over there, to make the component more clear and readable.
But just moving the setTimeout to be before the return statement is not enough. The function will run on every render, and there are many things that can trigger a re-render - a state change, or a parent re-rendering. So on every re-render, you will set a new timeout. The timeout itself updates a state which triggers a render which triggers the setTimeout - so you are creating an infinite loop.
So you want to call setTimeout only once - you can use useEffect, which will re-run only when the dependency array changes, but if you will leave it empty, it will run only once, because nothing will change and a re-run will never be triggered.
function MyComponent (){
const [heading, setHeading] = useState('React(Loading)')
const [content, setContent] = useState('Loading...')
useEffect((
setTimeout(() =>{
setHeading('React(Loaded)')
setContent('Loaded')
}, 2000)
), []);
return(
<main>
<h1>{heading}</h1>
<p>{content}</p>
</main>
);
}
So, by using the above answer we get the following error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function
The reason for this is again the fact that setTimeout returns a number, the final answer is to use the code as a separate function as below:
useEffect( timeOutFunction, [])
function timeOutFunction() {
setTimeout(() => {
setHeading('React(Loaded)')
setContent('Loaded'), 2000)
}
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?