DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Electron Adventures: Episode 75: NodeGui React

Let's continue exploring Electron alternatives. This time, NodeGui. NodeGui uses Qt5 instead of Chromium, so we'll be leaving the familiar web development behind, but it tries to not be too far from it, as web development is what everyone knows.

Interestingly it comes with preconfigured Svelte, React, and Vue setups, but since Svelte starter doesn't work at all, we'll try out the React one.


We need to install a bunch of dependencies, not just npm packages. For OSX this one extra line of brew is required. For other OSes, check documentation.

$ brew install make cmake
$ npx degit episode-75-nodegui-react
$ cd episode-75-react-nodegui
$ npm i
Enter fullscreen mode Exit fullscreen mode

Unfortunately instead of having happy React started, what we get at this point is some T***Script abomination, so next few steps were me ripping out T***Script and putting back plain JavaScript in its place.

Start the app

To start the app we'll need to run these in separate terminals:

$ npm run dev
$ npm run start
Enter fullscreen mode Exit fullscreen mode


Stripped out of unnecessary dependencies, here's what's left:

  "name": "react-nodegui-starter",
  "main": "index.js",
  "scripts": {
    "build": "webpack -p",
    "dev": "webpack --mode=development",
    "start": "qode ./dist/index.js",
    "debug": "qode --inspect ./dist/index.js"
  "dependencies": {
    "@nodegui/react-nodegui": "^0.10.2",
    "react": "^16.13.1"
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "@babel/preset-react": "^7.10.4",
    "@nodegui/packer": "^1.4.1",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "file-loader": "^6.1.0",
    "native-addon-loader": "^2.0.1",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12"
Enter fullscreen mode Exit fullscreen mode


There's small .babelrc after removing unnecessary stuff:

  "presets": [
    ["@babel/preset-env", { "targets": { "node": "12" } }],
  "plugins": []
Enter fullscreen mode Exit fullscreen mode


And here's similarly cleaned up webpack.config.js:

const path = require("path")
const webpack = require("webpack")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

module.exports = (env, argv) => {
  const config = {
    mode: "production",
    entry: ["./src/index.jsx"],
    target: "node",
    output: {
      path: path.resolve(__dirname, "dist"),
      filename: "index.js"
    module: {
      rules: [
          test: /\.jsx?$/,
          exclude: /node_modules/,
          use: {
            loader: "babel-loader",
            options: { cacheDirectory: true, cacheCompression: false }
          test: /\.(png|jpe?g|gif|svg|bmp|otf)$/i,
          use: [
              loader: "file-loader",
              options: { publicPath: "dist" }
          test: /\.node/i,
          use: [
              loader: "native-addon-loader",
              options: { name: "[name]-[hash].[ext]" }
    plugins: [new CleanWebpackPlugin()],
    resolve: {
      extensions: [".js", ".jsx", ".json"]

  if (argv.mode === "development") {
    config.mode = "development";
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    config.devtool = "source-map"; = true;

  return config
Enter fullscreen mode Exit fullscreen mode


This is reasonably close to what we would use in plain React.

import { Renderer } from "@nodegui/react-nodegui"
import React from "react"
import App from "./app"

process.title = "My NodeGui App"
Renderer.render(<App />)
// This is for hot reloading (this will be stripped off in production by webpack)
if ( {["./app"], function() {
Enter fullscreen mode Exit fullscreen mode

Hot module reloading

Important thing to note is hot module reloading we enabled.

You can use hot module reloading in Electron as well, but you can also use Cmd-R to reload manually, so it's nice but unnecessary.

NodeGUI has no such functionality, so you're very dependent on hot module reloading for development to be smooth. Unfortunately if you ever make a syntax error in your code, you get this:

[HMR] You need to restart the application!
Enter fullscreen mode Exit fullscreen mode

And you'll need to quit the application, and start it again.

So in practice, the dev experience is a lot worse than default Electron experience.


And finally we can get to the app.

Similar to how React Native works, instead of using html elements, you need to import components from @nodegui/react-nodegui.

The nice thing is that we can declare window properties same as any other widgets, instead of windows being their own separate thing. Some APIs differ like event handling with on={{...}} instead of individual onEvent attributes.

A bigger issue is the Qt pseudo-CSS. It supports different properties from HTML (so there's now "How to center in Qt" question, which you can see below), and unfortunately it doesn't seem to support any element type or class based selectors, just attaching to an element with style or using ID-based selectors. There's probably some way to deal with this.

import { Text, Window, hot, View, Button } from "@nodegui/react-nodegui"
import React, { useState } from "react"

function App() {
  let [counter, setCounter] = useState(0)

  return (
      windowTitle="Welcome to NodeGui"
      minSize={{ width: 800, height: 600 }}
      <View style={containerStyle}>
        <Text id="header">Welcome to NodeGui</Text>
        <Text id="text">The button has been pressed {counter} times.</Text>
        <Button id="button" on={{
          clicked: () => setCounter(c => c+1)
        }}>CLICK ME!</Button>
        <Text id="html">
            <p>For more complicated things</p>
              <li>Use HTML</li>
              <li>Like this</li>

let containerStyle = `
  flex: 1;

let styleSheet = `
  #header {
    font-size: 24px;
    padding-top: 20px;
    qproperty-alignment: 'AlignHCenter';
    font-family: 'sans-serif';

  #text, #html {
    font-size: 18px;
    padding-top: 10px;
    padding-horizontal: 20px;

  #button {
    margin-horizontal: 20px;
    height: 40px;

export default hot(App)
Enter fullscreen mode Exit fullscreen mode

Overall this wasn't too bad a change from plain React. We can still structure the components the same way, use either hooks or classes for state, and also import any frontend JavaScript libraries we want.


Here's the results:

Episode 75 Screenshot

After all the work setting up Nodegui with React and plain JavaScript it would be a shame not to write a small app with it, so in the next episode we'll do just that.

As usual, all the code for the episode is here.

Top comments (0)