{"componentChunkName":"component---src-templates-blog-jsx","path":"/blog/creating-electron-react-app","result":{"data":{"mdx":{"frontmatter":{"title":"Electron and React integration done right","date":"2020-12-13","subtitle":"Creating an updated Electron + React app, learning how to communicate between them, and how to build for cross-platform","coverCredit":"Raphaël Biscaldi","coverWebsite":"Unsplash","cover":{"publicURL":"/static/07e284aeb527d9e207a9ac9f18db7f50/cover.jpg","childImageSharp":{"fluid":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAQP/xAAWAQEBAQAAAAAAAAAAAAAAAAADAQL/2gAMAwEAAhADEAAAAX15tFDw4mp//8QAGhABAAMAAwAAAAAAAAAAAAAAAgEDEQASE//aAAgBAQABBQKOpNhFvFbAlqfZg7kZ/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8BV//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAECAQE/AUf/xAAdEAACAgEFAAAAAAAAAAAAAAAAEQECITEyUWHw/9oACAEBAAY/AnJW1/QLd2RV4wJcGh//xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhMVFh/9oACAEBAAE/Ie1gfc8olgXPFeOBm7xvxn0MGMIFFfzf/9oADAMBAAIAAwAAABBsz//EABYRAQEBAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxAcI//EABYRAQEBAAAAAAAAAAAAAAAAAAEAIf/aAAgBAgEBPxB1YV//xAAcEAEAAgIDAQAAAAAAAAAAAAABETEAIUFhcYH/2gAIAQEAAT8QUAO1xgySIYopZe1jzCDgBYDidX5jXaQz0y4iT2wqb+arGTQlUXn/2Q==","aspectRatio":1.7793103448275862,"src":"/static/07e284aeb527d9e207a9ac9f18db7f50/cf77e/cover.jpg","srcSet":"/static/07e284aeb527d9e207a9ac9f18db7f50/8196e/cover.jpg 258w,\n/static/07e284aeb527d9e207a9ac9f18db7f50/26313/cover.jpg 516w,\n/static/07e284aeb527d9e207a9ac9f18db7f50/cf77e/cover.jpg 1032w,\n/static/07e284aeb527d9e207a9ac9f18db7f50/7022b/cover.jpg 1548w,\n/static/07e284aeb527d9e207a9ac9f18db7f50/14c55/cover.jpg 2064w,\n/static/07e284aeb527d9e207a9ac9f18db7f50/fd1df/cover.jpg 3024w","sizes":"(max-width: 1032px) 100vw, 1032px"}}}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Electron and React integration done right\",\n  \"author\": \"Patrick Passarella\",\n  \"date\": \"2020-12-13\",\n  \"subtitle\": \"Creating an updated Electron + React app, learning how to communicate between them, and how to build for cross-platform\",\n  \"cover\": \"./cover.jpg\",\n  \"coverCredit\": \"Raphaël Biscaldi\",\n  \"coverWebsite\": \"Unsplash\",\n  \"published\": true\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"In this post, I will show you how to create a full React and Electron app, test, and build it.\"), mdx(\"h3\", null, \"Summary\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#react-app-setup\"\n  }), \"React app setup\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#electron-app-setup\"\n  }), \"Electron app setup\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#integrating-both\"\n  }), \"Integrating both\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#communication-between-react-and-electron\"\n  }), \"Communication between React and Electron\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#testing-the-ipcrenderer-with-jest\"\n  }), \"Testing the ipcRenderer with Jest\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#adding-a-script-to-start-both-electron-and-react-simultaneously\"\n  }), \"Adding a script to start both Electron and React simultaneously\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"#building-for-cross-platform\"\n  }), \"Building for cross-platform\"))), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Electron is a framework to create cross-platform desktop apps. Ages ago it was mostly being used with plain js and html. Now we can integrate it with React. But I found it kinda troublefull, so I wrote this post to help you create a nice integration between these two great technologies.\"), mdx(\"p\", null, \"If you've never heard of Electron, I guess I can make a small tour.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Electron uses Node.js for the back-end development;\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Electron uses \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://www.chromium.org/Home\"\n  }), \"Chromium\"), \" for displaying web content;\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"It also has some custom API's to deal with OS functions.\")), mdx(\"p\", null, \"There are a few reasons to use Electron.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"To use desktop features (obviously);\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"To create cross-platform desktop apps more easily (compared to native technologies);\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Depending on the app, you can process stuff on the user-side, instead of using your server resources;\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Some apps generally work better as a desktop version, for me at least.\")), mdx(\"p\", null, \"Some popular apps developed with Electron are.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"VScode;\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Slack;\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Whatsapp.\")), mdx(\"h2\", null, \"React app setup\"), mdx(\"p\", null, \"This isn't the scope of the post, so we will use the simplest possible CRA project.\"), mdx(\"p\", null, \"You can create the project using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn create react-app my-app\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ npm init react-app my-app\"), \". If you have already created a folder, just replace \\\"my-app\\\" with a dot (current folder).\"), mdx(\"p\", null, \"We will also change the folder structure, since it's going to have the electron app in the same project, it would be a mess of files.\"), mdx(\"p\", null, \"Create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"client\"), \" (or any name you want) folder in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"src\"), \" folder, move everything except the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"index.js\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setupTests.js\"), \" (if you're using CRA) to it. Remember to also correct the imports.\"), mdx(\"p\", null, \"That's it for now for the React structure, we will be back to it shortly.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"Electron app setup\"), mdx(\"p\", null, \"Now we need to create the Electron part of the app.\"), mdx(\"p\", null, \"First, create a folder, outside of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"src\"), \" folder, named \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electron\"), \", and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main.js\"), \" file inside it. Change the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \" property in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electron/main.js\"), \". This will define the main entry point for the app.\"), mdx(\"p\", null, \"After that, add the Electron package to the project, running \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn add electron --dev\"), \" and a start script to your \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \". In the start script, we also add the environment variable containing the React app url, to access later.\"), mdx(\"h4\", null, \"package.json\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"...\\n\\\"main\\\": \\\"electron/main.js\\\",\\n\\\"scripts\\\": {\\n    \\\"start:electron\\\": \\\"ELECTRON_START_URL=http://localhost:3000 electron .\\\"\\n  }\\n...\\n\")), mdx(\"p\", null, \"Lastly, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main.js\"), \" content. I will be using the Electron default code, found on their \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.electronjs.org/docs/tutorial/first-app\"\n  }), \"documentation\"), \". With just some small tweaks.\"), mdx(\"h4\", null, \"electron/main.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const { app, BrowserWindow } = require('electron');\\nconst path = require('path');\\n\\n// Keep a global reference of the window object, if you don't, the window will\\n// be closed automatically when the JavaScript object is garbage collected.\\nlet window = null;\\n\\nfunction createWindow() {\\n  // Create the browser window.\\n  window = new BrowserWindow({\\n    width: 800,\\n    height: 600,\\n    webPreferences: {\\n      nodeIntegration: true,\\n    },\\n  });\\n\\n  // Load the index.html of the app.\\n  // This will give an error for now, since we will be using a React app instead of a file.\\n  window.loadFile('index.html');\\n\\n  // Open the DevTools.\\n  window.webContents.openDevTools({ mode: 'detach' });\\n}\\n\\napp.on('ready', () => {\\n  createWindow();\\n});\\n\\napp.on('window-all-closed', () => {\\n  if (process.platform !== 'darwin') {\\n    app.quit();\\n  }\\n});\\n\\napp.on('activate', () => {\\n  // On macOS it's common to re-create a window in the app when the\\n  // dock icon is clicked and there are no other windows open.\\n  if (BrowserWindow.getAllWindows().length === 0) {\\n    createWindow();\\n  }\\n});\\n\")), mdx(\"p\", null, \"This file is where all the electron code will be. Here, we are creating a window after the app is ready, and adding some functionality, like being able to close the window.\"), mdx(\"p\", null, \"Running \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn start:electron\"), \" should open up a blank window, and the devTools.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"Integrating both\"), mdx(\"p\", null, \"It's time to show the React app inside the Electron desktop window. For that, it's quite simple, we just render the url instead of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"index.html\"), \" file directly.\"), mdx(\"p\", null, \"So, we need to change the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createWindow\"), \" function to load the url.\"), mdx(\"h4\", null, \"electron/main.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const createWindow = () => {\\n  // Here, we are grabbing the React url from the env (which is on the start script)\\n  const startUrl = process.env.ELECTRON_START_URL;\\n\\n  window = new BrowserWindow({\\n    width: 800,\\n    height: 600,\\n    webPreferences: {\\n      nodeIntegration: true,\\n    },\\n  });\\n\\n  // And loading it in the window\\n  window.loadURL(startUrl);\\n  window.show();\\n  window.webContents.openDevTools({ mode: 'detach' });\\n};\\n\")), mdx(\"p\", null, \"Now your Electron app should load the React app in the window, try it by running \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn start\"), \" (I renamed it to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"start:client\"), \") to start the React app, and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn start:electron\"), \" again to show the window.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"Communication between React and Electron\"), mdx(\"p\", null, \"Now, another thing left to do, is to learn how to communicate between them. But you may ask, \\\"what do you mean by communication\\\"?, what can we do between Electron and React? The main.js file (the Electron part), is a node application, so you could do anything \\\"back-end\\\" related. You could fetch a database, add a notification system, use the local file-system, and so on. Technically you can use Node APIs inside the Renderer process also, but I don't think that's a good practice.\"), mdx(\"p\", null, \"Electron provides a way to communicate between the main and the renderer (React) process using modules called \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.electronjs.org/docs/api/ipc-main\"\n  }), \"ipcMain\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.electronjs.org/docs/api/ipc-renderer\"\n  }), \"ipcRenderer\"), \".\"), mdx(\"p\", null, \"Both have a few functions, like \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"send\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"on\"), \", who are used for asynchronous communication. And \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"handle\"), \" (ipcMain) and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"invoke\"), \" (ipcRenderer) for synchronous communication.\"), mdx(\"p\", null, \"For the sake of simplicity, we will just send a simple object from one to another, but you could do anything.\"), mdx(\"p\", null, \"Let's look at it step by step.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"1. Defining the channel names\")), mdx(\"p\", null, \"Create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"shared\"), \" folder, inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"src\"), \" folder. In there, is where it's gonna have the code that will be used inside the main and renderer process. So, let's also create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"constants.js\"), \" file inside it, where we define our message names.\"), mdx(\"h4\", null, \"src/shared/constants.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"module.exports = {\\n  channels: {\\n    GET_DATA: 'get_data',\\n  },\\n};\\n\")), mdx(\"p\", null, \"As you can notice, we are using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"module.exports\"), \" in there, since we are going to use it in electron also.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"2. Creating the action that will send the message to Electron main process\")), mdx(\"p\", null, \"In the React app, we will change some css, just to open up space. (you can skip this part if you are using it in your own app, obviously). I just deleted some css code, added a button and h3 style, and decentralized the logo vertically.\"), mdx(\"h4\", null, \"App.css\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-css\"\n  }), \"@media (prefers-reduced-motion: no-preference) {\\n  .App-logo {\\n    animation: App-logo-spin infinite 20s linear;\\n  }\\n}\\n\\n.App {\\n  background-color: #282c34;\\n  min-height: 100vh;\\n  color: #fff;\\n}\\n\\ninput {\\n  margin-top: 16px;\\n  padding: 8px;\\n}\\n\\nh3 {\\n  margin-left: 16px;\\n}\\n\\n.App-header {\\n  display: flex;\\n  flex-direction: column;\\n  align-items: center;\\n}\\n\\n.App-header button {\\n  border: none;\\n  padding: 10px 20px;\\n  background: #777;\\n  color: #fff;\\n  z-index: 1;\\n  margin-top: 16px;\\n  cursor: pointer;\\n  outline: none;\\n}\\n\\n@keyframes App-logo-spin {\\n  from {\\n    transform: rotate(0deg);\\n  }\\n  to {\\n    transform: rotate(360deg);\\n  }\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"App.js\"), \" file, we will add the button, and the function to send the message.\"), mdx(\"h4\", null, \"App.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import logo from './logo.svg';\\nimport './App.css';\\nimport { channels } from '../shared/constants';\\n\\nconst { ipcRenderer } = window.require('electron');\\n\\nfunction App() {\\n  const getData = () => {\\n    ipcRenderer.send(channels.GET_DATA, { product: 'notebook' });\\n  };\\n\\n  return (\\n    <div className=\\\"App\\\">\\n      <header className=\\\"App-header\\\">\\n        <img src={logo} width={200} className=\\\"App-logo\\\" alt=\\\"logo\\\" />\\n        <button onClick={getData}>Get data</button>\\n      </header>\\n    </div>\\n  );\\n}\\n\\nexport default App;\\n\")), mdx(\"p\", null, \"We need to import the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ipcRenderer\"), \" module using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"window.require\"), \", because we want to require the electron during runtime from the node environment, rather than the one used during compilation by webpack. link to the github issue \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/electron/electron/issues/7300\"\n  }), \"here\"), \".\"), mdx(\"p\", null, \"In this file, we import the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ipcRenderer\"), \", and the channels we've created in the constants file. And create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getData\"), \" function, that will be triggered by clicking on the button.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"send\"), \" method from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ipcRenderer\"), \", receives the channel name as the first parameter, and a optional data that will be sent to the main process.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"3. Receiving the data in the main process\")), mdx(\"p\", null, \"At the end of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main.js\"), \" file, we need to listen to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"get_data\"), \" event, using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ipcMain\"), \" module.\"), mdx(\"h4\", null, \"electron/main.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const { app, BrowserWindow, ipcMain } = require('electron');\\nconst { channels } = require('../src/shared/constants');\\n\\n...\\n\\n// End of the file\\nipcMain.on(channels.GET_DATA, (event, arg) => {\\n  const { product } = arg;\\n  console.log(product);\\n});\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ipcMain.on\"), \" method, also receives the channel as the first parameter, and a function as the second, that function has an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"event\"), \", and an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"arg\"), \" arguments. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"arg\"), \" is the data we've sent from the renderer process.\"), mdx(\"p\", null, \"Restart the Electron app, and click the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Get data\"), \" button, you should see the data print in the server console.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"4. Sending a response back to the renderer process\")), mdx(\"p\", null, \"Now we will mock a database call (or a file system), by just returning an object to the renderer process.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const products = {\\n  notebook: {\\n    name: 'notebook',\\n    price: '2500',\\n    color: 'gray',\\n  },\\n  headphone: {\\n    name: 'headphone',\\n    price: '700',\\n    color: 'black',\\n  },\\n};\\n\\n// End of the file\\nipcMain.on(channels.GET_DATA, (event, arg) => {\\n  const { product } = arg;\\n  event.sender.send(channels.GET_DATA, products[product]);\\n});\\n\")), mdx(\"p\", null, \"This will send an event back to the renderer process, for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"get_data\"), \" channel.\"), mdx(\"p\", null, \"We also need to change our React app to listen for the event, and show it on screen. We use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"useEffect\"), \" hook, to listen to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"get_data\"), \" channel. After the response is sent from the main process, it will get that data, and set the state, which will fill the information about the product.\"), mdx(\"p\", null, \"Here is the full file.\"), mdx(\"h4\", null, \"App.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import { useState, useEffect } from 'react';\\nimport logo from './logo.svg';\\nimport './App.css';\\nimport { channels } from '../shared/constants';\\n\\nconst { ipcRenderer } = window.require('electron');\\n\\nfunction App() {\\n  const [product, setProduct] = useState('');\\n  const [data, setData] = useState(null);\\n\\n  const getData = () => {\\n    // Send the event to get the data\\n    ipcRenderer.send(channels.GET_DATA, { product });\\n  };\\n\\n  useEffect(() => {\\n    // Listen for the event\\n    ipcRenderer.on(channels.GET_DATA, (event, arg) => {\\n      setData(arg);\\n    });\\n\\n    // Clean the listener after the component is dismounted\\n    return () => {\\n      ipcRenderer.removeAllListeners();\\n    };\\n  }, []);\\n\\n  return (\\n    <div className=\\\"App\\\">\\n      <header className=\\\"App-header\\\">\\n        <img src={logo} width={200} className=\\\"App-logo\\\" alt=\\\"logo\\\" />\\n        <input\\n          onChange={(e) => setProduct(e.target.value)}\\n          placeholder=\\\"Product name\\\"\\n        />\\n        <button onClick={getData}>Search</button>\\n      </header>\\n\\n      {data && (\\n        <>\\n          <h3>Product info</h3>\\n          <ul>\\n            <li>Name: {data.name}</li>\\n            <li>Price: {data.price}</li>\\n            <li>Color: {data.color}</li>\\n          </ul>\\n        </>\\n      )}\\n    </div>\\n  );\\n}\\n\\nexport default App;\\n\")), mdx(\"p\", null, mdx(\"span\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"gatsby-resp-image-wrapper\",\n    \"style\": {\n      \"position\": \"relative\",\n      \"display\": \"block\",\n      \"marginLeft\": \"auto\",\n      \"marginRight\": \"auto\",\n      \"maxWidth\": \"795px\"\n    }\n  }), \"\\n      \", mdx(\"span\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"gatsby-resp-image-background-image\",\n    \"style\": {\n      \"paddingBottom\": \"72.09302325581395%\",\n      \"position\": \"relative\",\n      \"bottom\": \"0\",\n      \"left\": \"0\",\n      \"backgroundImage\": \"url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAcuklgH/xAAXEAADAQAAAAAAAAAAAAAAAAAAAhIg/9oACAEBAAEFAh5z/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGBAAAgMAAAAAAAAAAAAAAAAAAAESIKH/2gAIAQEABj8CFF5X/8QAGhAAAgIDAAAAAAAAAAAAAAAAAREAICExgf/aAAgBAQABPyFLcwsE8V//2gAMAwEAAgADAAAAECAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGhABAAMAAwAAAAAAAAAAAAAAARARIQAxgf/aAAgBAQABPxBSKIPEq3FInHi7Cr2rP//Z')\",\n      \"backgroundSize\": \"cover\",\n      \"display\": \"block\"\n    }\n  })), \"\\n  \", mdx(\"img\", _extends({\n    parentName: \"span\"\n  }, {\n    \"className\": \"gatsby-resp-image-image\",\n    \"alt\": \"Electron App\",\n    \"title\": \"Electron app\",\n    \"src\": \"/static/ccee244180930865965a23f175cdf46b/dc4d4/electron_1.jpg\",\n    \"srcSet\": [\"/static/ccee244180930865965a23f175cdf46b/53f89/electron_1.jpg 258w\", \"/static/ccee244180930865965a23f175cdf46b/b6e7a/electron_1.jpg 516w\", \"/static/ccee244180930865965a23f175cdf46b/dc4d4/electron_1.jpg 795w\"],\n    \"sizes\": \"(max-width: 795px) 100vw, 795px\",\n    \"style\": {\n      \"width\": \"100%\",\n      \"height\": \"100%\",\n      \"margin\": \"0\",\n      \"verticalAlign\": \"middle\",\n      \"position\": \"absolute\",\n      \"top\": \"0\",\n      \"left\": \"0\"\n    },\n    \"loading\": \"lazy\"\n  })), \"\\n    \")), mdx(\"p\", null, \"It seems complicated, but it's quite simple, I'm gonna summarize it for you below.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the React app, we add the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcRenderer.send\"), \" on an action (or in another way), to notify Electron about something we need, and also add a listener to listen for the respective response using \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcRenderer.on\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"main\"), \" process, we add the listener to listen for the renderer process (React app) events, using \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcMain.on\"), \", and we can send back the response it is waiting for using \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"event.sender.send\"), \".\")), mdx(\"h3\", null, \"Isn't it better to just use an api?\"), mdx(\"p\", null, \"Depends, I've used this example here because it's simple, but the main focus is the communication, you can do anything with it. One thing for example that we could do, that is more useful, is an option to quit the app.\"), mdx(\"h4\", null, \"App.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"...\\n\\nconst handleQuit = () => {\\n  ipcRenderer.invoke(channels.QUIT);\\n};\\n\\nreturn (\\n  <nav>\\n    <button>Config</button>\\n    <button onClick={handleQuit}>Quit app</button>\\n  </nav>\\n);\\n\\n\")), mdx(\"h4\", null, \"electron/main.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"...\\n\\nipcMain.handle(channels.QUIT, () => {\\n  app.quit();\\n});\\n\")), mdx(\"hr\", null), mdx(\"h2\", null, \"Testing the ipcRenderer with Jest\"), mdx(\"p\", null, \"It took me a few hours to find a way to test it properly, and I don't think I found the perfect way for it, there are some questions on Stackoverflow about it, but it was complicated for me to understand and implement, so I'm gonna just comment on how I tested my Electron apps, even if it isn't the best way to do it.\"), mdx(\"p\", null, \"I'm gonna use the app I created in this post as an example.\"), mdx(\"p\", null, \"First, we need to setup our tests initialization config. Because we use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"window.require\"), \" to require electron, the tests will scream about it.\\nTo fix that, I'm gonna add this single-line config in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setupTests.js\"), \" file that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"create-react-app\"), \" makes available.\"), mdx(\"h4\", null, \"src/setupTests.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"window.require = require;\\n\")), mdx(\"p\", null, \"If you're using just Jest, you can add the same config in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jest.setup.js\"), \", and add the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setupFilesAfterEnv\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jest.config.js\"), \", \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://jestjs.io/docs/en/configuration#setupfilesafterenv-array\"\n  }), \"like that\"), \".\"), mdx(\"p\", null, \"Now, we need to mock the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ipcRenderer\"), \" used in the React app. In the same file (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setupTests\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jest.setup.js\"), \"), we can use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jest.mock\"), \" to mock the electron package. This function takes the module name, and a factory function (a function that returns an object) containing the module properties we want to mock.\"), mdx(\"h4\", null, \"src/setupTests.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"window.require = require;\\n\\njest.mock('electron', () => {\\n  return {\\n    ipcRenderer: {\\n      on: jest.fn(),\\n      send: jest.fn(),\\n      removeAllListeners: jest.fn(),\\n    },\\n  };\\n});\\n\")), mdx(\"p\", null, \"We can now start testing without initial errors. I'm gonna show you the full test file, and then explain some sections later.\"), mdx(\"h4\", null, \"index.test.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"import { render, screen } from '@testing-library/react';\\nimport userEvent from '@testing-library/user-event';\\nimport { act } from 'react-dom/test-utils';\\nimport { channels } from '../../shared/constants';\\nimport App from '../App';\\n\\nconst { ipcRenderer } = require('electron');\\n\\ndescribe('App component', () => {\\n  it('Should search for a product after clicking search', () => {\\n    render(<App />);\\n    const input = screen.getByRole('textbox');\\n    const searchButton = screen.getByRole('button');\\n    const product = 'notebook';\\n\\n    userEvent.type(input, product);\\n    userEvent.click(searchButton);\\n\\n    expect(ipcRenderer.send).toBeCalledWith(channels.GET_DATA, {\\n      product,\\n    });\\n  });\\n\\n  it('Should render the search result on the page', () => {\\n    render(<App />);\\n    const mData = {\\n      name: 'notebook',\\n      price: '2500',\\n      color: 'gray',\\n    };\\n\\n    act(() => {\\n      ipcRenderer.on.mock.calls[0][1](null, mData);\\n    });\\n\\n    expect(ipcRenderer.on).toBeCalledWith(\\n      channels.GET_DATA,\\n      expect.any(Function)\\n    );\\n\\n    expect(screen.getByText(/Name/).textContent).toEqual(`Name: ${mData.name}`);\\n    expect(screen.getByText(/Price/).textContent).toEqual(\\n      `Price: ${mData.price}`\\n    );\\n    expect(screen.getByText(/Color/).textContent).toEqual(\\n      `Color: ${mData.color}`\\n    );\\n  });\\n});\\n\")), mdx(\"p\", null, \"Let's start with the first test\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"it('Should search for a product after clicking search', () => {\\n  // 1\\n  render(<App />);\\n  const input = screen.getByRole('textbox');\\n  const searchButton = screen.getByRole('button');\\n  const product = 'notebook';\\n\\n  // 2\\n  userEvent.type(input, product);\\n  userEvent.click(searchButton);\\n\\n  // 3\\n  expect(ipcRenderer.send).toBeCalledWith(channels.GET_DATA, {\\n    product,\\n  });\\n});\\n\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Rendering the component that is being tested, getting the input and search button.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Typing the product name on the input, and hitting search.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Testing if the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcRenderer.send\"), \" is being called with the right channel and product name typed in the input, after hitting search.\")), mdx(\"p\", null, \"Now, the second test. This one is more tricky, since we need to also mock the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"on\"), \" method call, and execute it's callback.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-jsx\"\n  }), \"it('Should render the search result on the page', () => {\\n  // 1\\n  render(<App />);\\n  const mData = {\\n    name: 'notebook',\\n    price: '2500',\\n    color: 'gray',\\n  };\\n\\n  // 2\\n  act(() => {\\n    ipcRenderer.on.mock.calls[0][1](null, mData);\\n  });\\n\\n  // 3\\n  expect(ipcRenderer.on).toBeCalledWith(\\n    channels.GET_DATA,\\n    expect.any(Function)\\n  );\\n\\n  // 4\\n  expect(screen.getByText(/Name/).textContent).toEqual(`Name: ${mData.name}`);\\n  expect(screen.getByText(/Price/).textContent).toEqual(\\n    `Price: ${mData.price}`\\n  );\\n  expect(screen.getByText(/Color/).textContent).toEqual(\\n    `Color: ${mData.color}`\\n  );\\n});\\n\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Rendering the component that is being tested, and creating an object to mock the returned data.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the App component, the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcRenderer.on\"), \" is called on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"useEffect\"), \" hook, so we can already access its properties, \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcRenderer.on.mock.calls\"), \" is accessing all the calls made, it returns something like this \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"[ [ 'get_data', [Function] ] ]\"), \", which is the arguments passed to it, including the callback, in this case, I'm getting that callback and firing it, that way, it will also call the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"setData\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Testing if the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ipcRenderer.on\"), \" is being called with the right channel and a callback function.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Testing if the product information is rendered correctly on the page, and with the right data.\")), mdx(\"p\", null, \"I hope it's all clear. Another thing it's important to say, is that, technically, we shouldn't test implementations, only the UI like it's the user who is using the app. But with Electron, it's hard to test like that, since we have that communication between them.\"), mdx(\"hr\", null), mdx(\"h2\", null, \"Adding a script to start both Electron and React simultaneously\"), mdx(\"p\", null, \"As you noticed, we need to start both React (with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yarn start:client\"), \") and Electron (with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"yarn start:electron\"), \"). And, we need to always start the React app first.\\nWe can do something to solve that, and make it easier for someone to start the app.\"), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn add --dev concurrently wait-on cross-env\"), \", to install those required dependencies. Then, change your start script in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \" file.\"), mdx(\"h4\", null, \"package.json\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"\\\"scripts\\\": {\\n  \\\"start\\\": \\\"concurrently  \\\\\\\"cross-env BROWSER=none PORT=3000 react-scripts start\\\\\\\" \\\\\\\"wait-on http://localhost:3000 && ELECTRON_START_URL=http://localhost:3000 electron .\\\\\\\"\\\"\\n  ...\\n}\\n\")), mdx(\"p\", null, \"We use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"concurrently\"), \" to run two scripts at once, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"wait-on\"), \" to wait for the React app to load, and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cross-env\"), \" is used to being able to use the same env for any platform, in one command.\"), mdx(\"p\", null, \"That's it, now you only need to run one command to start your app.\"), mdx(\"h2\", null, \"Building for cross-platform\"), mdx(\"p\", null, \"Electron most powerful feature is being able to build for cross-platform easily. But, not so much with React, it's more verbose and needs some weird configuration.\"), mdx(\"p\", null, \"I'll start by installing the library \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.electron.build/\"\n  }), \"electron-builder\"), \". And back again with the step-by-step process.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"1. Configuring the package.json\")), mdx(\"p\", null, \"We need to add two new properties to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \" file, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"build\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"homepage\"), \". \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"build\"), \" have some general configuration, and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"homepage\"), \" is the path for the built app to be served from a subdirectory, React sets the root path based on this setting.\"), mdx(\"h4\", null, \"package.json\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"...\\n\\\"homepage\\\": \\\"./\\\",\\n\\\"build\\\": {\\n    \\\"productName\\\": \\\"Sample App\\\",\\n    \\\"appId\\\": \\\"com.company.sample-app\\\",\\n    \\\"files\\\": [\\n      \\\"build/**/*\\\",\\n      \\\"node_modules/**/*\\\",\\n      \\\"dist/\\\",\\n      \\\"package.json\\\"\\n    ],\\n    \\\"directories\\\": {\\n      \\\"output\\\": \\\"release\\\"\\n    }\\n  }\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"productName:\"), \" The name that will be shown on the executable.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"appId:\"), \" Necessary if building for mac.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"files:\"), \" Specifies which resource files to include when creating the package.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"directories:\"), \"The platform executable output folder.\"), mdx(\"p\", null, \"We also need to add the packing scripts, to build React, Electron, and use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electron-builder\"), \" to build it.\"), mdx(\"h4\", null, \"package.json\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"\\\"scripts\\\": {\\n    \\\"build:client\\\": \\\"react-scripts build\\\",\\n    \\\"build:electron\\\": \\\"rm -rf build/src build/shared && mkdir build/src && cp -r electron/. build/electron && cp -r src/shared/. build/src/shared\\\",\\n    \\\"build:electron-win\\\": \\\"rm -rf build/src build/shared && mkdir build/src && robocopy electron build/electron /S & robocopy src/shared build/src/shared /S\\\",\\n    \\\"pack:linux\\\": \\\"electron-builder -c.extraMetadata.main=build/electron/main.js --publish never\\\",\\n    \\\"pack:windows\\\": \\\"electron-builder --win -c.extraMetadata.main=build/electron/main.js --publish never\\\",\\n  },\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"build:client:\"), \" Build the React app normally.\", mdx(\"br\", {\n    parentName: \"p\"\n  }), \"\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"build:electron:\"), \" Here, I'm copying the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electron\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"src/shared\"), \" folder to the build folder. We do that because we need to access this from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"react-builder\"), \" later'.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"build:electron-win:\"), \" The same as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"build:electron\"), \", except that it's a script for windows.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"pack:linux:\"), \" Executing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electron-builder\"), \" to build electron. Here I needed to use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"-c.extraMetadata.main\"), \" property, to change the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \" property to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"build/electron/main.js\"), \", to point to the correct build files which we moved in the script before.\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"pack:windows:\"), \" The same as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pack:linux\"), \", except that it's to build for windows. Notice the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"--win\"), \" flag.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"2. Rendering the compiled react code for production\")), mdx(\"p\", null, \"For running the project locally, we render the React app url into Electron, but for production, we will use the compiled react app, which is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"index.html\"), \" file located in the build folder.\"), mdx(\"p\", null, \"It's just some adjustments in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main.js\"), \" file.\"), mdx(\"h4\", null, \"electron/main.js\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const url = require('url');\\nconst createWindow = () => {\\n  const startUrl =\\n    process.env.ELECTRON_START_URL ||\\n    url.format({\\n      pathname: path.join(__dirname, '../index.html'),\\n      protocol: 'file:',\\n      slashes: true,\\n    });\\n\\n  window = new BrowserWindow({\\n    width: 800,\\n    height: 600,\\n    webPreferences: {\\n      nodeIntegration: true,\\n    },\\n  });\\n\\n  window.loadURL(startUrl);\\n  window.show();\\n  window.webContents.openDevTools({ mode: 'detach' });\\n};\\n\")), mdx(\"p\", null, \"As you can see, I just added the index.html path when there is no \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ELECTRON_START_URL\"), \" env. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"path.join(__dirname, '../index.html')\"), \" grabs the index.html path, because the main entry point (the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \" property from \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \") is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"build/electron/main.js\"), \" file, doing a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"../index.html\"), \" will get it from the build folder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"3. Creating the executable\"), \"\\nTo create the executable, I choose to use Docker. The reason, is that we need different setups for each operating system without it. For example, Linux building for windows would require you to install Wine.\"), mdx(\"h4\", null, \"Building for Linux\"), mdx(\"p\", null, \"Just run this (very big) docker script, and it will run this docker image in your local machine.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {}), \"docker run --rm -ti \\\\\\n --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_') \\\\\\n --env ELECTRON_CACHE=\\\"/root/.cache/electron\\\" \\\\\\n --env ELECTRON_BUILDER_CACHE=\\\"/root/.cache/electron-builder\\\" \\\\\\n -v ${PWD}:/project \\\\\\n -v ${PWD##*/}-node-modules:/project/node_modules \\\\\\n -v ~/.cache/electron:/root/.cache/electron \\\\\\n -v ~/.cache/electron-builder:/root/.cache/electron-builder \\\\\\n electronuserland/builder\\n\")), mdx(\"p\", null, \"Inside Docker, run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn && yarn pack:linux\"), \".\", mdx(\"br\", {\n    parentName: \"p\"\n  }), \"\\n\", \"After that, a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"release\"), \" folder (which is the folder we defined in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"build\"), \" property inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \") will be created in the project root, which will have the app in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppImage\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".deb\"), \" format for Linux.\"), mdx(\"h4\", null, \"Building for Windows\"), mdx(\"p\", null, \"It's almost the same thing, except that we need another image, for Linux we are using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electronuserland/builder\"), \", and for Windows we need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"electronuserland/builder:wine\"), \", which has Wine installed within it.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {}), \"docker run --rm -ti \\\\\\n --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_') \\\\\\n --env ELECTRON_CACHE=\\\"/root/.cache/electron\\\" \\\\\\n --env ELECTRON_BUILDER_CACHE=\\\"/root/.cache/electron-builder\\\" \\\\\\n -v ${PWD}:/project \\\\\\n -v ${PWD##*/}-node-modules:/project/node_modules \\\\\\n -v ~/.cache/electron:/root/.cache/electron \\\\\\n -v ~/.cache/electron-builder:/root/.cache/electron-builder \\\\\\n electronuserland/builder:wine\\n\")), mdx(\"p\", null, \"Inside Docker, run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$ yarn && yarn pack:windows\"), \".\", mdx(\"br\", {\n    parentName: \"p\"\n  }), \"\\n\", \"The same \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"release\"), \" folder will be created, but with Windows .exe executable.\"), mdx(\"p\", null, \"You can check out more about building with Docker for other platforms in the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.electron.build/multi-platform-build#docker\"\n  }), \"electron-builder documentation\"), \".\"), mdx(\"hr\", null), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"If everything occurred right, you should have a working Electron + React app to share!\"), mdx(\"p\", null, \"As you can see, in the end, it's not that complicated, but also there is a lot to learn and implement to not have a headache working with Electron and React. I hope it was useful for you. I will update this post if some change that could break the app happens.\"));\n}\n;\nMDXContent.isMDXComponent = true;","excerpt":"In this post, I will show you how to create a full React and Electron app, test, and build it. Summary React app setup Electron app setup…","fields":{"slug":"creating-electron-react-app"}}},"pageContext":{"slug":"creating-electron-react-app","previous":{"fields":{"slug":"how-to-position-electron-tray"}},"next":{"fields":{"slug":"making-decisions-with-decision-matrix"}}}},"staticQueryHashes":["2337317332","3351591964"]}