简体   繁体   中英

React useRef() not getting defined

I have a prosemirror based editor that I'd like to enable real-time collaboration on. So I've set up a socket server as described here

I set up the WebsocketProvider as a useRef() so that we're not constantly re-creating it everytime we render the component (before I was spinning up dozens of websockets). However, now it's not even getting defined, as the console.log(this.yXmlFragment, this.provider) in get plugins() is returning both as undefined

My desired behavior is that I want provider and yXmlFragment to be updated only when the sectionID changes, not for any other re-render. But it's not even being set the first time. Can anyone explain how I'm wrong?

import React, { useContext, useEffect, useRef } from "react";
import Editor, { Extension } from "rich-markdown-editor";
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { ySyncPlugin, yCursorPlugin } from 'y-prosemirror';
import { AuthUserContext } from "../Util/AuthUser";

type EditorInput = {
  id?: string;
  readOnly?: boolean;
  defaultValue?: string;
  value?: string;
  placeholder?: string;
  sectionID?: string;
  onChange(e: string): void;
};

const EditorContainer: React.FC<EditorInput> = (props) => {
  const {
    id,
    readOnly,
    placeholder,
    sectionID,
    defaultValue,
    value,
    onChange,
  } = props;
  const { currentUser } = useContext(AuthUserContext);

  const provider = useRef<WebsocketProvider>();
  const yXmlFragment = useRef<Y.XmlFragment>();
  
  useEffect(() => {
    const ydoc = new Y.Doc();
    yXmlFragment.current = ydoc.getXmlFragment('prosemirror');
    provider.current = new WebsocketProvider('wss://my_socket_server.herokuapp.com', `${sectionID}`, ydoc);
  }, [sectionID]);
  
  return (
    <div className="Editor-Container" id={id}>
      <Editor
        onChange={(e: any) => onChange(e)}
        defaultValue={defaultValue}
        value={value}
        readOnly={readOnly}
        placeholder={placeholder}
        extensions={[
          new yWebsocketExt(provider.current, yXmlFragment.current, currentUser)
        ]}
      />
    </div>
  );
};

export default EditorContainer;

class yWebsocketExt extends Extension {
  provider?: WebsocketProvider;
  yXmlFragment?: Y.XmlFragment;
  currentUser: User;

  constructor(provider: WebsocketProvider | undefined, yXmlFragment: Y.XmlFragment | undefined, currentUser: User) {
    super();  

    if (provider?.shouldConnect) {
      provider?.connect()
    } else {
      provider?.disconnect()
    }  

    this.provider = provider;
    this.yXmlFragment = yXmlFragment;
    this.currentUser = currentUser;
  }

  get name() {
    return "y-websocket sync";
  }

  get plugins() {
    console.log(this.yXmlFragment, this.provider);
    if (this.yXmlFragment && this.provider) {
      this.provider.awareness.setLocalStateField('user', {
        name: currentUser.name,
        color: '#1be7ff',
      });
      return [
        ySyncPlugin(this.yXmlFragment),
        yCursorPlugin(this.provider.awareness),
      ]
    }
    return []
  }
};

This is happening because you are including sectionID in the dependency list of the useEffect within EditorContainer . Because sectionID doesn't change on initial load, this useEffect never fires. I've created a minimal example of this here:https://codesandbox.io/s/focused-babbage-ilx4j?file=/src/App.js

I recommend changing useEffect to useMemo because it runs at least once on initial render of EditorContainer . And you still get the performance benefit because it shouldn't rerun unless sectionID changes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM