React Hooks give us useState option, and I always see Hooks vs Class-State comparisons. But what about Hooks and some regular variables?
For example,
function Foo() {
let a = 0;
a = 1;
return <div>{a}</div>;
}
I didn't use Hooks, and it will give me the same results as:
function Foo() {
const [a, setA] = useState(0);
if (a != 1) setA(1); // to avoid infinite-loop
return <div>{a}</div>;
}
So what is the diffrence? Using Hooks even more complex for that case...So why start using it?
The reason is if you useState it re-renders the view. Variables by themselves only change bits in memory and the state of your app can get out of sync with the view.
Compare this examples:
function Foo() {
const [a, setA] = useState(0);
return <div onClick={() => setA(a + 1)}>{a}</div>;
}
function Foo() {
let a = 0;
return <div onClick={() => a = a + 1}>{a}</div>;
}
In both cases a changes on click but only when using useState the view correctly shows a's current value.
Local variables will get reset every render upon mutation whereas state will update:
function App() {
let a = 0; // reset to 0 on render/re-render
const [b, setB] = useState(0);
return (
<div className="App">
<div>
{a}
<button onClick={() => a++}>local variable a++</button>
</div>
<div>
{b}
<button onClick={() => setB(prevB => prevB + 1)}>
state variable b++
</button>
</div>
</div>
);
}
function Foo() {
const [a, setA] = useState(0);
if (a != 1) setA(1); // to avoid infinite-loop
return <div>{a}</div>;
}
is equivalent to
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 0
};
}
// ...
}
What useState returns are two things:
new state variable
setter for that variable
if you call setA(1) you would call this.setState({ a: 1 }) and trigger a re-render.
Your first example only works because the data essentially never changes. The enter point of using setState is to rerender your entire component when the state hanges. So if your example required some sort of state change or management you will quickly realize change values will be necessary and to do update the view with the variable value, you will need the state and rerendering.
Updating state will make the component to re-render again, but local values are not.
In your case, you rendered that value in your component.
That means, when the value is changed, the component should be re-rendered to show the updated value.
So it will be better to use useState than normal local value.
function Foo() {
let a = 0;
a = 1; // there will be no re-render.
return <div>{a}</div>;
}
function Foo() {
const [a, setA] = useState(0);
if (a != 1) setA(1); // re-render required
return <div>{a}</div>;
}
It is perfectly acceptable to use standard variables. One thing I don't see mentioned in other answers is that if those variables use state-variables, their value will seemingly update on a re-render event.
Consider:
import {useState} from 'react';
function NameForm() {
// State-managed Variables
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// State-derived Variables
const fullName = `${firstName} ${lastName}`;
return (
<input value={firstName} onChange={e => setFirstName(e.target.value)} />
<input value={lastName} onChange={e => setLastName(e.target.value)} />
{fullName}
);
}
/*
Description:
This component displays three items:
- (2) inputs for _firstName_ and _lastName_
- (1) string of their concatenated values (i.e. _lastName_)
If either input is changed, the string is also changed.
*/
Updating firstName or lastName sets the state and causes a re-render. As part of that process fullName is assigned the new value of firstName or lastName. There is no reason to place fullName in a state variable.
In this case it is considered poor design to have a setFullName state-setter because updating the firstName or lastName would cause a re-render and then updating fullName would cause another re-render with no perceived change of value.
In other cases, where the view is not dependent on the variable, it is encouraged to use local variables; for instance when formatting props values or looping; regardless if whether the value is displayed.
Related
I am setting initial values on a these variables. I change the values, and they do log correctly, however in the return function, they only display the original values. How do I make sure they show the correct updated values?
I declare my variables:
let teamBillableData = [];
let teamMemberBillableAmount = 0;
let teamMemberIndex = 0;
Then I change the values:
teamBillableData = parsedData.results;
teamMemberIndex = teamBillableData.findIndex(function (teamMember) {
return teamMember.user_name === teamMemberName;
})
teamMemberBillableAmount = teamBillableData[teamMemberIndex].billable_amount;
When I log the variables, they are correct:
console.log(teamMemberIndex); <---- Returns correct new value of 1
console.log(teamMemberBillableAmount); <---- Returns correct new value of 1,221.25
However, when I render the values in my return function in my React app, they render the initial values stull:
return (
<div>
<div>
<div>
<h5>{teamMemberIndex}</h5> <---- Returns old original value of 0
<h5>${teamMemberBillableAmount.toLocaleString()} </h5> <---- Returns old original value of 0
</div>
</div>
</div>
);
};
I assume it's rendering before the values are changed. But I do not know how to make it render AFTER they values are changed.
A React component only re-renders after either its state or its props change.
If you want your component to re-render when a variable changes, you have to add said variable to the component's state
If you're dealing with functional components, look into useState hook.
As told by Michael and Shubham you can write a functional component that triggers a rerender each time the state is updated using the useState hook.
An Example could be:
import React, { useState } from "react";
function Example() {
const [teamMemberBillableAmount, setTeamMemberBillableAmount] = useState(0);// initial state
const clickHandler = () => setTeamMemberBillableAmount(teamMemberBillableAmount + 1); // add 1 to the teamMemberBillableAmount state each time the button is clicked (a rerender will happen)
return (
<div>
<p>The team billable amount is:<strong style={{ color: "red" }}> teamMemberBillableAmount}</strong></p>
<button onClick={clickHandler}>Press me to add +1</button>
</div>
);
}
You can run and play with this code here
I have two functional components called Crucials and Timer, where Crucials is the parent and Timer is the Child.
Crucials has the user time data that needs to be passed down to the Timer for it to start the timer from the time provided by user.
My parent component is as follows:
export default function Crucials() {
const [Hdata, setHData] = useState(0);
const [Mdata, setMData] = useState(0);
let data = {
Hour: 0,
Min: 0,
Sec: 0
};
function timerStart(){
if(Hdata === 0 && Mdata === 0){
console.log("Minimum Timer Initiated");
setHData((minH)=> minH = 0);
setMData((minM)=> minM = 15);
}
data.Hour = 0;
data.Min = Mdata;
data.Secy = Hdata;
}
return (<div>
<div><Timer timerdata={data}/></div>
<Button variant="contained" color="primary" onClick={timerStart}> Start Timer</Button>
<form className={styles.HTM} onSubmit={timerStart}>
<Input type="number" onChange={e => setHData(e.target.value) } id="outlined-basic" placeholder="Hours(H)" ></Input>
<Input type="number" id="outlined-basic" onChange={e => setMData(e.target.value)} placeholder="Minutes(M)" ></Input>
</form>
</div>
)}
My child is as follows:
export default function Timer(timerdata) {
let Seconds = timerdata.Sec;
let Minutes = timerdata.Min;
let Hours = timerdata.Hour;
....
...
}
All I want to do is for me to be able to pass the data object to the child and for it to only update when timerStart is invoked via the Start Timer button.
However, what is happening is that the data object passed to the child only ever holds the initialization values (0,0,0) and invoking timerStart function, which changes the values within data, is not reflected in the Child.
Also, for some odd reason, just inputting values into the two <Input> fields causes child to update data, but it still holds the initial values rather than what the user input.
I'm probably doing something wrong, but I'm unable to figure it out. Any help is appreciated.
You need to make data a state value, currently:
your updates to your object won't persist between re-renders. Using setHData/setMData to update your state causes your component function to be called/executed again, redefining local variables such as the data object to the initial values
updating your data alone using data.Hour = 0; etc. won't cause your component/children components to re-render with the updated values. As a result, you need to use react's state setter function, setX to signal to react that your data object has changed and that your component needs to re-render using the new state values.
Instead, create a new state value that holds your data object, and use setData() to update it. This will cause your update to re-render your component + its children:
const {useState} = React;
const App = () => {
const [data, setData] = useState({hour:1,min:2});
const clickHandler = () => {
setData({hour: 10, min: 20}); // use input values instead of hard-coding values
};
return <div>
<Child data={data} />
<button onClick={clickHandler}>Update data</button>
</div>
}
const Child = (props) => <div>
<p>H: {props.data.hour}</p>
<p>M: {props.data.min}</p>
</div>;
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
Side note:
Your code setHData((minH)=> minH = 0); can be written as setHData(0);, and same with setMData(15). To update the state value, all you need to do is pass the new value to the state setter function. You don't need to pass an arrow function here, as your new value (0, and 15) doesn't rely on the previous state value.
So say you have a custom hook:
useCustomHook()=>{
const [state, setState] = React.useState(0);
const modifyState = ({state, n}) => {setState(state + n);}
/*Does state need to be included as an argument/parameter here in modifyState? If, alternatively,
const modifyState = ({n}) => {setState(state + n)};
Will state always be 0 in the scope of modifyState, since that was its value when the function was
created originally. So everytime modifyState is called, it is equivalent to (n)=>setState(0+n) ?
*/
return [state, modifyState];
}
const FunctionalComponent = () => {
const [state, modifyState] = useCustomHook();
const n = 5;
modifyState({state,n}) /* Does state need to be passed here? (since useCustomHook already has its own
copy of state) */
//... logic ....
return <div></div>
}
From doing some testing in the console, it appears that state doesn't need to be passed as an argument to modifyState. But, I'm confused as to the scoping logic behind this, and am unsure if the behavior hooks would change it. Could someone explain the logic behind this?
Instead of passing the state you can make use of function implementation of setState. Something like this:
const modifyState = (n) => {
setState(state => state + n);
}
Ref: https://reactjs.org/docs/hooks-reference.html#functional-updates
If I receive a prop and change it in my functional component, can it create a problem? Or it's ok to change it inside the component?
e.g
const MyComponent = ({ foo }) => {
// is this bad?
foo = someCondition ? bar : foo
return (...)
}
I know that I could create another variable and store the values, but I would like to know if changing the prop it self won't cause any problem because it's a functional component.
No, it shouldn't create any problems. As with regular functions the arguments passed are their own variables in the function scope and don't mutate the original value passed to the function.
function something(value) {
value = 'nothing';
}
var anything = 0;
something(anything);
// Anything should still be 0;
console.log(anything);
But I would suggest to not mutate your variables.
If foo in your example is passed from the parrent, and the parrent keeps it in its state, then you would also need to pass setFoo as a paramater to your component and use that to update it properly.
function Parrent(){
let [foo, setFoo] = useState('foo');
return <Child foo={foo} setFoo={setFoo}/>
}
As for changing the props directly, you can if they are arrays or objects.
Props in the React are just read-only variables. You should change the props values by the parent component
I avoid changing the prop.
But I created a simple example and changing the prop in the children do not affected the value in the parent component.
https://codesandbox.io/s/objective-cdn-cq55d
I tested it with several render. Thats why I added an input. Typing in it makes the component rerender.
const MyComponent = ({ foo }) => {
// Not valid
foo = someCondition ? bar : foo
return (...)
}
There are two kinds of data in React,
a)Props(immutable data)
b)State(mutable data)
you are not supposed to change the immutable data(there are some ways to do it, but not recommended). what you should do is, (you can't assign a callback and change from here, i'll explain later why)
if you want to just use just the value inside this component
const baz = foo === condition ? bar : foo
or render something based on foo meets some condition
return (
<div>
{foo === somecondition ? <A /> : <B />}
</div>
)
Or you want to actually change it,
coming from a global state like redux or mobx,
u should change it from the reducers in case of redux or
#action decorated functions in mobx.
if it's a local state which passed down to the child component,
u can set a call back and assign to a click handler in which the case it is feasible
handleClick = () => {
this.setState(prevState => ({
...prevState,
foo: someCondition ? bar : foo,
}))
}
render () {
const { handleClick } = this
return <ChildComponent {...{ handleClick }} />
}
Like said before u can't change the passed down local state from render of the child(or render of any component[actually u can, but may end up in infinite loops: each time a state change happens, the component will re render, so the loop(pure components excluded eg: shouldComponentUpdate() hook which validates an unrelated condition)])
in such cases what u should do is to make the child component also a stateful component and change the parent props with a callback
class Child extends Component {
//set once
componentWillMount() {
if (condition) callback
}
//whenever there is change
componentWillReceiveProps(nextProps) {
if (condition) callback
}
//use correct lifecycle method which meets your requirement..
}
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In the above example whenever setCount(count + 1) is invoked a re-render happens. I am curious to learn the flow.
I tried looking into the source code. I could not find any reference of useState or other hooks at github.com/facebook/react.
I installed react#next via npm i react#next and found the following at node_modules/react/cjs/react.development.js
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
On tracing back for dispatcher.useState(), I could only find the following ...
function resolveDispatcher() {
var dispatcher = ReactCurrentOwner.currentDispatcher;
!(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
return dispatcher;
}
var ReactCurrentOwner = {
/**
* #internal
* #type {ReactComponent}
*/
current: null,
currentDispatcher: null
};
I wonder where can I find dispatcher.useState() implementation and learn how it triggers re-render when setState setCount is invoked.
Any pointer would be helpful.
Thanks!
The key in understanding this is the following paragraph from the Hooks FAQ
How does React associate Hook calls with components?
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
(This also explains the Rules of Hooks. Hooks need to be called unconditionally in the same order, otherwise the association of memory cell and hook is messed up.)
Let's walk through your counter example, and see what happens. For simplicity I will refer to the compiled development React source code and React DOM source code, both version 16.13.1.
The example starts when the component mounts and useState() (defined on line 1581) is called for the first time.
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
As you have noticed, this calls resolveDispatcher() (defined on line 1546). The dispatcher refers internally to the component that's currently being rendered. Within a component you can (if you dare to get fired), have a look at the dispatcher, e.g. via
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)
If you apply this in case of the counter example, you will notice that the dispatcher.useState() refers to the react-dom code. When the component is first mounted, useState refers to the one defined on line 15986 which calls mountState(). Upon re-rendering, the dispatcher has changed and the function useState() on line 16077 is triggered, which calls updateState(). Both methods, mountState() on line 15352 and updateState() on line 15371, return the count, setCount pair.
Tracing ReactCurrentDispatcher gets quite messy. However, the fact of its existence is already enough to understand how the re-rendering happens. The magic happens behind the scene. As the FAQ states, React keeps track of the currently rendered component. This means, useState() knows which component it is attached to, how to find the state information and how to trigger the re-rendering.
setState is a method on the Component/PureComponent class, so it will do whatever is implemented in the Component class (including calling the render method).
setState offloads the state update to enqueueSetState so the fact that it's bound to this is really only a consequence of using classes and extending from Component. Once, you realize that the state update isn't actually being handled by the component itself and the this is just a convenient way to access the state update functionality, then useState not being explicitly bound to your component makes much more sense.
I also tried to understand the logic behind useState in a very simplified and basic manner, if we just look into its basic functionalities, excluding optimizations and async behavior, then we found that it is basically doing 4 things in common,
maintaining of State, primary work to do
re-rendering of the component through which it get called so that caller component can get the latest value for state
as it caused the re-rendering of the caller component it means it must maintain the instance or context of that component too, which also allows us to use useState for multiple component at once.
as we are free to use as many useState as we want inside our component that means it must maintain some identity for each useState inside the same component.
keeping these things in mind I come up with the below snippet
const Demo = (function React() {
let workInProgress = false;
let context = null;
const internalRendering = (callingContext) => {
context = callingContext;
context();
};
const intialRender = (component) => {
context = component;
workInProgress = true;
context.state = [];
context.TotalcallerId = -1; // to store the count of total number of useState within a component
context.count = -1; // counter to keep track of useStates within component
internalRendering(context);
workInProgress = false;
context.TotalcallerId = context.count;
context = null;
};
const useState = (initState) => {
if (!context) throw new Error("Can only be called inside function");
// resetting the count so that it can maintain the order of useState being called
context.count =
context.count === context.TotalcallerId ? -1 : context.count;
let callId = ++context.count;
// will only initialize the value of setState on initial render
const setState =
!workInProgress ||
(() => {
const instanceCallerId = callId;
const memoizedContext = context;
return (updatedState) => {
memoizedContext.state[instanceCallerId].value = updatedState;
internalRendering(memoizedContext);
};
})();
context.state[callId] = context.state[callId] || {
value: initState,
setValue: setState,
};
return [context.state[callId].value, context.state[callId].setValue];
};
return { useState, intialRender };
})();
const { useState, intialRender } = Demo;
const Component = () => {
const [count, setCount] = useState(1);
const [greeting, setGreeting] = useState("hello");
const changeCount = () => setCount(100);
const changeGreeting = () => setGreeting("hi");
setTimeout(() => {
changeCount();
changeGreeting();
}, 5000);
return console.log(`count ${count} name ${greeting}`);
};
const anotherComponent = () => {
const [count, setCount] = useState(50);
const [value, setValue] = useState("World");
const changeCount = () => setCount(500);
const changeValue = () => setValue("React");
setTimeout(() => {
changeCount();
changeValue();
}, 10000);
return console.log(`count ${count} name ${value}`);
};
intialRender(Component);
intialRender(anotherComponent);
here useState and initialRender are taken from Demo. intialRender is use to call the components initially, it will initialize the context first and then on that context set the state as an empty array (there are multiple useState on each component so we need array to maintain it) and also we need counter to make count for each useState, and TotalCounter to store total number of useState being called for each component.
FunctionComponent is different. In the past, they are pure, simple. But now they have their own state.
It's easy to forget that react use createElement wrap all the JSX node, also includes FunctionComponent.
function FunctionComponent(){
return <div>123</div>;
}
const a=<FunctionComponent/>
//after babel transform
function FunctionComponent() {
return React.createElement("div", null, "123");
}
var a = React.createElement(FunctionComponent, null);
The FunctionComponent was passed to react. When setState is called, it's easy to re-render;