Preact: [QUESTION] How to avoid hydration for static content

Created on 23 Feb 2020  路  10Comments  路  Source: preactjs/preact

We recently switched from React to Preact and one of the things that we miss in performance is that with React we had a hack to avoid Hydration for static content.

I had this code (Suggested on https://github.com/facebook/react/issues/10923#issuecomment-338715787):

<StaticContent><MyStaticComponent /></StaticContent>
import { createElement, useRef, useState, useEffect } from 'react'

function useStaticContent() {
  const ref = useRef(null)
  const [render, setRender] = useState(typeof window === 'undefined')

  useEffect(() => {
    // check if the innerHTML is empty as client side navigation
    // need to render the component without server-side backup
    const isEmpty = ref.current.innerHTML === ''
    if (isEmpty) {
      setRender(true)
    }
  }, [])

  return [render, ref]
}

export default function StaticContent({ children, element = 'div', ...props }) {
  const [render, ref] = useStaticContent()

  // if we're in the server or a spa navigation, just render it
  if (render) {
    return createElement(element, {
      ...props,
      children,
    })
  }

  // avoid re-render on the client
  return createElement(element, {
    ...props,
    ref,
    suppressHydrationWarning: true,
    dangerouslySetInnerHTML: { __html: '' },
  })
}

However, in Preact this hack is not avoiding the hydration... Is there another way to do it? 馃憦

compat has fix

Most helpful comment

Hmm in a real SSR scenario this shouldn't happen but yes I can see why this happens in this test, I'll see if I can do something about it

All 10 comments

Hi! This should work in Preact - looks like we had an issue with our compat layer that @JoviDeCroock's PR addresses.

FWIW we're using this exact dangerouslySetInnerHTML hydration bypass trick on preactjs.com 馃憤
https://github.com/preactjs/preact-www/blob/a3a4839eb7bfe1bddfedda4b9b5df0307ee7dcdf/src/lib/hydrator.js#L98

Hey @aralroca

This will be fixed in the next release, thanks for the awesome issue report, it really helps us with rapidly fixing issues like these.

I鈥檓 going to close this issue now, feel free to reopen if there鈥檚 anything wrong with it.

Thank you @JoviDeCroock and @developit ! I just reported yesterday and today is already fixed! This is incredible and I feel very happy with the change to Preact ecosystem.

feel free to reopen if there鈥檚 anything wrong with it.

@JoviDeCroock adding this check to the test that you did, the test is failing. Is this as you expect?

    it('should support static content', () => {
        const updateSpy = sinon.spy();
        const mountSpy = sinon.spy();
        const renderSpy = sinon.spy();
+       const eventSpy = sinon.spy();

        function StaticContent({ children, element = 'div', staticMode }) {
            // if we're in the server or a spa navigation, just render it
            if (!staticMode) {
                return createElement(element, {
                    children
                });
            }

            // avoid re-render on the client
            return createElement(element, {
                dangerouslySetInnerHTML: { __html: '' }
            });
        }

        class App extends Component {
            componentDidMount() {
                mountSpy();
            }

            componentDidUpdate() {
                updateSpy();
            }

            render() {
                renderSpy();
+               return <div onClick={() => eventSpy()}>Staticness</div>;
            }
        }

        act(() => {
            render(
                <StaticContent staticMode={false}>
                    <App />
                </StaticContent>,
                scratch
            );
        });

        expect(scratch.innerHTML).to.eq('<div><div>Staticness</div></div>');
        expect(renderSpy).to.be.calledOnce;
        expect(mountSpy).to.be.calledOnce;
        expect(updateSpy).to.not.be.calledOnce;

        act(() => {
            hydrate(
                <StaticContent staticMode>
                    <App />
                </StaticContent>,
                scratch
            );
        });

        expect(scratch.innerHTML).to.eq('<div><div>Staticness</div></div>');
        expect(renderSpy).to.be.calledOnce;
        expect(mountSpy).to.be.calledOnce;
        expect(updateSpy).to.not.be.calledOnce;
+       scratch.firstChild.firstChild.dispatchEvent(new Event('click'))
+       expect(eventSpy).to.not.be.calledOnce; // fails, is called...
    });
});

I'm not sure if this is in purpose. In React these events are added on the hydration step. So dispatching the event looks that it is not avoiding the hydration step.

I can't reopen the issue, I don't have these permissions... 馃槄

That would be magic if React would add that since you set it as dangerouslySetInnerHTML which isn鈥檛 diffed

@JoviDeCroock Maybe I didn't express myself correctly. I wanted to say that in a normal hydration (not this example) the events are attached during the hydration, as react do. However, avoiding the hydration with this hack, I was expecting that the events are not attached... And they are attached as the failing example shows.

Hmm in a real SSR scenario this shouldn't happen but yes I can see why this happens in this test, I'll see if I can do something about it

@JoviDeCroock is there any way to avoid hydration for static content in older version of preact 8.1.0?

@Nandan1996 the dangerouslySetInnerHTML hack works in Preact 8. Best to upgrade though.

@Nandan1996 the dangerouslySetInnerHTML hack works in Preact 8. Best to upgrade though.

@developit Thank you for replying. I've tried this yesterday but the line at https://github.com/preactjs/preact/blob/17cc8deddd3d4e592685dfa42e832f6e8d2cb795/src/vdom/diff.js#L143 seems to remove static html injected using dangerouslySetInnerHTML on server during hydration. later it gets injected again that causes high LCP score in our case.

we're planning to revamp our product but till then we need a temp fix.

Thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jasongerbes picture jasongerbes  路  3Comments

k15a picture k15a  路  3Comments

paulkatich picture paulkatich  路  3Comments

KnisterPeter picture KnisterPeter  路  3Comments

nopantsmonkey picture nopantsmonkey  路  3Comments