How to rerender when refs change
Asked Answered
K

4

54

Code:

import DrawControl from "react-mapbox-gl-draw";

export default function MapboxGLMap() {
    let drawControl = null
    return(
      <DrawControl ref={DrawControl => {drawControl = DrawControl}}/>
    )
}

I want to load data when the drawControl not null. I check the document that may use callback ref.

So, how do I listen the drawControl changes and load data?

Kenzi answered 5/1, 2020 at 13:52 Comment(1)
Do you need to re-render when ref is updated, or is it enough to use useRef? The way yo do it you do not store the ref in any way. You must either use some hook to store it, a class component, or and object outside the component (for example a store).Salley
S
80

If you want to trigger a re-render after the ref changes, you must use useState instead of useRef. Only that way can you ensure that the component will re-render. E.g.:

function Component() {
  const [ref, setRef] = useState();
  
  return <div ref={newRef => setRef(newRef)} />
}

As described under useRef 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.

It may sometimes be better to store whatever value you are getting from the DOM node, as suggested here, instead of storing the node itself.

Salley answered 5/1, 2020 at 23:18 Comment(12)
Thanks for your advice, I tried your code, but it repeats re-render all the time. I need to re-render when ref is updated. I think the callback ref may help me out. But I do not know how to do itKenzi
Thanks, I use the callback ref to fix my problem.Kenzi
@YangYun, can you reproduce the re-render loop in a codesandbox? I tried, but it does not happen here.Salley
The problem is, sometimes you have to use a ref.Peisch
In what case do you need to use a ref? It's possible to store the element in a useState, and write it to a ref.current on each render pass to get both. Useful if you for example want the ref to be accessible without making it a dependency to a hook, while at the same time forcing re-render when the element changes.Salley
Shorter: <div ref={setRef} />Dockery
And what if you do not want always to rerender when setting state, how can you implement that ? I have a form with email value and don't want to rerender every time I use setEmail because value is controlled in component itself. But want to change and re-render if initial value is changed, so sometimes want it to act as setState and other time as ref.current = newEmail. How can i ipmlement suchDoggy
I recommend that you post a new question for that, @kristi-jorgji. Then you can also be a bit more specific about the code you have, how it works and what you want.Salley
This will cause a warning about setting state inside renderStrand
@Foobar, if you get that warning it sounds like you are calling a setState function during render, such as onClick={setState(true)} instead of correctly calling it in a function onClick={() => setState(true)}. Here is an example of setting ref to state, there is no such warning: codesandbox.io/s/rerender-when-refs-change-9pwuqlSalley
I want to store the "state" of a component in the parent, I do not want the parent to re-render when this state changes only the child, by switching to useState this will re-render the parent and child.Anemograph
@ZackOfAllTrades, yes, that will re-render the parent as well, since the parent's state changes.Salley
K
15

useCallback could listen the ref changed

export default function MapboxGLMap() {
    const drawControlRef = useCallback(node => {
      if (node !== null) {
        //fetch(...)   load data
      }
    },[]);

    return(
      <DrawControl ref={drawControlRef }/>
    )
}
Kenzi answered 6/1, 2020 at 13:10 Comment(1)
Worked perfectly for meHirsutism
C
3

In some cases, forcing a render with useState might be your best option.

First, initalize some state in the component that needs to be rendered. It could also be initiated in any of the parent components. In my case, it's in a context:

const [, setForceRender] = useState();

Second, set this state any time you need to force a render.

exampleRef.current = 'this refs data just changed';
setForceRender(Object.entries(exampleRef.current));

This is a sort of hacky way to do it, but sometimes it's the only simple option.

Campanula answered 18/10, 2023 at 6:42 Comment(0)
P
-9

You can use a callback function that useEffect base on the change in useRef

   function useEffectOnce(cb) {

        const didRun = useRef(false);
        useEffect(() => {
            if(!didRun.current) {
                cb();
                didRun.current = true
            }
        })
    }
Planetoid answered 24/1, 2020 at 0:54 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.