6 November 2019

hookstate: How one small React Library saved MoonPiano

Ladies and Gentelmen. Below is a story of love, hate and hope.

The development of moonpiano has been stalled for over a month due to my big move to Germany for a Data Science Position at vialytics.

In case you didn’t know, MoonPiano was developed in React and Electron. The project was smooth as long as the user was supposed to play easy songs. But in case he starts to smash the keys on this piano, the app performance would get worse, playback would be crappy and unhearable, let alone the UI freezes.

Technically, I have been using Redux to manage every state. The user can practice well, but pressing way too many buttons would lead to poor app performance due to slow UI updates. This happens especially in the new challenge mode that I have been working on, where keys that the user should press were highlighted but also the keys that he pressed already. This fires 2x more updates.

I have been stuck for a couple of days to solve the issue, no solution to be found. I have moved event handling outside of Redux back the normal react props way, where a master component would listen to the events and spread them to each keyboard key, but that also had me passing the props down a couple of components to reach the Key object of the UI Keyboard.

Now I am not the best React developer out there, but I have tried a lot of things. And this was a more than a month ago.

Solution? Switch from React to something else. Maybe preact? Sure! There is even a compatibility layer with React. Neat. But even the compat layer does not fully support the react features the libraries I use. I even considered rewriting things from scratch with preact.

A final solution I had before my brain would explode is to try other Redux alternatives. A quick google search led me to this article: https://blog.logrocket.com/redux-vs-mobx/. I did read quiet a bit to be honest, but look at the final comment

This guy claims an incredible performance. With React? No way. Gotta see it with my own eyes. Quick look at the Demo and this example already got me very impressed: https://hookstate.netlify.com/performance-demo-large-table

It supports hooks from everywhere. My code does use Controllers that dispatch actions. Also this library allowed me to create unmounted hooks from my controller, and fire events from there which will be probagated down the keys. I have used this library for both highlighting keys that the user should press as well as coloring the pressed keys. The performance? Incredible. The app works smoother than I ever wanted it to be. And ofcourse, it can do much better now that I have this secret weapon.

image

This entire post, is to spread the word and make everyone aware of this awesome tool. I think it deserves more attention. Special thanks to @avkonst for making this happen.

Repository: https://github.com/avkonst/hookstate

Update (7 Nov. 2019):

As requested by in the comments, here a code that demonstrates the redux version performance. The libraries I have used are a bit outdated (I forked an existing redux code-sandbox), but the performance I encountred is pretty much the same. The update procedure is a bit naive, and the optimization I did using Hookstate is the same as this example: https://hookstate.netlify.com/performance-demo-large-form. The state holds an array but each value of the array updates a single Key on the Keyboard. Thus I had to tweak the react-piano component for this, and defintely each Piano Key recieves a new props of its index in the array state gets updated.

Warning: Overusing the demo below will overheat your Mac and probably crash your browser tab (or all tabs).

Update (8 Nov. 2019):

New video demonstrating the performance boost:

You may also like...

12 Responses

  1. Very nice article, thanks. I like the performance comparison animation demo.

    I will also check if your app is useful for my daughter. She is quite experienced with piano and has completed 6 years of learning. I was looking for an app for her, which would connect to Roland P90 piano and showed her music sheets, highlighted current position and changed the pages automatically. Having access to the digitised music sheet database would be awesome to have too. Currently we download PDFs and djvu files from various sources and print them, or get books from a teacher. An app would advance this.

    • praisethemoon says:

      Glad to hear your feedback! My app is really unique in the sense that you can use any music you can get. I personally download MusicXML files from MuseScore https://musescore.com/. Just make sure to download MusicXML extension and not MIDI. Most learning apps use MIDI but it is a format for production not suitable for rendering. MusicXML on the other hand has precise rendering information. Additionally, Some MuseScore sheets are not fully MusicXML compliant, thus cannot be loaded in the app. I cannot do much about it for now. But I am working on the music renderer as well, it is getting better. Please let me know how the app is going and especially your daughter’s feedback! Thank you very much for passing by 🙂

  2. Mark Erikson says:

    Hi, I’m a Redux maintainer. Out of curiosity, do you have a link to the earlier Redux implementation? I’d be interested in seeing what could be done to improve its performance.

    • praisethemoon says:

      Hello, thank you for your message. I am currently re-creating a basic version with Redux, the same way I had it in my app. The reason for that is that my source code is not yet public. Will reply back once it is done! Looking forward to what you can find!

      • Mark Erikson says:

        I thought I had clicked “Notify of follow-up comments by email”, but I didn’t get any notifications of this. Fortunately I saw a tweet from Andrey Konstantinov re-linking this post, and checked back to see that you’d posted a sandbox.

        Looking at the source of the sandbox, I notice that it’s not even actually _using_ React-Redux – it’s just blindly subscribing to the store. Is that how the “real” Redux implementation of this app worked? Is there any chance you can update the sandbox to better reflect how the real implementation worked?

        I realized that you’ve already moved on from Redux, so I don’t want to pester you or force you to do work that isn’t actually going to benefit your future codebase. But, I’d genuinely like to see what the actual performance looked like so I can try to diagnose any inefficiencies and at least let you know what might have worked better.

      • Mark Erikson says:

        Glancing through the code in that Redux repo, I have a few observations:

        – Per my comment, while it depends on React-Redux v6, it’s not even _using_ React-Redux. It’s blindly calling `store.subscribe()` and re-rendering the whole component tree on every store update. It’s not calling `connect()` at all.
        – Having said that, both the app component tree and the state are incredibly simplistic. There’s really only one meaningful component in the app, the state is just one array, and the entire state is just being passed down into the “ component.

        Based on that, this demo app really shouldn’t be using Redux or any other external state management at all. Component state is more than sufficient to do that.

        For that matter, I tried downloading the code, updating CRA, starting it in dev mode, and letting the app play some music to see how the perf looked. In all honesty, the highlighting update speed _seemed_ fine to me, and this was with React running in dev mode, not prod.

        Whatever perf problems you’re seeing in the real app, it doesn’t seem like they’re being reflected in this example (or at least I’m not understanding how the performance here is specifically slower than it should be).

        In general, Redux performance is optimized by:

        – Ensuring you’re using React-Redux v7. v6 had known performance limitations, as we tried to pass down state updates via context, and that was too slow. v7 goes back to using direct subscriptions, and also uses React update batching.
        – Directly connecting as many components as possible, and having each component depend on the smallest piece of state it needs in `mapState`.
        – If appropriate, “normalizing” state as lookup tables to make it easy to find items by their IDs, rather than keeping everything in arrays.

        So yeah, if you do happen to have a more realistic version of this setup, I’d be happy to take another look. With the example in its current form, I don’t think there’s much I can do with it. If you do happen to come up with a larger version of this example, please ping me on Twitter so I know about this, since the email notifications don’t seem to be working right.

    • Andrey Konstantinov says:

      Hi Mark. How should the performance demo mentioned in the article be implemented with redux? I am also very curious how to get efficient variant with redux and learn from it to apply in other projects.

      • praisethemoon says:

        I have updated the post with a forkable demo at the end of the article. I think improving it would require to add `react-piano` not as an NPM dependency but including its source so it can be tweaked. Let me know of your thoughts!

        • Mark Erikson says:

          Yeah, it’s also likely that whatever tweaks you made to `react-piano` would also be applicable to trying to use it from Redux as well. Hard to do a 1:1 comparison without know what those tweaks are.

          (also, as a side note: the comment system here keeps saying I haven’t solved a captcha correctly, but I don’t see a captcha at all. It seems to let comments through after trying once or twice, though.)

Leave a Reply

Your email address will not be published. Required fields are marked *