I'm trying to update state in another window depedning on state changes in the first window but ipc communcation doesn't work as expected for me.
In my first window, I have:
onStatsSelect(event, menuItem, index) {
const selectedStatsCopy = this.state.selectedStats.slice();
const itemIndex = selectedStatsCopy.indexOf(menuItem.props.primaryText);
if (itemIndex > -1) {
delete selectedStatsCopy[itemIndex];
} else {
selectedStatsCopy[index] = menuItem.props.primaryText;
}
// Update state
this.setState({ selectedStats: selectedStatsCopy });
// Notify worker window of change
if (!(this.props.isReport)) {
console.log('in renderer main window');
ipcRenderer.send("updateSelectedStatsMain", event, selectedStatsCopy);
}
}
This is a callback for updating selectedStats state. It will also notify worker window of this update as seen in the last lines. if (!(this.props.isReport)) This is an important check since both windows share the same component and so I use property isReport to distinguish between the two.
In my main process, I have:
// Selected Stats have changed in main window
ipcMain.on('updateSelectedStatsMain', (event, selectedStatsCopy) => {
console.log('in main');
// Send to worker window
workerWindow.webContents.send('updateSelectedStatsRen', event, selectedStatsCopy);
});
This code will communicate back to worker window with the new state selectedStatsCopy.
In my comoponentDidMount, I have:
componentDidMount() {
// Register listening to message in case this is the worker window
if (this.props.isReport) {
console.log('in renderer worker window');
ipcRenderer.on("updateSelectedStatsRen", (event, selectedStatsCopy) => {
console.log('in renderer worker window event');
this.setState({ selectedStats: selectedStatsCopy });
});
}
}
This is supposed to work but electron hangs at line ipcRenderer.send("updateSelectedStatsMain", event, selectedStatsCopy); making main window hangs for a while and it continues using resources until PC freezes.
What is the problem here?
I figured out the error which I still don't know why it did freeze electron.
Basically I was doing
ipcRenderer.send("updateSelectedStatsMain", event, selectedStatsCopy);
This make absolutely no sense since I'm passing the event as a parameter. I don't even have an event variable to pass.
Updating this:
ipcRenderer.send("updateSelectedStatsMain",event, selectedStatsCopy);
to this:
ipcRenderer.send("updateSelectedStatsMain", selectedStatsCopy);
and this:
workerWindow.webContents.send('updateSelectedStatsRen', event, selectedStatsCopy);
to this:
workerWindow.webContents.send('updateSelectedStatsRen', selectedStatsCopy);
fxied the issue for me.
Related
I'm just starting to enter the world of Adobe Analytics. I'm working in React with Typescript and I am trying to leverage the adobe data layer to send information to Adobe Launch. I've been able to successfully use the adobe push function (i.e. window.adobeDataLayer.push({ test: 'test succeeded' })) and can both see that in the console via window.adobeDataLayer.getState() and ran a simple test to confirm it made its way to Adobe Launch.
However, when it comes to adding an event listener, I'm stumped. I attempted to follow Adobe's Documentation and came up with the following (doStuff() was just to confirm that eventListeners were working as expected, which they do):
function myHandler(event: any): void {
console.log("My handler was called")
}
function doStuff() {
console.log('do stuff was called')
}
function adobeAnalyticsTest(): void {
console.log(' adobeAnalyticsTest function called')
window.adobeDataLayer = window.adobeDataLayer || []
window.adobeDataLayer.push({ test: 'test succeeded' })
window.addEventListener('click', doStuff)
window.adobeDataLayer.push(function (dl: any) {
dl.getState()
dl.addEventListener('click', myHandler)
})
}
useEffect(() => {
adobeAnalyticsTest()
}, [])
Looking at window.adobeDataLayer, I couldn't see anything that seemed to indicate there was a click event listener (although this could be ignorance on my part) nor was 'My Handler was called' ever logged to the console. Does anyone have any ideas as to what I'm doing wrong or know how to tell when it's working correctly?
At face value, it looks like you don't have anything in your code that actually calls adobeAnalyticsTest().
Also, the listener you attach to dl doesn't listen for DOM events like clicking on something in window (like your window.addEventListener line); it listens for payloads pushed to adobeDataLayer where the passed event property value matches what you are listening for.
For example, put adobeDataLayer.push({'event':'click'}) in your js console you should see "My handler was called".
Think of it more like subscribing to a CustomEvent (because that's what it is, under the hood), rather than a native DOM event.
I have encountered strange behavior when using Electron's ipcRenderer with React's useEffect.
Within my electron app, I have the following code:
import React, { useEffect } from 'react'
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
...
const someValueThatChanges = props.someValue;
useEffect(() => {
const myEventName = 'some-event-name';
console.log(`Using effect. There are currently ${ipcRenderer.listenerCount(eventName)} listeners.`);
console.log(`Value that has changed: ${someValueThatChanges}.`);
ipcRenderer.addListener(myEventName, myEventHandler);
console.log('Added a new listener.');
// Should clean up the effect (remove the listener) when the effect is called again.
return () => {
ipcRenderer.removeListener(myEventName, myEventHandler)
console.log('Cleaned up event handler.');
}
}, [ someValueThatChanges ]);
function myEventHandler() {
console.log('Handled event');
}
The code above is supposed to listen to the some-event-name event fired by Electron's main process with mainWindow.webContents.send('some-event-name'); and console.log(...) a message inicating that the event was handled.
This works as expected when the effect is initially run. A listener is added, the event is raised at a later time, and the string 'Handled event' is printed to to the console. But when the someValueThatChanges variable is assigned a different value and the event is raised for a second time, the 'Handled event' string is printed out to the console twice (the old listener does not appear to have been removed).
The line with the listenerCount(eventName) call returns 0 as expected when the removeListener(...) call is included in the useEffect return/cleanup function. When the removeListener(...) call is removed, the listenerCount(eventName) call returns a value that is incremented as expected (e.g. 0, 1, 2) as listeners are not removed.
Here's the really weird part. In either case, whether or not I include the call to removeListener(...), the myEventHandler function is always called for as many times as useEffect has been run. In other words, Electron reports that there are no event listeners, but myEventHandler still seems to be called by the previous listeners. Is this a bug in Electron, or am I missing something?
Never try with ipcRenderer.addListener, But try ipcRenderer.on instead
useEffect(() => {
ipcRenderer.send('send-command', 'ping');
ipcRenderer.on('get-command', (event, data) => {
console.log('data', data);
});
return () => {
ipcRenderer.removeAllListeners('get-command');
};
}, []);
I believe, the docs changed. ipcRenderer.removeAllListeners accept single string instead of array of string Source electron issues,
I am relatively new to the Gatbsy framework and I am trying to figure out a way to toggle classes on some elements on DOMContentLoaded or on window.load, to animate them as soon as the user can see the screen.
This is what I did until now however it doesn't seem very appropriate:
componentDidMount = () => {
if (typeof window === "undefined") return
window.addEventListener("load", this.myEventHandler)
// or
if (typeof document === "undefined") return
document.addEventListener("DOMContentLoaded", this.myEventHandler)
}
Is there a better way of doing this?
Thank you in advance.
I think you should add these event listeners in the gatsby-browser.js:
// gatsby-browser.js
// ES6
export const onClientEntry = () => {
window.onload = () => { /* do stuff */ }
}
// or commonjs
exports.onClientEntry = () => {
window.onload = () => { /* do stuff */ }
}
You don't have to check for window there because this file is only run on the client side. Here's the full list of available hooks.
also AFAIK document doesn't have a ready event, it's a jQuery thing. You might be interested in DOMContentLoaded event, though there's some small different between that and jQuery's ready IIRC.
This is what I did until now however it doesn't seem very appropriate:
This is perfectly legitimate code imho:
componentDidMount = () => {
window.addEventListener("load", this.myEventHandler);
...
EDIT
Thinking about this, by the time componentDidMount has run, window "load" and document "ready" will already have fired... so it is a bit pointless.
https://codesandbox.io/s/n43z5x00j4
You can just use componentDidMount to check that the DOM has loaded and not bother with the other two events.
I'm developing a React app without Redux or any other state manager.
Let's say I want to do three things when a button is clicked:
Enable some other button
Remove a label
Show a confirmation toaster
These 3 things are controlled by 3 variables of the state. I could therefore do simply this:
myHandler = () => {
this.setState({
canSave: true, // Enable the button
isLabelVisible: false, // Hide label
isConfirmationMessageVisible: true, // Show confirmation message
});
}
However, I could get rid of those comments by using some private class functions, like this:
myHandler = () => {
this.toggleSaveButton(true);
this.toggleLabel(false);
this.toggleConfirmationMessage(true);
}
toggleSaveButton= (enabled) => {
this.setState({
canSave: enabled,
});
}
toggleLabel= (visible) => {
this.setState({
isLabelVisible: visible,
});
}
toggleConfirmationMessage= (visible) => {
this.setState({
isConfirmationMessageVisible: visible,
});
}
In addition to remove those comments which could easily get out-of-sync with the code, this allows me to reuse the private methods in other places of my code.
Since this is handled in a synthetic event, I have read here that it will be batched, so I can expect no performance penalty.
My question is: is this good practice? have you used this approach? can you point some potential drawbacks I can not foresee right now?
This is perfectly fine. As you mention, React batches all updates to the state that are triggered from event handlers. This means that you can safely use multiple setState() like you are doing here.
In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
The only thing you need to look out for is if you are changing the same state twice from two setState() calls. For example:
a() {
this.setState({foo: foo+1});
}
b() {
this.setState({foo: foo+1});
}
Calling a() and then b() from the same event, will increment foo by 1, not two.
Instead use:
a() {
this.setState(prevState => ({foo: foo+1}));
}
b() {
this.setState(prevState => ({foo: foo+1}));
}
this will correctly increment foo by 2.
For potential future readers who are not calling multiple setState() from an event handler, I should note the following:
With current version, several setStates outside of event handlers (e.g. in network responses) will not be batched. So you would get two re-renders in that case.
Alternative solution
What you can do though, regardless if you call setState() from an event handler or not, is to build an object and then set it as the new state. The potential benefit here is that you only set the state once and thus don't rely on batching or where the function was triggered from (event handler or not).
So something like:
myHandler = () => {
let obj = {}
obj = this.toggleSaveButton(obj, true);
obj = this.toggleLabel(obj, false);
obj = this.toggleConfirmationMessage(obj, true);
this.setState(obj);
}
toggleSaveButton= (obj, enabled) => {
obj.canSave = enabled;
return obj;
}
toggleLabel= (visible) => {
obj.isLabelVisible = visible;
return obj;
}
toggleConfirmationMessage= (visible) => {
obj.isConfirmationMessageVisible = visible;
return obj;
}
I have heavy window (Devexpress UI), and, when I call window.Show() in my ICommand body, UI freezes. I want to show some progress while window loaded.
I was trying to call it in async method, but could not find any way to call Show in async way:
I was try Task.Run but it fail with exception of different UI threads.
I was try to call Application.Dispatcher.BeginInvoke but this also freezes UI.
Little adjustment:
I was wrong about Show() freezes. UI freeze at constructor call: var window = new ProductsPricesWindow()
If your execute some time-consuming work in another thread, then you must synchronize a result of another thread with UI thread. To synchronize two threads(new thread and UI thread), it is necessary to use Dispatcher.
As MSDN says:
Only one thread can modify the UI thread. But how do background
threads interact with the user? A background thread can ask the UI
thread to perform an operation on its behalf. It does this by
registering a work item with the Dispatcher of the UI thread. The
Dispatcher class provides two methods for registering work items:
Invoke and BeginInvoke. Both methods schedule a delegate for
execution. Invoke is a synchronous call – that is, it doesn’t return
until the UI thread actually finishes executing the delegate.
BeginInvoke is asynchronous and returns immediately.
For example:
xaml:
<TextBox Name="textBox"/>
in code-behind:
Task.Run(()=> {
Thread.Sleep(5000);//imitate time consuming work
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render,
new Action(() => {
textbox.Text="Hello World!:)"}));
});
In the above example, we create a new thread (Task.Run(...)) and synchronize result of new thread with UI thread by calling Dispatcher(Dispatcher.BeginInvoke(...))
UPDATE:
You should call new window on UI thread. So the code would be:
var yourWindow=new YourWindow();
yourWindow.ShowDialog();
However, as you did not show any code located in constructor of YourWindow, I try to suppose that there is some method which is time consuming. Then your code should look like this:
public class YourWindow()
{
public YourWindow()
{
Task.Run(()=> {
TimeConsumingMethod();
});
}
private void TimeConsumingMethod()
{
Thread.Sleep(5000);//imitate time consuming work
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render,
new Action(() => {
textbox.Text="Hello World!:)"}
}
}
Looks like this is not possible to make UI responsible when other UI component initialized.
So in my case solution is just allow UI to show in BusyIndicator (I use Xceed.Wpf.Toolkit) current state.
To do this I just put await Task.Delay(100); before creating window.
Also was helpful to pre-initialize creating window in background thread that starts on my app start. This makes creatng new window more responsible and even makes BusyIndicator live sometime.
public void Init()
{
var thr = new Thread(
async () =>
{
BusyContent = "Loading...";
try
{
// pre-init dx
await Application.Current.Invoke(
() =>
{
var window = new Views.ProductsPricesWindow();
window.Close();
}
);
...
}
finally
{
NotBusy();
}
}
);
thr.Name = nameof(Init);
thr.Start();
}
public async Task AddProductsPrices(Window p)
{
try
{
BusyContent = "Loading...";
await Task.Delay(100);
var window = new ProductsPricesWindow();
window.Show();
}
finally
{
NotBusy();
}
}
Application.Current.Invoke is extender:
public async static Task Invoke(this Application app, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
if (app != null)
{
await app.Dispatcher.BeginInvoke(
priority,
action
);
}
else
action();
}
XAML
<extToolkit:BusyIndicator
IsBusy="{Binding IsBusy, Mode=OneWay}"
>
<extToolkit:BusyIndicator.BusyContent>
<TextBlock
Text="{Binding BusyContent}"
FontSize="72"
/>
<Grid>
...
</Grid>
</extToolkit:BusyIndicator.BusyContent>
</extToolkit:BusyIndicator>