In React, is it possible to store a ref in a context?

Viewed 8972

I need global app-wide access to a VideoElement to play it on user events on browsers like Safari and was wondering if storing the VideoElement in a context would be the best way to do that. I programmatically play my video through a redux action and in Safari that is not possible unless it has been played once through a user triggered event (like a click)

Is it possible to store an element (ref) within a context? The VideoElement will be then rendered within the component which I want to have my video, and then other components will also have access to the context and be able to call functions such as usePlayVideo that based on the context's state, will either call videoElement.play() if this is the first time the video is being played, or dispatch the redux action to play the video programmatically otherwise

4 Answers

It is possible to store a ref into context! You need to create a context at first. Then you need to pass value to the context provider and create a ref object using useRef hook. After that, you pass the ref into the value.

Now, You have a ref object sharing between components under the context provider and if you want to retrieve or pass a new ref, you could use useContext hook to deal with it.

Here is the demo (codesandbox).

Here is the sample code.

import { createContext, useContext, useEffect, useRef, useState } from "react";
import "./styles.css";

const MyContext = createContext();

export const ContextStore = (props) => {
  const ref = useRef();
  return <MyContext.Provider value={ref}>{props.children}</MyContext.Provider>;
};

export default function App() {
  return (
    <>
      <ContextStore>
        <MyComponent />
        <MyComponent2 />
      </ContextStore>
    </>
  );
}

const MyComponent = () => {
  const myContext = useContext(MyContext);

  return (
    <div className="App" ref={myContext}>
      <h1>Hello MyComponent1</h1>
    </div>
  );
};

const MyComponent2 = () => {
  const myContext = useContext(MyContext);
  const [divRef, setDivRef] = useState();
  useEffect(() => {
    setDivRef(myContext);
  }, [myContext]);

  return (
    <div className="App">
      <h1>{divRef?.current && divRef.current.innerText}</h1>
    </div>
  );
};

Why not? I'll say. Let's see if we can setup an example.

const fns = {}
const addDispatch = (name, fn) => { fns[name] = fn }
const dispatch = (name) => { fns[name] && fns[name]() }
const RefContext = createContext({ addDispatch, dispatch })
export default RefContext
const Child1 = () => {
  const [video, dispatchVideo] = useState(...)
  const { addDispatch } = useContext(RefContext)
  useEffect(() => {
    addDispatch('video', dispatchVideo)
  }, [])
}

const Child2 = () => {
  const { dispatch } = useContext(RefContext)
  const onClick = () => { dispatch('video') }
  ...
}

The above two childs do not have to share the same ancestor.

I didn't use ref the way you wanted, but i think you can pass your ref to one of the function. This is a very basic idea. I haven't tested it yet. But seems it could work. A bit

You can use this approach:

VideoContext.js

import { createContext, createRef, useContext } from "react";

const VideoContext = createContext();
const videoRef = createRef();

export const VideoContextProvider = (props) => {
  return (
    <VideoContext.Provider value={videoRef}>
      {props.children}
    </VideoContext.Provider>
  );
};

export const useVideoContext = () => useContext(VideoContext);

and App.js for example:

import { useState, useEffect } from "react";
import { useVideoContext, VideoContextProvider } from "./VideoContext";

const SomeComponent = () => {
  const videoRef = useVideoContext();

  return (
    <div ref={videoRef}>
      <h1>Hey</h1>
    </div>
  );
};

const SomeOtherComponent = () => {
  const [ref, setRef] = useState();
  const videoRef = useVideoContext();

  useEffect(() => {
    setRef(videoRef);
  }, [videoRef]);

  return (
    <div>
      <h1>{ref?.current?.innerText}</h1>
    </div>
  );
};

export default function App() {
  return (
    <>
      <VideoContextProvider>
        <SomeComponent />
      </VideoContextProvider>

      {/* ... */}

      {/* Some other component in another part of the tree */}
      <VideoContextProvider>
        <SomeOtherComponent />
      </VideoContextProvider>
    </>
  );
}

code sandbox

No. It's not possible to use Ref on context api. React ref is considered to be used on rendering element.

What you're looking for is to forward the ref, so that you can consume them wherever you want.

Related