Search code examples
reactjsmaterial-uiserver-side-rendering

How to deal with text input changes in SSR text box before hydration is complete?


I'm working on a simple server-side rendering (SSR) scenario: just a login page, with a text input for a username and password.

As per usual with SSR, when a client first loads the page they get the server-rendered version, which is not hydrated. It looks fine, and the text boxes can be clicked and typed in.

Hopefully, the JS loads quickly and hydration occurs before the user types anything in the box, and everything works.

But, what if the user is on a slow network and it takes several seconds for the JS to load? Then the following happens:

  1. User types some characters in the box
  2. JS suddenly loads and React takes control of the input box, and clears it because the initial state is an empty string (!)
  3. User is confused and has to re-type.

There must be a best practice around this, right? I've tried a couple things like testing if typeof window === "undefined" and setting the input to disabled if so, but nothing is quite satisfactory. I think the best UX would be for the hydrated component to pick up the characters that have been typed, but I'd also be okay with disabling editing until hydration is complete.

FWIW I'm using Material UI, which presents some extra styling issues, but otherwise I think this question applies to any SSR text input scenario.


Solution

  • According to this question, what you could do is get a ref of the dom element and sync the values when the component is mounted

    import React, { useState, useRef, useEffect } from "react";
    
    export const Component = () => {
      const [value, setValue] = useState("");
      const inputRef = useRef(null);
    
      const onChange = (e) => {
        setValue(e.target.value);
      };
    
      useEffect(() => {
        const domValue = inputRef.current?.value;
        if (domValue) setValue(domValue);
      }, []);
    
      return <input ref={inputRef} type="text" value={value} onChange={onChange} />;
    };