Will ReactDOM.hydrate() trigger lifecycle methods on the client? - reactjs

From the React 16 docs about ReactDOM.hydrate(),
Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Will ReactDOM.hydrate() also trigger lifecycle methods on the client such as componentWillMount(), componentDidMount() during initial render?
Will render() method be called on the client during hydration? I suppose not, because that's the difference between ReactDOM.render() and ReactDOM.hydrate()?
If render method won't be called on the client, we wouldn't expect componentDidMount() lifecycle method to be triggered.
If none of the lifecycle methods are called on the client, how would we know when has React finished rendering. I suppose the callback in the following syntax:
ReactDOM.hydrate(element, container[, callback])
I want to understand if there are lifecycle methods / hooks (which give more control over the application) available when React is "attempting to attach event listeners to existing markup".

Since ReactDOM.hydrate is (and should be) called on the client then YES it is supposed to run componentDidMount. componentWillMount is already called when rendered on the server.
componentDidMount does not run on the server, therefore when you call hydrate, the app runs the event.
Think about hydrate as a different render method. It does render but not in the same way. It looks for mismatches between your server rendered React and your client React. It does not render everything again.
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content (such as timestamps), but you should treat mismatches as bugs and fix them
However you might want to do some crazy stuff like rendering something completely different on the client side (than what was rendered on the server). For this pay attention to this paragraph
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.
So as you can see it does a render pass. If there are no mismatches React is optimized for that.
I hope it was clarifying. I speak from experience with React SSR and basic understanding of reading the docs.

The rendered elements probably aren't same between server and client, because initially the elements are rendered into texts at the server in memory, therefore they are not mounted. When the content is moved to client, it can be re-attached to react via hydrate which is fake "render" to wire with the rest of react functionalities, such as events.
In order to tell when it's hydated, here's a piece from internet which I found clearly stated the above rational. https://dev.to/merri/understanding-react-ssr-spa-hydration-1hcf?signin=true
const HydrateContext = createContext('hydrated')
export function useIsHydrated() {
return useContext(HydrateContext)
}
export function IsHydratedProvider({ children }) {
const [isHydrated, setIsHydrated] = useState(false)
useEffect(() => {
setIsHydrated(true)
}, [])
return (
<HydrateContext.Provider value={isHydrated}>
{children}
</HydrateContext.Provider>
)
}
To use it,
function MyComponent() {
const isHydrated = useIsHydrated()
return !isHydrated ? 'Initial render' : 'SPA mode'
}
function App() {
return (
<IsHydratedProvider>
<MyComponent />
</IsHydratedProvider>
)
}
It feels to me, any rendered component teleports from the server to the client.
p.s Here's another article which talks about the second render after the mount, https://medium.com/swlh/how-to-use-useeffect-on-server-side-654932c51b13

I read the type of ReactDOM.hydrate in TypeScript system:
(
element: SFCElement<any> | Array<SFCElement<any>>,
container: Container| null,
callback?: () => void
): void;
And example to the above declaration:
ReactDOM.hydrate(
<App />, // element
document.getElementById('root'), // container
() => { // callback
/* do what you want after hydration */
}
);

Related

How to mutate the dom without breaking React

I'm using a library with a component that returns a bunch of HTML. Some of the HTML is ok, and some of it needs changed after rendering.
I'm aware this is unadvised however I am in a situation where this kind of hacky method seems necessary.
My problem is that following the dom mutations, React seems to break. Re-renders no longer occur, and there are no errors in the console to indicate why. This is obviously due to the mutations, but I think there must be some way to do this without breaking React. I've tried various things including using a ref and eg useLayoutEffect.
I have create a minimal-ish repro here:
https://codesandbox.io/s/busy-sea-mz04cr
couple of things here from your sandbox:
Re-renders are still occurring in both the App and MyComponent when you press the buttons, you can prove it by adding a console.log("XXX has re-rendered") in each component
Updating the DOM via a ref will not cause React to re-render
Your custom hook will always only ever execute once (when MyComponent mounts) because the dependencies never change
Your state changes (button presses) are still firing events and updating state but your DOM update via ref removes the rendering of the data variable
Going from
<div id="data">
<p>Data: {data}</p>
</div>
to
<div id="data">
<p>Data: mutation one</p>
</div>
So every button press, your <MyComponent /> is updating and rendering the same static JSX.
Here's a modified sandbox that passes the data state variable to the effect to cause it to trigger on button change + appendChild via ref instead replaceChild so we can see the re-renders
Some things that might be helpful:
Updating the DOM via refs will undoubtedly lead to problems, maybe you should parse the HTML that is returned from your mentioned library and render it with dangerouslySetInnerHTML (assuming it's safe to do so and is from a trusted source otherwise it opens potential exploits)
<div dangerouslySetInnerHTML={{__html: data}} />
Effects (useEffect and useLayoutEffect) are typically reserved for keeping your app in sync with external systems or other non-react effects, you might be able to create an event handler to achieve your goal
function MyComponent() {
const [data, setData] = useState("");
const handleButtonPressed = (html) => {
const fixedHtml = // ... make your changes to the HTML
setData(fixedHtml);
};
return (
<div>
<button onClick={handleButtonPressed}>some button</button>
<p>{data}</p>
</div>
);
}
}
Hope that helps, the new version of the React docs is a fantastic resource.

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

ReactJS - Triggering Re-render from external Process

Im very new at React and Frontend development. Its literally my first Project now and I have design problem listening to external events. So basically I want to build a UI that only changes on external Events, meaning you control it with another Process (e.g. an AI that triggers the changes). The App should listen to incoming messages and depending on the message it should update the UI.
My idea was to make the Component, that receives the messages from outside, an observable and notify the MainApp of the React-Ui. The following code should give an idea to my approach.
export default class App extends Component {
constructor(props){
super (props);
this.state = {mode: "idle"};
this.observable = new Observable();
this.observable.add((m) => mode(m));
}
mode(m){
this.setState({
mode: m
});
}
render() {
return (
<div>
<Home/>
<ComponentA mode={this.state.mode}/>
<ComponentB mode={this.state.mode}/>
</div>
)
}}
My Question now is, Is this a good way to update the UI or are there maybe better ways or pattern that I can use or that are common in Frontend-Development?
Your approach is totally valid, I don't see any issues with it.
You could try initializing the observable in a lifecycle method instead like componentDidMount. You could even use redux to manage the data passed from the observable.

When testing a React component with Enzyme is it better to use simulate or to call the method directly on the instance()?

If you have a component like this
class Editor extends Component {
handleChange() {
// some code
}
render() {
<div>
<input className="Editor" onChange={this.handleChange} />
</div>
}
}
Is it better to test the handle change by simulating the change event with simulate like this:
wrapper.simulate('change', { // })
Or by calling the method directly by using instance:
wrapper.instance().handleChange()
If you are using Shallow Rendering then .simulate just tries to find the right prop and call it. From the Common Gotchas section for .simulate:
Even though the name would imply this simulates an actual event, .simulate() will in fact target the component's prop based on the event you give it. For example, .simulate('click') will actually get the onClick prop and call it.
There isn't any advantage to calling .simulate when using Shallow Rendering and simply calling the prop directly avoids issues caused by the event not mapping to the correct prop.
If you are using Full DOM Rendering then .simulate will fire an event that ends up calling runEventsInBatch from the comically named ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events.
So using .simulate with mount will actually simulate the event, just note from the Common Gotchas section that:
ReactWrapper will pass a SyntheticEvent object to the event handler in your code. Keep in mind that if the code you are testing uses properties that are not included in the SyntheticEvent, for instance event.target.value, you will need to provide a mock event...for it to work.
For Full DOM Rendering it is up to you to determine if there is any value in having ReactDOM simulate the event to call your handler or to just call your handler directly.
From this post by an Airbnb dev:
In general, I've found it's best to invoke the prop directly and avoid .simulate.
For your test it would look something like this:
test('onChange', () => {
const wrapper = shallow(<Editor />); // works with shallow or mount
const onChangeHandler = wrapper.find('input').prop('onChange');
// test onChangeHandler
})

React.js shouldComponentUpdate() and react-router Link

I currently have a doubt about the correct combined implementation of react-router Link navigation and shouldComponentUpdate() on the root application level.
That is, I have a root component called App.jsx which contains a global component with a header, footer, sidebar etc and this same component has an ajax long-poll which retrieves new registrations in the system and updates the state when new users register.
Since I don't want to push a re-render to the component (and therefore all it's children) on ajax responses that don't have updates I decided to make use of the lovely shouldComponentUpdate() method.
So, I came up with something like this - noting that I'm making use of lo-dash:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState);
}
With this the component correctly ignores irrelevant responses about the latest registrations.
Now, the problem appears when I have to make the routing. To clarify before, this is the kind of structure of the render():
Note: the _routerTransitionKey is just a helper I have to not make transitions when I'm navigating internal views state and it's working correctly.
<Grid key='app' id="wrapper" className="no-padding">
<Header user={this.state.user} allRegistrations={this.state.allRegistrations}/>
<section id="page-wrapper">
<NotificationArea key='internalNotification' />
<RouteHandler key={_routerTransitionKey} user={this.state.user} allRegistrations={this.state.allRegistrations}/>
</section>
</Grid>
Because I have the RouteHandler inside this global component, I have the issue that a change in the route is completely ignored by it, since the application state itself didn't change. That causes the component to never trigger the render() on navigation and therefore never update the RouteHandler.
What I needed would be something like:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState) || ROUTE_CHANGED ;
}
My question is: does anybody out there knows of a clever approach to this issue? I'm trying to avoid having to create yet another wrapping component to handle the Routes before they reach this App component I currently have...
So, after the tip from #WayneC, even though the react-router doesn't inject the props directly into the react component props, there's a possible way inspired by that approach.
I achieved what I wanted by doing a slight change using not the this.props, but instead the this.context.router.getCurrentPath()
So now the solution looks like this:
shouldComponentUpdate (/*nextProps*/, nextState) {
return !_.isEqual(this.state,nextState) || this.context.router.getCurrentPath() !== _routerTransitionKey;
}
Just to make it clearer, my _routerTransitionKey gets its value from an imported Util that looks mostly like this:
var Utils = {
Router: {
TransitionKey: {
get: function(){
return Router.HistoryLocation.getCurrentPath();
}
}
}
}
_routerTransitionKey = Utils.Router.TransitionKey.get();
This _routerTransitionKey is scoped in an upper level, and I modify it on every render(), so that I keep track of it for later comparison.
And... that's it.

Resources