How do you all handle the case of Multiple Windows in the boilerplate?
I'm considering removing all the renderer specific code from the app folder and then create a new folder for the primary window and another folder for the secondary window.
But this really breaks away from the structure of the boilerplate, so I was curious how others have solved it. Or any ideas.
Should this be closed? Doesn't seem like anyone else is doing this? How did you solve this?
@amilajack I'm working on solving this now. I'll post what I end up doing and close this so if anyone in the future searches for it they can see one solution.
@dustintownsend Have you checked this issue?
I'm also looking for a good way to handle multiple windows. Please let me know if you find out a nice way to deal with this.
So I ended up not doing multiple windows. I think in order for it to work something like redux-electron-store needs to be set-up. @nlemoine
What I did was instead of creating a new window I just resize the current window and reposition it to give the same effect as a new window. When the user exits that "window" it just resets the window to the original size and position. This will only work in the case that both windows don't need to be used at the same time which in my case this was true.
I think if we are able to add something like redux-electron-store in the future it will open the door for multiple window applications. Otherwise you will need to effectively have separate "apps" for each window.
Definitely interested in this! Anyone's got a good solution to this?
I'm in this problem too...
I think redux-electron-store is needed anyway.
I am just diving into this same problem. My app will have three windows.
1) Main window of app. You can create tasks and run them. Most everything happens here.
2) Window for managing preferences.
3) Window for managing profiles.
My preferences and profiles are both saved in a local db via nedb. Since both the preferences and profiles pages will require the user to hit a SAVE button, I intend to hook into the save success action and fire off an IPC call to the main window of the app. At which point the main window will update its store for preferences or profiles accordingly.
When I open a new window I load a page like this instead of using the main app.html file.
prefsWindow.loadURL(`file://${__dirname}/app.html#/prefs`);
And then in my routes file:
<Route path="/prefs" component={PrefsPage} />
I think there could be some improvements to this process as this new window has empty state for things it doesn't care about. For instance, this preferences window doesn't need to know about the task part of the store. So while this window has a task property in the store, it is empty. The only part of the store that this window actually loads and manages is the prefs part of the store. And on save, it will fire an event back to the main window so the main window reloads data from the local nedb database.
Open to thoughts on this, but if nothing else hope it helps as an idea starter someone else in the future. Would also love to know how some of the others in this thread decided to go.
I just ran into this while trying to create a splash screen to display some loading info in a progress bar. First i thought it was as simple as copying the index.html and index.js and loading it the same way... nope.
I don't think having a main entry point html page that routes to other components is a terrible way. However are there any implications to loading all these routes and their components upfront? Is there a way to load them on demand or does it really matter anyways being a desktop app?
Okay I wrote solution for this based off from a few days of research and experimenting.
Assume we have two windows defined in a main process, each with their own view. Lets have window-a call viewA, and window-b call viewB.
windowA = new BrowserWindow(
{
name: 'window-a',
width: 400,
height: 300,
}
);
window.loadURL(
isDev
? 'http://localhost:3000?viewA'
: `file://${path.join(__dirname, '../build/index.html?viewA')}`
);
windowB = new BrowserWindow(
{
name: 'window-b',
width: 400,
height: 300,
}
);
window.loadURL(
isDev
? 'http://localhost:3000?viewB'
: `file://${path.join(__dirname, '../build/index.html?viewB')}`
);
we should use a query parameter, also known in react as a search property of the path like ?viewA or ?viewB
In our entry point file index.js we will import a new class called ViewManager which will handle all of the view routing and configuration. The index.js would look like this:
import React from 'react';
import ReactDOM from 'react-dom'
import ViewManager from './ViewManager';
ReactDOM.render(<ViewManager />, document.getElementById('root'));
and our ViewManager.js class file should look something like this:
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route
} from 'react-router-dom'
import Update from './ViewA';
import Console from './ViewB';
class ViewManager extends Component {
static Views() {
return {
viewA: <ViewA/>,
viewB: <ViewB/>
}
}
static View(props) {
let name = props.location.search.substr(1);
let view = ViewManager.Views()[name];
if(view == null)
throw new Error("View '" + name + "' is undefined");
return view;
}
render() {
return (
<Router>
<div>
<Route path='/' component={ViewManager.View}/>
</div>
</Router>
);
}
}
export default ViewManager
ViewA and ViewB are just regular React.Component classes. The ViewManager will inject these components based on the routes defined by the name of the query string after ? When no view is defined or an incorrect name is used. an exception is thrown so you know the mapping is wrong.
When you need to add another view, such as ViewC to your project, just import the new Component at the top of the ViewManager like
import Update from './view/ViewC';
and update this array method of the ViewManager:
static Views() {
return {
viewA: <ViewA/>,
viewB: <ViewB/>,
viewC: <ViewC/>
}
}
There currently is no way to dynamically lookup and return a react component with a string directly. You have to register the component into an object. Using a ViewManager class is clean, and provides a global way to access the views and associated components statically; such as :
ViewManager.Views() -> returns a map of the view components
ViewManager.View(prop) -> a way to return the component on the name
I am sure there are some ways to extend the functionality, but this is for minimal code
GLHF;
=> https://medium.com/@1t4x0r/multiple-electron-windows-using-a-view-manager-and-react-js-6d8b1e209faf
I'm not sure I understand the need for ViewManager? You are currently loading pages via query string when you could just use hash urls and the built in ReactRouter which would require far less code imo. What benefit does ViewManager add over this approach?
The view manager does use the builtin routing, but yes its true you could use #/ which worked for me in dev mode hosting live code.. When i went to package a release the url paths were not working for me with a # in the file path. IE loading page from the file:// protocol.
I also wanted a static array to store my views in that the router selects from. I wanted a single class that manages all of my required views, and access them from ViewManager.Views.viewA.
I do agree it might seem like overkill when you can just require the component as you needed. In my project we have hundreds of views and dozens of different windows that use a combination of views. I also needed a way to statically store the states of each of these views.
Using a manager provided me a way to easily reuse a single view component statically, and inject it into window directly or nested into another component recursively using the built in react router. Without a manager i would either have to add the view as a global object (gets messy when you have hundreds of views) and reuse it that way. My other option would be to instantiate a new view component and inject the stored state into the constructor (which uses more memory and less responsive on complex views with large DOM)
I also found that by instantiate in a static Views() object in the ViewManager, the components are rendered upfront and not when the router detects a url change. This usually isn't noticeable but on some of the more complex views there will be about a second white screen lag of the window while the router instantiates the new view component.
@1-14x0r Big ups, yo.
@1-14x0r Looks to me like this solution loads two separate react app instances, one per Electron window. In that case, inter-window data-sharing will still have to occur via the Electron process.
@1-14x0r Looks to me like this solution loads two separate react app instances, one per Electron window. In that case, inter-window data-sharing will still have to occur via the Electron process.
yes always have to use IPC to share data between windows.. just the same way two browser tabs work.
How does one redirect between components using BrowserRouter . I had this previously working with HashRouter but HashRouter does not have support for the prop Search. I was previously using this.props.history.push("/viewC"); to redirect. @1-14x0r
I fixed this by creating adding a history to the router <Router history={history}>. If there is a better way with props that would be nice. But this works for now.
@1-14x0r I have used same url routing approach with multiple window (before landed here). My problem is that it takes lot longer (expectation would be to load in 1-2 seconds) to load new window content. even after the built version.
What did you do to optimize the load time with view manager? code splitting?
What about loading WebViews in different routes? Can WebViews have each of then stored cookies?
Thanks man. You saved me days!
Yo? I'm attempting to do the same thing while also using HashRouter and it doesn't seem to work.
const createWindow = () => {
mainWindow = new BrowserWindow({width: 1281, height: 800, webPreferences: {
nodeIntegration: true
}});
mainWindow.autoHideMenuBar = true;
mainWindow.loadURL(`file://${path.join(__dirname, '../build/index.html#/dashboard')}`);
mainWindow.webContents.openDevTools()
mainWindow.on("closed", () => (mainWindow = null));
}
@1-14x0r I have used same url routing approach with multiple window (before landed here). My problem is that it takes lot longer (expectation would be to load in 1-2 seconds) to load new window content. even after the built version.
What did you do to optimize the load time with view manager? code splitting?
that does help, but i compile my code and minify it. I also load all of my windows compiled code front through a AppLoader class i created. This allows your ViewRouter to quickly load this cache.. Poke around in your apps data directory and local storage in the CLI to see how things are loading.
Ill be honest, one of our projects has 5MB of compiled JS code that spawns about 5 windows with hundreds of classes and it takes <1 sec to load in dev. do you use minify and uglify?
What about loading WebViews in different routes? Can WebViews have each of then stored cookies?
yes and yes. it can be very powerful check out https://www.npmjs.com/package/electron-cookies
but if your using persistent data check out NeDB or LokiJS way more secure and faster then cookies. GL;HF
HashRouter
check your URL, looks invalid with a #/
Okay I wrote solution for this based off from a few days of research and experimenting.
Assume we have two windows defined in a main process, each with their own view. Lets have
window-acallviewA, andwindow-bcallviewB.windowA = new BrowserWindow( { name: 'window-a', width: 400, height: 300, } ); window.loadURL( isDev ? 'http://localhost:3000?viewA' : `file://${path.join(__dirname, '../build/index.html?viewA')}` ); windowB = new BrowserWindow( { name: 'window-b', width: 400, height: 300, } ); window.loadURL( isDev ? 'http://localhost:3000?viewB' : `file://${path.join(__dirname, '../build/index.html?viewB')}` );we should use a query parameter, also known in react as a search property of the path like
?viewAor?viewBIn our entry point file
index.jswe will import a new class calledViewManagerwhich will handle all of the view routing and configuration. The index.js would look like this:import React from 'react'; import ReactDOM from 'react-dom' import ViewManager from './ViewManager'; ReactDOM.render(<ViewManager />, document.getElementById('root'));and our
ViewManager.jsclass file should look something like this:import React, { Component } from 'react'; import { BrowserRouter as Router, Route } from 'react-router-dom' import Update from './ViewA'; import Console from './ViewB'; class ViewManager extends Component { static Views() { return { viewA: <ViewA/>, viewB: <ViewB/> } } static View(props) { let name = props.location.search.substr(1); let view = ViewManager.Views()[name]; if(view == null) throw new Error("View '" + name + "' is undefined"); return view; } render() { return ( <Router> <div> <Route path='/' component={ViewManager.View}/> </div> </Router> ); } } export default ViewManager
ViewAandViewBare just regularReact.Componentclasses. TheViewManagerwill inject these components based on the routes defined by the name of the query string after?When no view is defined or an incorrect name is used. an exception is thrown so you know the mapping is wrong.When you need to add another view, such as
ViewCto your project, just import the new Component at the top of theViewManagerlikeimport Update from './view/ViewC';and update this array method of the
ViewManager:static Views() { return { viewA: <ViewA/>, viewB: <ViewB/>, viewC: <ViewC/> } }There currently is no way to dynamically lookup and return a react component with a string directly. You have to register the component into an object. Using a
ViewManagerclass is clean, and provides a global way to access the views and associated components statically; such as :
ViewManager.Views()-> returns a map of the view components
ViewManager.View(prop)-> a way to return the component on the nameI am sure there are some ways to extend the functionality, but this is for minimal code
GLHF;
=> https://medium.com/@1t4x0r/multiple-electron-windows-using-a-view-manager-and-react-js-6d8b1e209faf
Good work!
You can also import useLocation from react-router-dom instead of passing with props.
For instance, I use the following function to parse the URL query.
function useQuery() {
return new URLSearchParams(useLocation().search);
}
I can then access more than one params of the query by using the following code (Assuming I am using standard URL query syntax that has been encoded e.g. http://localhost:3000?key1=val1&key2=val2).
let query = useQuery();
query.get("key1"); # Returns the value at key1
query.get("key2"); # Returns the value at key2
let x = query.get("key3"); # x is undefined
I then can write
x ? console.log("x has a value") : console.log("x is null or undefined or false")
good looks:) yes useLocation is a more pragmatic and extensible approach, however has more overhead. I actually recently switched to this when i needed to pass in a flag for rendering 3d in one of my windows. This flag was determined by a setting in the main process so i bubbled it as a url param in this router..
@ZoeDreams i seen your suggestion the way to handle multiple windows... in that case how can i use parent window redux state value in child one?
Console from './Vi
Hello, how are you.
I did exactly as you said in the tutorial, but my application, when opening the second view in a new window, an error occurs saying that
Uncaught ReferenceError: require is not defined and in debug, it points the error exactly to theexport default of the screen in question.
do you know how to solve?
Console from './Vi
Hello, how are you.
I did exactly as you said in the tutorial, but my application, when opening the second view in a new window, an error occurs saying that
Uncaught ReferenceError: require is not definedand in debug, it points the error exactly to theexport defaultof the screen in question.do you know how to solve?
the renderer uses ECMA 6+ require is ECMA v5. consider using export statements in your renderer code.
Hi, I'm successfully using this HashRouter config but only on Linux and MacOS.
On Windows, the printPreviewWindow content is blank and no error is produced.
Why, I'm wondering.
Any Idea here ?
import React from "react"
import ReactDOM from "react-dom"
import App from "./App.js"
import PrintPreview from "./PrintPreview.js"
// import * as serviceWorker from "./serviceWorker"
import { HashRouter, Route } from "react-router-dom"
ReactDOM.render(
<React.StrictMode>
<HashRouter>
<div>
<Route exact path="/">
<App />
</Route>
<Route exact sensitive path="/printPreview">
<PrintPreview />
</Route>
</div>
</HashRouter>
</React.StrictMode>,
document.getElementById("root")
)
Building on Linux. I never had a problem of different behavior under Win until now.
[...]
mainWindow.loadURL(
isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "../build/index.html")}`
)
[...]
printPreviewWindow.loadURL(
isDev
? "http://localhost:3000/#/printPreview"
: `file://${path.join(
__dirname,
"../build/index.html#/printPreview"
)}`
)
[...]
I found out the relative path constructor is not well understood on Windows.
Replacing
file://${path.join(
__dirname,
"../build/index.html#/printPreview"
)}
with
file://${__dirname}/index.html#/printPreview
And voil脿 HashRouter is working like a charm on every platform
is this HashRouter working fast for you, long time ago when I used this, it was very slow in opening new window.
Yes, it is pretty fast, under 1s in dev. I'm using the v6 of react-router, quite lighter than the previous release too.
is there support for dynamic import so that my very large main page is not loaded just to mount a slick light dialog box?
@gautamsi Route to the dialog component on "/" then and your main when needed.
I will try that, thanks
I found out the relative path constructor is not well understood on Windows.
Replacing
file://${path.join( __dirname, "../build/index.html#/printPreview" )}with
file://${__dirname}/index.html#/printPreviewAnd voil脿 HashRouter is working like a charm on every platform
Thank you. Yes, i haven't tested this on windows since around the time that i originally wrote this stuff. That sounds like a bug in the Path module within your windows environment. Good troubleshooting, i am happy you figured it out.
Most helpful comment
Okay I wrote solution for this based off from a few days of research and experimenting.
Assume we have two windows defined in a main process, each with their own view. Lets have
window-acallviewA, andwindow-bcallviewB.we should use a query parameter, also known in react as a search property of the path like
?viewAor?viewBIn our entry point file
index.jswe will import a new class calledViewManagerwhich will handle all of the view routing and configuration. The index.js would look like this:and our
ViewManager.jsclass file should look something like this:ViewAandViewBare just regularReact.Componentclasses. TheViewManagerwill inject these components based on the routes defined by the name of the query string after?When no view is defined or an incorrect name is used. an exception is thrown so you know the mapping is wrong.When you need to add another view, such as
ViewCto your project, just import the new Component at the top of theViewManagerlikeand update this array method of the
ViewManager:There currently is no way to dynamically lookup and return a react component with a string directly. You have to register the component into an object. Using a
ViewManagerclass is clean, and provides a global way to access the views and associated components statically; such as :ViewManager.Views()-> returns a map of the view componentsViewManager.View(prop)-> a way to return the component on the nameI am sure there are some ways to extend the functionality, but this is for minimal code
GLHF;
=> https://medium.com/@1t4x0r/multiple-electron-windows-using-a-view-manager-and-react-js-6d8b1e209faf