ruby-hyperloop / ruby-hyperloop.io

The project has moved to Hyperstack!! - Ruby Hyperloop Website and Documentation
https://hyperstack.org/
22 stars 18 forks source link

client component doesnt match rendered component explanation #94

Open catmando opened 6 years ago

catmando commented 6 years ago

Users get this message:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server: and wonder what it is

The message comes right out of react, so you would think you could find a nice SO explanation but I didn't.

So here goes.

First a little background on server-side rendering (or prerendering.) Server-side rendering works by running your hyperloop components in a javascript engine on the server, and saving the resulting HTML output. This is then shipped down to the client as the initial page view. So server-side rendering makes your hyperloop code work just like any other view templating engine.

Once the page is loaded on the client, react then renders your components again. If the checksum of the stuff it just rendered matches the checksum of the server rendered code then it can simply attach event handlers without updating the DOM.

If the checksums don't match then it has to update the DOM, and it gives you a warning to let you know that something might be wrong.

How does this happen? Consider the following component:

class TheTime < Hyperloop::Component
  render(DIV) do
    "The time is #{Time.now}"
  end
end

In this case the time will very likely be different between the server and client and so you will get the above warning.

These situations can almost always be fixed by moving some initialization to after_mount. This is because after_mount only runs on the client, never during pre-rerendering.

class TheTime < Hyperloop::Component
  after_mount do 
    mutate.time Time.now
  end
  render(DIV) do
    "The time is #{state.time}" 
  end
end

This will fix the problem because during both pre-rerendering and client rendering state.time will be nil and the generated html will be <SPAN>The time is </SPAN>. Then after client rendering is complete after_mount will run, the time state will be mutated causing a re-rerender.

It might appear that we have not accomplished anything because end up rendering TheTime component 3 times, and updating the DOM. However if TheTime is just one component buried in a larger app then we have accomplished a lot, because only TheTime has to be re-rerendered and not the whole component tree.