dsuryd / dotNetify-react-template

Real-time React SPA template using dotNetify.
https://dotnetify.net/react
Apache License 2.0
203 stars 57 forks source link

Problem making the Traffic widget work with Rx Buffer() #29

Closed fastrocket closed 5 years ago

fastrocket commented 6 years ago

So I made one test change to the demo project to the Traffic widget mock data source:

Traffic = Observable
            .Interval(TimeSpan.FromMilliseconds(50))
            .StartWith(0)
            .Select(_ => _random.Next(200, 300))
            .Buffer(TimeSpan.FromMilliseconds(1000))
            .Where(x => x.Any()).Select(x => x.ToArray());

        var sub = Traffic.Subscribe(x =>
        {
            Console.WriteLine($"x has {x.Length} entries");
        });

I can confirm that the test subscription above is seeing an array of int.

But the javascript console shows these errors when trying to display the UI:

Uncaught TypeError: Cannot read property 'fn' of undefined at setTransitionEndSupport (util.js:56) at util.js:147 at util.js:10 at bootstrap.bundle.js:9 at bootstrap.bundle.js:10 react-dom.development.js:16634 Download the React DevTools for a better development experience: https://fb.me/react-devtools Utils.js:196 Information: Normalizing '/dotnetify' to 'http://localhost:63922/dotnetify'. Utils.js:196 Information: WebSocket connected to ws://localhost:63922/dotnetify?id=kIUbBTeyreXBACvNBYnENw client.js:92 [HMR] connected Averages.js:26 Uncaught TypeError: Cannot read property 'length' of null at Averages (Averages.js:26) at updateFunctionalComponent (react-dom.development.js:8317) at beginWork (react-dom.development.js:8980) at performUnitOfWork (react-dom.development.js:11814) at workLoop (react-dom.development.js:11843) at HTMLUnknownElement.callCallback (react-dom.development.js:100) at Object.invokeGuardedCallbackDev (react-dom.development.js:138) at invokeGuardedCallback (react-dom.development.js:187) at replayUnitOfWork (react-dom.development.js:11318) at renderRoot (react-dom.development.js:11885) react-dom.development.js:9643 The above error occurred in the component: in Averages (created by Dashboard) in div (created by Dashboard) in div (created by Dashboard) in div (created by Dashboard) in MuiThemeProvider (created by Dashboard) in Dashboard

Consider adding an error boundary to your tree to customize error handling behavior. Visit https://fb.me/react-error-boundaries to learn more about error boundaries. logCapturedError @ react-dom.development.js:9643 react-dom.development.js:12431 Uncaught TypeError: Cannot read property 'length' of null at Averages (Averages.js:26) at updateFunctionalComponent (react-dom.development.js:8317) at beginWork (react-dom.development.js:8980) at performUnitOfWork (react-dom.development.js:11814) at workLoop (react-dom.development.js:11843) at renderRoot (react-dom.development.js:11874) at performWorkOnRoot (react-dom.development.js:12449) at performWork (react-dom.development.js:12370) at performSyncWork (react-dom.development.js:12347) at requestWork (react-dom.development.js:12247)

I've gone through many permutations including removing the Where() check, the ToArray(), etc. to no avail. Any help appreciated! I'd like to use Buffer() if possible. I'm currently having to use the subscriber to dump the array into another observable which isn't elegant.

dsuryd commented 6 years ago

The client shouldn't care what Rx API use in the back-end, as long as it provides data in the expected format. This line here: Averages.js:26 Uncaught TypeError: Cannot read property 'length' of null

Is this your React component? it seems that it expects an array, but is given null. Remember, on initial render, the component hasn't received the state from the server yet, so it's important to define initial client state to a proper (empty) array object.

fastrocket commented 6 years ago

My Averages.js is an exact copy of Traffic.js (I forgot to capture the errors with stock demo). Here are the errors from using an unchanged Traffic.js:

Download the React DevTools for a better development experience: https://fb.me/react-devtools Utils.js:196 Information: Normalizing '/dotnetify' to 'http://localhost:50263/dotnetify'. Utils.js:196 Information: WebSocket connected to ws://localhost:50263/dotnetify?id=5PjSCZAnS9dLIhAB_E29_g client.js:92 [HMR] connected Traffic.js:26 Uncaught TypeError: Cannot read property 'length' of null at Traffic (Traffic.js:26) at updateFunctionalComponent (react-dom.development.js:8317) at beginWork (react-dom.development.js:8980) at performUnitOfWork (react-dom.development.js:11814) at workLoop (react-dom.development.js:11843) at HTMLUnknownElement.callCallback (react-dom.development.js:100) at Object.invokeGuardedCallbackDev (react-dom.development.js:138) at invokeGuardedCallback (react-dom.development.js:187) at replayUnitOfWork (react-dom.development.js:11318) at renderRoot (react-dom.development.js:11885) react-dom.development.js:9643 The above error occurred in the component: in Traffic (created by Dashboard) in div (created by Dashboard) in div (created by Dashboard) in div (created by Dashboard) in MuiThemeProvider (created by Dashboard) in Dashboard

Consider adding an error boundary to your tree to customize error handling behavior. Visit https://fb.me/react-error-boundaries to learn more about error boundaries. logCapturedError @ react-dom.development.js:9643 react-dom.development.js:12431 Uncaught TypeError: Cannot read property 'length' of null at Traffic (Traffic.js:26) at updateFunctionalComponent (react-dom.development.js:8317) at beginWork (react-dom.development.js:8980) at performUnitOfWork (react-dom.development.js:11814) at workLoop (react-dom.development.js:11843) at renderRoot (react-dom.development.js:11874) at performWorkOnRoot (react-dom.development.js:12449) at performWork (react-dom.development.js:12370) at performSyncWork (react-dom.development.js:12347) at requestWork (react-dom.development.js:12247)

Steps to repro:

Traffic = Observable
            .Interval(TimeSpan.FromMilliseconds(50))
            .StartWith(0)
            .Select(_ => _random.Next(200, 300))
            .Buffer(TimeSpan.FromMilliseconds(1000))
            .Where(x => x.Any()).Select(x => x.ToArray());

        var sub = Traffic.Subscribe(x =>
        {
            Console.WriteLine($"x has {x.Length} entries");
        });
dsuryd commented 6 years ago

With that code, the initial Traffic value is null, that's why the client breaks. You need to add a check on the component, and only render the component if the incoming value isn't null (or replace the value with an empty array).

fastrocket commented 6 years ago

Thanks for the pointer! I was able to fix the problem by adding the null checks in Traffic.js, and it occurred to me I could append

         .StartWith(new[] { 0 });

at the end of the Observable initialization in MockLiveDataServices.cs, and either solution worked.

I thought the initial setting of

this.state = {
  Traffic: [],

in Dashboard.js would take care of the first value? Answering my own question, here, it was nulls, not an empty initial array.

So it seems that this code:

     // Regulate data update interval to no less than every 200 msecs.
     _subscription = Observable
        .Interval(TimeSpan.FromMilliseconds(200))
        .StartWith(0)
        .Subscribe(_ => PushUpdates());

sends nulls to React if any Observable has not emitted any values?

Anyway, thanks again! This has been a fun crash course in Rx and React.