Skip to content

Server Side Rendering causes different id's to be generated on client/server #2519

@SchutteJan

Description

@SchutteJan

Describe the bug

I'm using JsonForms with React in an SSR framework (TanStack Start) and experiencing hydration mismatches due to the ID generation.

I've created a minimal reproduction for TanStack specifically here:
https://github.com/SchutteJan/ssr-jsonforms

Here is the full hydration mismatch error message
react-dom_client.js?v=6dcb9bfa:4073 A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch

  ...
    <Grid4 size="grow">
      <MuiGrid-root ref={null} as="div" ownerState={{...}} className="MuiGrid-ro..." sx={{}}>
        <Insertion6>
        <div className="MuiGrid-ro...">
          <WithJsonFormsContext uischema={{type:"Control", ...}} schema={{type:"object", ...}} path={undefined} ...>
            <WithContextToJsonFormsRendererProps ctx={{...}} props={{uischema:{...}, ...}}>
              <JsonFormsDispatchRenderer uischema={{type:"Control", ...}} schema={{type:"object", ...}} path={undefined} ...>
                <TestAndRender2 uischema={{type:"Control", ...}} schema={{type:"object", ...}} ...>
                  <WithJsonFormsContext uischema={{type:"Control", ...}} schema={{type:"object", ...}} path={undefined} ...>
                    <WithContextToControlProps ctx={{...}} props={{uischema:{...}, ...}}>
                      <MaterialTextControl uischema={{type:"Control", ...}} schema={{type:"string", ...}} path="name" ...>
                        <MaterialInputControl uischema={{type:"Control", ...}} schema={{type:"string", ...}} path="name" ...>
                          <FormControl2 fullWidth={true} onFocus={function} onBlur={function} variant="outlined" ...>
                            <MuiFormControl-root as="div" ownerState={{fullWidth:true, ...}} className="MuiFormCon..." ...>
                              <Insertion6>
                              <div
                                className="MuiFormControl-root MuiFormControl-fullWidth css-ytlejw-MuiFormControl-root"
                                onFocus={function}
                                onBlur={function}
+                               id="#/properties/name2"
-                               id="#/properties/name4"
                              >
                                <InputLabel2 htmlFor="#/properti..." error={false} required={false}>
                                  <MuiInputLabel-root data-shrink={false} ref={null} className="MuiInputLa..." ...>
                                    <Insertion6>
                                    <FormLabel2 data-shrink={false} className="MuiInputLa..." htmlFor="#/properti..." ...>
                                      <MuiFormLabel-root as="label" ownerState={{...}} className="MuiFormLab..." ...>
                                        <Insertion6>
                                        <label
                                          className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl Mu..."
                                          data-shrink={false}
+                                         htmlFor="#/properties/name2-input"
-                                         htmlFor="#/properties/name4-input"
                                        >
                                ...
                                  <div onClick={function handleClick} onPointerEnter={function onPointerEnter} ...>
                                    <MuiOutlinedInput-input aria-invalid={false} aria-describedby={undefined} ...>
                                      <Insertion6>
                                      <input
                                        aria-invalid={false}
                                        aria-describedby={undefined}
                                        autoComplete={undefined}
                                        autoFocus={undefined}
                                        defaultValue={undefined}
                                        disabled={false}
+                                       id="#/properties/name2-input"
-                                       id="#/properties/name4-input"
                                        onAnimationStart={function handleAutoFill}
                                        name={undefined}
                                        placeholder={undefined}
                                        readOnly={undefined}
                                        required={false}
                                        rows={undefined}
                                        value=""
                                        onKeyDown={undefined}
                                        onKeyUp={undefined}
                                        type="text"
                                        className="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputAdorned..."
                                        onBlur={function handleBlur}
                                        onChange={function handleChange}
                                        onFocus={function handleFocus}
                                        ref={function}
                                      >
                                    ...
                                ...

For this repro it seems that there is a difference in id's being generated:

+        htmlFor="#/properties/name2-input"
-        htmlFor="#/properties/name4-input"

There seems to be an increment that increases on request, and is somehow different for server or client.

See https://github.com/SchutteJan/ssr-jsonforms/blob/main/src/routes/index.tsx for how I use JSONForms.

  • Is there a recommended way to use SSR with jsonforms?
  • Would you consider using React's useId() hook (React 18+) for deterministic IDs?

I couldn't find any documentation about server-side rendering, I apologize if this has been asked many times already.

Expected behavior

No hydration issues

Steps to reproduce the issue

  1. clone repro repo
  2. (p)npm install
  3. (p)pnm run dev
  4. navigate to http://localhost:3000/
  5. open console for error

Screenshots

No response

Which Version of JSON Forms are you using?

v3.7.0

Package

React Material Renderers, React Bindings

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions