dsuryd / dotNetify

Simple, lightweight, yet powerful way to build real-time web apps.
https://dotnetify.net
Other
1.17k stars 164 forks source link

componentDidMount() fires before the websocket has connected. #186

Closed illumen closed 5 years ago

illumen commented 5 years ago

Hi!

I've noticed that the componentDidMount() method fires before the actual websocket connection has been established. This causes any dispatches fired from the componentDidMount() method to fail.

Is there any way to make sure componentDidMount() does not fire before the connection?

componentDidMount() {
    this.vm.$dispatch({ ... });
    console.log("componentDidMount() fired.");
}

leads to:

Information: Normalizing '/dotnetify' to 'http://localhost:5000/dotnetify'.
componentDidMount() fired.
Information: WebSocket connected to ws://localhost:5000/dotnetify?id=...
dsuryd commented 5 years ago

Short answer is no, you can't make a React lifecycle method wait for an asynchronous process. But why call dispatch right after the component is rendered? If you only need to initialize your view model with data from the client-side, then you can do this by passing it through the connect argument (see #178).

illumen commented 5 years ago

Ah ok, I see.

I do need to initialize my view model with data from the client-side. However, in my case, If I go from RouteA to RouteB, the vmArg argument has no effect since RouteA is destroyed after RouteB has fired its constructor.

class RouteA extends React.Component {
  constructor(props) {
    super(props);
    this.vm = dotnetify.react.connect("RouteA_VM", this);
  }

  componentWillUnmount() {
    this.vm.$destroy();
    console.log("RouteA destroyed");
  }
}
class RouteB extends React.Component {
  constructor(props) {
    super(props);
    this.vm = dotnetify.react.connect("RouteB_VM", this, { vmArg: { ... } });
    console.log("RouteB constructor fired");
  }

  componentWillUnmount() {
    this.vm.$destroy();
    console.log("RouteB destroyed");
  }
}

This results in:

RouteB constructor fired
RouteA destroyed

As such, the vmArgs of RouteB has no effect. I could however handle this by placing the connect method in RouteB's componentDidMount method. Is this the correct way?

dsuryd commented 5 years ago

It's not clear to me in your example how RouteA is affecting RouteB, but to your question, placing the connect method in componentDidMount is correct.

illumen commented 5 years ago

It's not clear to me in your example how RouteA is affecting RouteB, but to your question, placing the connect method in componentDidMount is correct.

The consequence of having the connect method in the constructor is that it is fired before any previous component has had time to fire componentDidUnmount (and any vm.$destroy). This leads to vmArgs not being properly sent to the view model.

The correct solution is as you said, placing the connect method in componentDidMount. Perhaps we could mention this, or change the examples? F.ex. this is the official example:

import React from 'react';
import dotnetify from 'dotnetify';

class MyApp extends React.Component {
   constructor(props) {
      super(props);
      dotnetify.react.connect("HelloWorld", this);
      this.state = { Greetings: "" };
   }
   render() {
      return <div>{this.state.Greetings}</div>
   }
}
dsuryd commented 5 years ago

I don't see it in that code, but you seem to imply that the argument value for vmArg in RouteB is dependent on RouteA completing its componentDidUnmount? In terms of design, it would be better to maintain decoupling when possible. But in any case, there's no restriction / correct way in which React lifecycle hook we want to call the connect, it just depends on how the component is designed. In most cases, placing it in the constructor will suffice.