7

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.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
tom
  • 958
  • 6
  • 17
  • 1
    Have you tried rendering input box after component is mounted by react? Eg. `const [isMounted,setMounted]=useState(false); useEffect(()=>{setMounted(true)},[])`. And then you can render input when `isMounted` is true. – Avkash Sep 16 '22 at 07:53
  • @Avkash I'm pretty sure this will a) cause an undesirable flicker and b) defeat the point of hydration. I'll confirm when I get a minute... – tom Sep 18 '22 at 01:09

1 Answers1

3

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} />;
};
diedu
  • 19,277
  • 4
  • 32
  • 49
  • I'm going to mark this accepted with the caveat that there is an underlying React issue that may be fixed some day and obviate this: https://github.com/facebook/react/issues/12955 – tom Sep 23 '22 at 10:19
  • That issue is about the `onChange` event. I think this issue might be more relevant: https://github.com/facebook/react/issues/26974 – Oliver Joseph Ash Jun 19 '23 at 11:41