Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How correctly pass a node from a ref to a context?

I'm trying to pass a node from a ref to a context. But because I have no re-render after the first render, the passed node is null. I thought about two variants (but I think they aren't the best):

  1. To pass ref instead of ref.current. But then in use cases, I'll be forced to use something like contextRef.current instead of contextNode.

  2. Use a state (something like firstRender) to re-render a component after getting a ref.current. This seems not optimal.

What is a correct (the best?) way to do it?

CodeSandbox

import React, { createContext, createRef, useContext, useEffect } from "react";
import ReactDOM from "react-dom";

const Context = createContext(null);

const App = ({ children }) => {
  const ref = createRef();

  return (
    <div ref={ref}>
      <Context.Provider value={ref.current}>{children}</Context.Provider>
    </div>
  );
};

const Child = () => {
  const contextNode = useContext(Context);

  useEffect(() => {
    console.log(contextNode);
  });

  return <div />;
};

const rootElement = document.getElementById("root");
ReactDOM.render(
  <App>
    <Child />
  </App>,
  rootElement
);
like image 787
sergdenisov Avatar asked Aug 20 '19 11:08

sergdenisov


People also ask

Can I pass ref through context?

Instead of passing the ref which doesn't trigger a render when changed, use a state that holds the ref. This way you can change the Context from a child if needed, and at the same time you get the value updated correctly. ref. current will still be null in first render.

How do you pass ref to the functional component?

To create a ref in a functional component we use the useRef() hook which returns a mutable object with a . current property set to the initialValue we passed to the hook. This returned object will persist for the full lifetime of the component. Thus, throughout all of its re-rendering and until it unmounts.


2 Answers

Instead of passing the ref which doesn't trigger a render when changed, use a state that holds the ref. This way you can change the Context from a child if needed, and at the same time you get the value updated correctly.

const App = ({ children }) => {
    const ref = useRef(null);
    const [ref_state, setRefState] = useState(null);

    useEffect(() => {
        if (!ref.current) {
            return;
        }

        setRefState(ref.current)
    }, []);

    return (
        <div ref={ref_state}>
            <Context.Provider value={ref.current}>{children}</Context.Provider>
        </div>
    );
};

If you need the initial render to point to the element, you could (in a non-optimal way) set the initial value to the HTML element:

const App = ({ children }) => {
    const ref = useRef(document.querySelector("#app"));

    return (
        <div id="app" ref={ref}>
            <Context.Provider value={ref.current}>{children}</Context.Provider>
        </div>
    );
};
like image 79
Alvaro Avatar answered Sep 21 '22 10:09

Alvaro


I didn't know about that, but passing ref.current doesn't work in the first render, but if you only pass ref, it will work in the first render.

Where is the working codesandbox.

I don't think that this

then is use cases I'll be forced to use something like contextRef.current instead of contextNode.

Will be a issue, it will be good, because when using it, you will know that what you are getting is a ref.

Also,

Do this

Use a state (something like firstRender) to rerender a component after getting a ref.current. This seems not optimal.

Only for not using ref.current, doesn't look like a good practice.

like image 22
Vencovsky Avatar answered Sep 21 '22 10:09

Vencovsky