Why need useRef and not mutable variable? - javascript

I have read A Complete Guide to useEffect - Swimming Against the Tide at Overreacted.
The example shows that if we want to get the latest count, we can use useRef to save the mutable variable, and get it in async function laster:
function Example() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
// Set the mutable latest value
latestCount.current = count;
setTimeout(() => {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
});
// ...
}
However, I can do the same thing by creating a variable outside the component function, such as:
import React, { useState, useEffect, useRef } from 'react';
// defined a variable outside function component
let countCache = 0;
function Counter() {
const [count, setCount] = useState(0);
countCache = count; // set default value
useEffect(() => {
setTimeout(() => {
// We can get the latest count here
console.log(`You clicked ${countCache} times (countCache)`);
}, 3000);
});
// ...
}
export default Counter;
Are both ways practical, or is there anything bad if I define the variable outside function component?

useRef will assign a reference for each component, while a variable defined outside a function component scope will only assigned once.
useRef reference life span is component's life span (it "dies" when the component unmounts, while JS variables are scope-blocked).
Hence, define constant purpose variables outside of the component scope:
// This statement will only called once
const DEFAULT_VALUE = 5;
function Component() {
// use DEFAULT_VALUE.
}
Defining the same statement inside component's scope, will redefine it on every render:
// We can do better
function Component() {
// Redefined on every render
const DEFAULT_VALUE = 5;
}
Now for the question:
First, we can't actually reflect UI changed with outer scoped variables since changing them does not trigger render (only React API does).
Therefore the reflected value is its closure value.
let countCache = 0;
function Counter() {
...
countCache = 0;
useEffect(() => {
countCache = count;
});
...
// closure value of countCache
return <div>{countCache}</div>
}
Now, whats special with outer scope variables that they are global to the module itself, so using its value is global to all components referencing it (in the module).
For example if you want to count how many times the component mounted in your whole application life span, increase the variable inside useEffect on mount (couldn't find any other possible use-case).
let howMuchMounted = 0;
function Component() {
useEffect(() => { howMuchMounted += 1, [] };
}
To reflect the differences of outer variable and useRef reference, in the next example, on button click, you may notice that the variable is global for both of the components, while the reference is always updated to current state value.
import React, { useEffect, useRef, useReducer } from "react";
import ReactDOM from "react-dom";
// defined a variable outside function component
let countCache = 0;
function Counter() {
const [num, count] = useReducer((num) => num + 1, 0);
const countRef = useRef(count);
useEffect(() => {
// set count value on count change
countCache = num;
countRef.current = num;
}, [num]);
return (
<>
<button onClick={count}>Count</button>
<h3>state {num}</h3>
<h3>variable {countCache}</h3>
<h3>reference {countRef.current}</h3>
</>
);
}
export default function App() {
return (
<>
<Counter />
<hr />
See what happens when you click on the other counter
<hr />
<Counter />
</>
);
}
Please see a follow up question on useEffect use cases, there are many common mistakes when working with useRef references inside useEffect.

There are differences between the following four possible variations:
A variable inside the function component
A variable outside the function component
A state variable returned by useState()
A variable ( an object with property 'current' ) returned by useRef()
The last 2 can only be used within the component.
Let's take each case:
1. A variable inside the function component
Initialization: Variables are always re-initialized both on every render and consequently in multiple component instances
Variable Updates: Can be updated except const
Render: No render is triggered by React, and hence updated values are not reflected
2. A variable outside the function component
Initialization: Variables are initialized only when the file is loaded. And the file is loaded only once, irrespective of how many components consume the exports from the file. This is the main difference compared to using useRef(). If there is only one component instantiation, it would be same as useRef().
Variable updates: Can be updated except const
Render: No render is triggered by React, and hence updated values are not reflected
3. A state variable returned by useState()
Initialization: Variables are initialized only when the component is mounted, irrespective of the number of renders. However, each component instance gets it's own copy of state variables.
Variable updates: Can be updated using the state updater function.
Render: Render is triggered by React after the state variable is updated ( multiple updates may be batched into a single render )
4. A variable ( an object with property 'current' ) returned by useRef()
Initialization: Variables are initialized only when the component is mounted, irrespective of the number of renders. However, this will independently initialize the variable for each component instance in contrast to when it occurs only once when using a variable outside the component
Variable updates: Can be updated using the 'current' property
Render: No render is triggered by React, and hence updated values are not reflected
Choosing any of the above will largely depend on the requirement, but we have the necessary tools in our toolbox to get almost all the use cases covered.

Related

Why won't the updated variable values display?

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

React Native - need variable to update immediately, but useState is async?

Ok, I've looked extensively at useState set method not reflecting change immediately and I understand that React.useState variables are async.
This is a problem given that I'm working with a component structured React Native app, and need to set and use a variable immediately upon releasing drag of a component. I am doing this within a callback function of a Draggable from https://github.com/tongyy/react-native-draggable#readme , so the callback hook solution from useState set method not reflecting change immediately won't work.
Here's what I mean. I start with these React.useState
export default function App() {
const [firstColor, setFirstColor] = React.useState('');
const [secondColor, setSecondColor] = React.useState('');
Then I update them based on the position of the Draggable on the OnDragRelease callback function:
<Draggable onDragRelease={(event, gestureState) => { ToggleComboColor1(event, gestureState); ToggleComboPage(); } } onPressIn={() => {setDidDrag1(true);}} />
Here's where there is trouble. I have to set the useState variable based on gestureState, then if both Draggables have been dragged, toggle a result with ToggleComboPage() :
const ToggleComboColor1 = (event, gestureState) => {
setFirstColor(determineDraggableColor(gestureState.moveX, gestureState.moveY));
console.log(firstColor); //This does NOT print an updated value
}
const ToggleComboPage = () =>
{
if(didDrag1 && didDrag2)
{
console.log(firstColor + " | "+secondColor); //This, consequently, does NOT print an updated value for the most recently dragged
// setCurrentColorCombo(
// getColorComboItemArray(firstColor, secondColor)[0]
// );
// setCurrentKey("Combo");
// setDidDrag1(false);
// setDidDrag2(false);
}
}
I tried using a plain var to circumvent this, but those are re rendered / re created every time there's a re render.
How can I update and use these variables here?

React Hooks - using useState vs just variables

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.

Using value returned from function in react component (auto-update)

I wish to use the value returned from this function:
const quantity = () => {
let cookies = document.cookie.split("?");
return cookies.length;
}
in my react component:
const cart_button = (
<Mfont>{quantity}</Mfont>
);
<Mfont> is a standard span element styled with styled-components;
console.log(cookies.length)
gives me a number based on its length as expected, but I can't figure out how to insert it to my component and update every time a cookie.length increases.
According to this answer, the most reliable way still in 2019 to detect changes in cookies is to check document.cookie on interval.
In React 16.8, we have useEffect hook that we could use together with useState hook to run a function on interval and cause the component to update when the value of document.cookie has been updated, like
import React, { useState, useEffect } from 'react';
const MyComponent = props => {
const [latestCookie, setLatestCookie] = useState(document.cookie);
useEffect(() => {
const detectCookieUpdate = document.cookie !== latestCookie && setLatestCookie(document.cookie);
const interval = window.setInterval(detectCookieUpdate, 1000);
return () => window.clearInterval(interval);
});
return (
<Mfont>{quantity()}</Mfont>
);
};
This way we run a function every 1 second that checks if the value of document.cookie is equal to the previous value. Since the value is always a string, it's safe to use strict comparison operator === and not do any checks on top of that. When the values don't match, we run setLatestCookie provided by the state hook, causing the component to render again, and therefore making use of quantity function that will run again.
Have you tried to pass length value as props to your component? Then you can assign that value to the component state. When state changes do whatever you want.

How does React.useState triggers re-render?

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;

Categories

Resources