using useRef or createRef - javascript

I want to set the const [solution, setSolution] = useState(0); with the value of a input element by pressing a button
I get the same result using createRef or using the useRef hook
reading What's the difference between useRef and createRef?
gives quit different answers what exactly thees to do, is there a clear inside about thees to methods ?
function Interface() {
const [solution, setSolution] = useState(0);
const solutionInp = useRef();
// --createRef();
const onButtonClick = () => {
// `current` points to the mounted text input element
setSolution( solutionInp.current.value)
};
return (
<input
type="text"
// value={solution}
ref={solutionInp}
// onInput={(e) => setSolution(e.target.value)}
/>
</section>
<button type="button" onClick={onButtonClick}>do</button>
)}

createRef is for class components, calling it in a context of function component will be treated as a normal function, hence will RE-INITIAL your reference on every render.
useRef is for function components, you lose your ref on unmount lifecycle.

Related

call a function after rendering ends react

I have a function which gets all the inputs rendered (querySelector) in order to execute other stuff and also is called by the "onChange" method of a component I have in order to detect if other inputs were added.
The main problem here is that when useEffect() is called, for some reason not all the inputs are detected, for example if in the DOM there are 3 inputs it only detects 2 (the last input added is not being detected as you can see in the example).
what I think is happening is that the querySelector function ends before the other input is rendered so thats why only the last one added is not being detected. I tried adding a setTimeout() function but that is not a good practice...
Any recommendations?
const onChangeQuery = (e) => {
//some other code here...
selectFirstOpt();
};
useEffect(() => {
selectFirstOpt();
},[])
const selectFirstOpt = () => {
let selects = document.querySelectorAll(".rule--field > select");
if(!selects.length) return;
selects.forEach((select) => {
let firstOpt = select.options[0];
if(firstOpt.value === "") firstOpt.innerText = "select an option";
})
}
I think you could use useRef with useEffect. Also using useRef you can work on the element instead of using selectors to change element values.
import React, { useRef, useEffect } from 'react';
...
/* In your react hook function*/
const first = useRef();
const second = useRef();
...
useEffect(() => {
first.current.value = "value";
second.current.value = "changed";
}, [first, second])
...
/* in your return function */
<input type="text" ref={first} />
<input type="text" ref={second} />
This will change the value of the inputs as soon as they are loaded correctly, it is important to add useRef variables or constants into useEffect square brackets.

Can I use useRef Hook can also be used to keep track of previous state values

Already use useState, and useEffect to keep track of the previous state. Can I use useRef Hook can also be used to keep track of previous state values.
I belive you can use the useRef to keep track of pervious state value
Check the code example below You can refer this artical https://blog.logrocket.com/accessing-previous-props-state-react-hooks/
function Counter() {
const [count, setCount] = useState(0);
//the useRef Hook allows you to persist data between renders
const prevCountRef = useRef();
useEffect(() => {
//assign the ref's current value to the count Hook
prevCountRef.current = count;
}, [count]); //run this code when the value of count changes
return (
<h1>
Now: {count}, before: {prevCountRef.current}
{/*Increment */}
<button onClick={() => setCount((count) => count + 1)}>Increment</button>
</h1>
);
}

Get total width of JSX Element

How could I get the width of a JSX.Element? If I was in vanilla, I would do something like
const button = document.createElement('button');
document.body.appendChild(button)
window.getComputedStyle(button).width
Now I need to do the same, but it seems ref is null and I'm not sure even how to temporarily append to the DOM just to see what its width would be.
const button: JSX.Element = <CustomButton/>;
/// ....
Couple of things to check and consider.
where is the ref created and does it get forwarded correctly to a
valid dom element (make sure that CustomButton uses forwardRef).
you don't need to append anything in react to look at the width. all you need is ref.current.clientWidth or ref.current.getBoundingClientRect(), but ref.current has to exist in the first place :-)
if you need access to the ref.current element when your component first mounts (and not in a onClick or some other callback - then this does not apply) you'll have to use useLayoutEffect as the javascript runs before the dom is rendered so there is technically no to measure yet.
See this example:
ParentComponent.tsx
import {useState, useLayoutEffect} from 'react';
const ParentComponent = () => {
const [width, setWidth] = useState<number | null>(null);
useLayoutEffect(() => {
if(ref?.current && !width) {
const { clientWidth } = ref.current;
setWidth(clientWidth);
}
}, [ref?.current]);
console.log('width', width);
// `width` will be null at first render,
// then when CustomButton renders and <button> is created the ref will be
// updated, triggering your layout side effect that saves the
// clientWidth to the state. State change will trigger a rerender of
// ParentComponent and your console.log will finally print the width (whose
// value is stored in the state).
return <CustomButton ref={ref}/>;
};
CustomButton.tsx
import {forwardRef} from 'react';
const CustomButton = forwarRef((props, ref) => {
return (
<>
// some other stuff
<button ref={ref}/>>Click</button>
</>
);
};
The canonical way is to use a ref, and then observe it within an effect, which is called after the DOM gets rendered:
const ref = useRef();
useEffect(() => {
console.log(window.getComputedStyle(ref.current).width)
}, []);
return <button ref={ref}/>;

Why need useRef and not mutable variable?

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.

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