How to use Refs in Function based Component [duplicate] - reactjs

I was going through the hooks documentation when I stumbled upon useRef.
Looking at their example…
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
…it seems like useRef can be replaced with createRef.
function TextInputWithFocusButton() {
const inputRef = createRef(); // what's the diff?
const onButtonClick = () => {
// `current` points to the mounted text input element
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Why do I need a hook for refs? Why does useRef exist?

The difference is that createRef will always create a new ref. In a class-based component, you would typically put the ref in an instance property during construction (e.g. this.input = createRef()). You don't have this option in a function component. useRef takes care of returning the same ref each time as on the initial rendering.
Here's an example app demonstrating the difference in the behavior of these two functions:
import React, { useRef, createRef, useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [renderIndex, setRenderIndex] = useState(1);
const refFromUseRef = useRef();
const refFromCreateRef = createRef();
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex;
}
if (!refFromCreateRef.current) {
refFromCreateRef.current = renderIndex;
}
return (
<div className="App">
Current render index: {renderIndex}
<br />
First render index remembered within refFromUseRef.current:
{refFromUseRef.current}
<br />
First render index unsuccessfully remembered within
refFromCreateRef.current:
{refFromCreateRef.current}
<br />
<button onClick={() => setRenderIndex(prev => prev + 1)}>
Cause re-render
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

tldr
A ref is a plain JS object { current: <some value> }.
React.createRef() is a factory returning a ref { current: null } - no magic involved.
useRef(initValue) also returns a ref { current: initValue } akin to React.createRef(). Besides, it memoizes this ref to be persistent across multiple renders in a function component.
It is sufficient to use React.createRef in class components, as the ref object is assigned to an instance variable, hence accessible throughout the component and its lifecyle:
this.myRef = React.createRef(); // stores ref in "mutable" this context (class)
useRef(null) basically is equivalent to useState(React.createRef())[0] 1.
1 Replace useRef with useState + createRef
Following tweet has been enlightening for me:
useRef() is basically useState({current: initialValue })[0].
With insights from the tldr section, we now can further conclude:
useRef(null) is basically useState(React.createRef())[0].
Above code "abuses" useState to persist the returned ref from React.createRef(). [0] just selects the value part of useState - [1] would be the setter.
useState causes a re-render in contrast to useRef. More formally, React compares the old and new object reference for useState, when a new value is set via its setter method. If we mutate the state of useState directly (opposed to setter invocation), its behavior more or less becomes equivalent to useRef, as no re-render is triggered anymore:
// Example of mutating object contained in useState directly
const [ref] = useState({ current: null })
ref.current = 42; // doesn't cause re-render
Note: Don't do this! Use the optimized useRef API instead of reinventing the wheel. Above is for illustration purposes.

createRef always returns a new ref, which you'd generally store as a field on a class component's instance. useRef returns the same ref upon every render of a functional component's instance. This is what allows the state of the ref to persist between renders, despite you not explictly storing it anywhere.
In your second example, the ref would be re-created upon every render.

Just to highlight a purpose:
createRef is as simple as return {current: null}. It's a way to handle ref= prop in most modern way and that's it(while string-based is toooo way magic and callback-based looks too verboose).
useRef keeps some data before renders and changing it does not cause re-render(as useState does). They are rarely related. Everything you expect for class-based component go to instance fields(this.* =) looks like candidate to be implemented with useRef in functional components.
Say useCallback works as bounded class methods(this.handleClick = .....bind(this)) and may be re-implemented(but we should not re-invent the wheel for sure) with useRef.
Another examples are DOM refs, timeout/interval IDs, any 3rd party libraries' identifiers or references.
PS I believe React team better chose different naming for useRef to avoid confusion with createRef. Maybe useAndKeep or even usePermanent.

A ref is a plain JS object { current: }.
React.useRef(initValue) return a ref { current: initValue }
it is remember ref value across multiple render of function component.
It is advise to use in Function component
React.createRef(initValue) also return a ref { current: initValue }
it is not remember ref value across multiple render of function components. It is advise to use in class based component

Yet another but important addition to other's answers.
You can't set a new value for createRef. But you can for useRef.
const ur = useRef();
const cr = createRef();
ur.current = 10; // you can do it, and value is set
cr.current = 10; // you can, but it's no good, it will not change it

Related

Using `useEffect()` inside of a function

I have came across the following code in FlyerChat.
import * as React from 'react'
export const usePrevious = <T>(value: T) => {
const ref = React.useRef<T>()
React.useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
The function is called as
const previousChatMessages = usePrevious(chatMessages);
I have a hard time understanding how storing value in a ref in a function and with a useEffect is different than just keeping its value.
Looks like it is because library maintainers don't want to rerender when you change chatMessages
From React documentation:
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

Problem with useCallback not working as expected

I'm expecting that when I pass a function wrapped in useCallback as an attribute to another function, that the function will still work, but the function will not be re-created on each call.
I have this problem in a large application, but I've made the problem into a small, reproducible example that I can share here.
Below is the code that I'm struggling with. I have commented out my attempt at working with useCallback that is not working as expected. When I don't use useCallback (the non-commented out code), the app toggles the theme as I expect.
I have the simple example in codesandbox at this URL:
https://codesandbox.io/s/github/pkellner/callback-theme-toggle
If I un-comment the useCallback line, the theme toggles once, then never toggles again.
My expectation is that with the useCallback code that the theme will toggle and appMenu.js will not get re-rendered on every theme toggle click.
Here is the /pages/index.js
import {useCallback, useContext} from "react";
import AppMenu from "../src/AppMenu";
import { ThemeContext, ThemeProvider } from "../src/ThemeContext";
function Inner() {
const { toggleTheme, darkTheme } = useContext(ThemeContext);
return (
<div>
<h1>HOME</h1>
{/*<AppMenu toggleTheme={useCallback(toggleTheme,[])} />*/}
<AppMenu toggleTheme={toggleTheme} />
<h2>darkTheme: {darkTheme === true ? "true" : "false"}</h2>
</div>
);
}
export default function Home() {
return (
<ThemeProvider>
<Inner />
</ThemeProvider>
);
}
There's a few points to address here.
When you useCallback, you create a closure around whatever dependencies it has. The callback needs to be recreated whenever one of those dependencies changes. The "meat" of your callback is this:
const toggleTheme = () => {
setDarkTheme(!darkTheme);
};
Which means you have two dependencies - setDarkTheme (which is functionally stable), and darkTheme (which changes). So, when you wrap this in a useCallback and don't declare darkTheme as a dependency, the callback closures over the initial value of darkTheme, and always then uses that to toggle on (which is why the toggle works one time, but then never works again).
But good news! You can get rid of your dependency on darkTheme by using the callback version of setState -
const toggleTheme = () => {
setDarkTheme(prev => !prev);
};
Boom! Now you have a functionally stable callback! You can safely wrap this into a useCallback hook with no dependencies, and it will work as you had intended!
But... you'll notice that even if you do that, AppMenu will always re-render when the them changes. Toggling the theme updates your Context object, and:
All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes.
(from the doco of Context).
Inner is a consumer of the context (via the useContext hook), and the parent of AppMenu - which means they both re-render when the context changes.
When you do
useCallback(toggleTheme,[])
This tells React that the value to use at that point is the value of toggleTheme when the component mounts. Since the dependency array is empty, the callback never changes.
When the component mounts, toggleTheme in context closes over the state value at that point:
const toggleTheme = () => {
console.log(`useTheme:toggleTheme:${darkTheme}`);
setDarkTheme(!darkTheme); // <---- reference to state
};
darkTheme is false at that point. Although the value passed down by useContext changes when the component re-renders, because you're using useCallback with an empty dependency array, the value returned by useCallback remains the same - it refers to the initial toggleTheme from context, where darkTheme is its initial value (so further clicks don't appear to produce a change, because you're setting state to the same state as it is currently).
useCallback doesn't make any sense here; just use the value returned by useContext so that it always has the most up-to-date state value, instead of adding extra complication. The function is re-created by context, not by your use (or lack thereof) of useCallback.
If you want AppMenu to not re-render, then you should memoize it - and you should fix the context's toggleTheme so that the function it passes down doesn't depend on a reference to a (possibly stale) state value in the closure.
const toggleTheme = () => {
setDarkTheme(theme => !theme);
};
function Inner() {
const { toggleTheme, darkTheme } = useContext(ThemeContext);
const menu = useMemo(() => <AppMenu toggleTheme={toggleTheme} />, []);
return (
<div>
<h1>HOME</h1>
{menu}
<h2>darkTheme: {darkTheme === true ? "true" : "false"}</h2>
</div>
);
}

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Is it possible to get rid of useContext calculations each rerender?

We can prevent unnecessary calculations for useEffect with an empty array as the second argument in the hook:
// that will be calculated every single re-rendering
useEffect(() => {...some procedures...})
// that will be calculated only after the first render
useEffect(() => {...some procedures...}, [])
But, for the useContext hook we can't do like above, provide the second argument.
Also, we can't wrap useContext by useCallback, useMemo.
For instance, we have a component:
const someComponent = () => {
const contextValue = useContext(SomeContext);
const [inputValue, setInputValue] = useState('');
return (
<Fragment>
<input value={inputValue} onChange={onInputChange} />
<span>{contextValue}</span>
</Fragment>
)
The problem is that every single typing will launch re-rendering and we will have unnecessary useContext re-rendering each time. One of the decision is brake the component on two:
const WithContextDataComponent = () => {
const contextValue = useContext(SomeContext);
return <JustInputComponent contextValue={contextValue} />
const JustInputComponent = (props) => {
const [inputValue, setInputValue] = useState('');
return <input value={inputValue} onChange={onInputChange} />
So, now the problem is disappeared, but two components we have, though. And in the upper component instead <SomeComponent /> we should import <WithContextDataComponent />, that a little bit ugly, I think.
Can I stop the unnecessary re-rendering for useContext without splitting into two components?
From React Hooks API Reference:
https://reactjs.org/docs/hooks-reference.html#usecontext
useContext
const value = useContext(MyContext);
Accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest above the calling component in the tree.
When the nearest above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider.
As you can see from the documentation, the useContext() hook will only cause a re-render of your component if the values that it provides change at some point. Ans this is probably your intended behavior. Why would you need stale data in your context hook?
When your component re-render by itself, without changes in the context, the useContext() line will simply give back the same values that it did on the previous render.
It seems that you're using the useContext() hook the way it it meant to be used. I don't see anything wrong with it.

How can I reset a react component including all transitively reachable state?

I occasionally have react components that are conceptually stateful which I want to reset. The ideal behavior would be equivalent to removing the old component and readding a new, pristine component.
React provides a method setState which allows setting the components own explicit state, but that excludes implicit state such as browser focus and form state, and it also excludes the state of its children. Catching all that indirect state can be a tricky task, and I'd prefer to solve it rigorously and completely rather that playing whack-a-mole with every new bit of surprising state.
Is there an API or pattern to do this?
Edit: I made a trivial example demonstrating the this.replaceState(this.getInitialState()) approach and contrasting it with the this.setState(this.getInitialState()) approach: jsfiddle - replaceState is more robust.
To ensure that the implicit browser state you mention and state of children is reset, you can add a key attribute to the root-level component returned by render; when it changes, that component will be thrown away and created from scratch.
render: function() {
// ...
return <div key={uniqueId}>
{children}
</div>;
}
There's no shortcut to reset the individual component's local state.
Adding a key attribute to the element that you need to reinitialize, will reload it every time the props or state associate to the element change.
key={new Date().getTime()}
Here is an example:
render() {
const items = (this.props.resources) || [];
const totalNumberOfItems = (this.props.resources.noOfItems) || 0;
return (
<div className="items-container">
<PaginationContainer
key={new Date().getTime()}
totalNumberOfItems={totalNumberOfItems}
items={items}
onPageChange={this.onPageChange}
/>
</div>
);
}
You should actually avoid replaceState and use setState instead.
The docs say that replaceState "may be removed entirely in a future version of React." I think it will most definitely be removed because replaceState doesn't really jive with the philosophy of React. It facilitates making a React component begin to feel kinda swiss knife-y.
This grates against the natural growth of a React component of becoming smaller, and more purpose-made.
In React, if you have to err on generalization or specialization: aim for specialization. As a corollary, the state tree for your component should have a certain parsimony (it's fine to tastefully break this rule if you're scaffolding out a brand-spanking new product though).
Anyway this is how you do it. Similar to Ben's (accepted) answer above, but like this:
this.setState(this.getInitialState());
Also (like Ben also said) in order to reset the "browser state" you need to remove that DOM node. Harness the power of the vdom and use a new key prop for that component. The new render will replace that component wholesale.
Reference: https://facebook.github.io/react/docs/component-api.html#replacestate
The approach where you add a key property to the element and control its value from the parent works correctly. Here is an example of how you use a component to reset itself.
The key is controlled in the parent element, but the function that updates the key is passed as a prop to the main element. That way, the button that resets a form can reside in the form component itself.
const InnerForm = (props) => {
const { resetForm } = props;
const [value, setValue] = useState('initialValue');
return (
<>
Value: {value}
<button onClick={() => { setValue('newValue'); }}>
Change Value
</button>
<button onClick={resetForm}>
Reset Form
</button>
</>
);
};
export const App = (props) => {
const [resetHeuristicKey, setResetHeuristicKey] = useState(false);
const resetForm = () => setResetHeuristicKey(!resetHeuristicKey);
return (
<>
<h1>Form</h1>
<InnerForm key={resetHeuristicKey} resetForm={resetForm} />
</>
);
};
Example code (reset the MyFormComponent and it's state after submitted successfully):
function render() {
const [formkey, setFormkey] = useState( Date.now() )
return <>
<MyFormComponent key={formkey} handleSubmitted={()=>{
setFormkey( Date.now() )
}}/>
</>
}
Maybe you can use the method reset() of the form:
import { useRef } from 'react';
interface Props {
data: string;
}
function Demo(props: Props) {
const formRef = useRef<HTMLFormElement | null>(null);
function resetHandler() {
formRef.current?.reset();
}
return(
<form ref={formRef}>
<input defaultValue={props.data}/>
<button onClick={resetHandler}>reset</button>
</form>
);
}

Resources