FrakenApp #1: Part 7, React
After getting Webpack and Electron to play nicely together in the last post, getting React up and running was easy. Too easy, in fact. I could create a .jsx
file and render it with ReactDOM.render()
, recreate many examples from various tutorials from across the web in my Electron app, etc.
Things went sideways for me when I tried to receive Inter-Process Communication (IPC) messages from the main Electron process into my React component. It was all in my mind; I needed a shift in perspective. I had trouble visualizing where to put the two ipcRenderer
calls.
I use final state of the previous article as the starting point.
Getting React and Electron to play nicely together
Putting Messages Received into a State variable
In the earlier versions of the Phone Home App, I would embed any messages I received through ipcRenderer.on('received-data'
into the DOM. The full code for Dialog.jsx
is here
This time I put this data into a state variable, along with a couple of other variables that I needed to keep track of other parts of the app.
class Dialog extends Component {
constructor(props) {
super(props);
this.state = {message: "", returnedMessages: [], maxMessageId: 0};
where:
message
: bound to the text boxreturnedMessages
: the actual listmaxMessageId
: the ID of the last message
Working with ipcRenderer in Dialog.jsx
After much experimentation, I could make ipcRenderer
calls inside the React component! Sending is easy; I call ipcRenderer.send()
from within the event function.
The hard part is getting messages back from the Main process; where do I call ipcRenderer.on()
? I ended up calling ipcRenderer.once()
after the send call. once()
is like on()
except that the listener is automatically removed after it is invoked a single time, which works well in this case because I don't need to clean up after I receive the one message I expect.
So the code looks like this:
At the top of the .jsx
file I added:
const ipcRenderer = require('electron').ipcRenderer;
I made all of the calls to ipcRenderer
in the SendMessage()
method:
// This logic is used in 2 places
SendMessage (messageValue) {
if (messageValue) {
// Send the message home, the Main process will send it on
ipcRenderer.send('phoneHome', messageValue);
// This does not survive the closure
const myComponent = this;
// Get the response back from the Main process
// once recieves a single message, it is like a one time on().
ipcRenderer.once('received-data', function (evt, message) {
// Add this item to returnedMessages, React will rerender the UI
const id = myComponent.state.maxMessageId + 1;
const newMessages = [...myComponent.state.returnedMessages,
{id, content: message}];
myComponent.setState({returnedMessages: newMessages});
myComponent.setState({maxMessageId: id});
// Clear the phoneHome textBox
myComponent.setState({message: ""});
});
}
}
Transpiling the .jsx
file(s)
React uses Babel to translate the JSX files into plain JavaScript. So I need to add a rule to tell Webpack to use the babel-loader
on all .jsx
files. To do this, I added the following to the rules array in my webpack.config.js
file:
{
test: /\.jsx?$/,
loader: 'babel-loader'
}
So now Webpack knows what to do. I also need to tell Babel what to do by adding a new file called babel.config.js
module.exports = {
presets:[
"@babel/preset-env",
"@babel/preset-react"
]
}
Cutting renderer.ts
down to size
This file got smaller. All I do is call ReactDOM.render()
. I use React.createElement(Dialog)
instead of '` because the latter wouldn't work in TypeScript, and I didn't want to spend the time to figure out what's wrong.
import Dialog from './components/Dialog.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
// Couldn't get "<React />" to work with TypeScript
React.createElement(Dialog),
document.getElementById('app-content')
);
And cutting index.ejs
down too
All I need is a div with the id of app-content
for React to find.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Phone Home App</title>
</head>
<body>
<h1>Phone Home App</h1>
<br />
<div id="app-content"></div>
</body>
</html>
Added Dev Dependencies
the Phone Home App has the same dependencies as the last post plus these:
@babel/core
@babel/preset-env
@babel/preset-react
@types/react
@types/react-dom
fs
path
react
react-dom
Progress in the FrakenApp #1
What I have
- The app uses some TypeScript
- Still using Webpack
- Still have the .NET Core part.
What's still missing
- No Oracle.
- Backsliding on TypeScript, used a
.jsx
file. - Not building the Electron App; I'm running it from VS Code.
- Didn't test it on the Mac this cycle
The Code
The code is available on my GitHub as Frakenapp-01 or the Release for this article