I have an app using Electron, React, and React Router. I'm using ipcRenderer in the component constructor to send events from my component to the main Electron process. After I added React Router to the mix, I noticed that my ipcRenderer event was getting added again and again each time I left and came back to the component. I figure it's because React Router is mounting and unmounting the component as needed.
I found a way around the issue by checking if the event already has been registered like this:
if (!ipcRenderer._events['open-file-reply']) {
ipcRenderer.on('open-file-reply', (event, fileContents) => {
if(fileContents){
this.setState({
data: JSON.parse(fileContents)
});
}
});
}
I'm just wondering if there is a more correct way to do this. Does ipcRenderer.on belong in the constructor anyway, or is there a more appropriate place to put it?
EDIT
This is the best solution I've come up with so far:
import {ipcRenderer} from 'electron';
/* inside React component */
constructor(props) {
super(props);
// ...
this.loadFileListener = this.loadFileListener.bind(this);
ipcRenderer.on('open-file-reply', this.loadFileListener);
}
componentWillUnmount() {
ipcRenderer.removeAllListeners(['open-file-reply']);
}
loadFileListener(event, fileContents) {
// set state and stuff...
}
I don't think you should be setting up IPC until the component is mounted, so I would suggest this approach:
constructor(props) {
super(props)
this._loadFileListener = this._loadFileListener.bind(this)
}
componentDidMount() {
ipcRenderer.on('open-file-reply', this._loadFileListener)
}
componentWillUnmount() {
ipcRenderer.removeListener('open-file-reply', this._loadFileListener)
}
Related
This seems to be a dumb question, but I didn't find a one in SO.
Here is it:
I have a react native app. Every time I exit the app and re-open it, I expect to have Home page as the default screen being displayed, and also re-render the Home page as componentDidMount inside the Home screen will fetch latest data from the database. How can we achieve such case? Thanks!
If you don't mean closing the app and rather just sending it to the background, you can use the exported AppState object to detect it. See the answer to this question for an example. Once you detect the event, you can force a rerender using forceUpdate() for class components, or by using a dummy hook in functional components
Slightly modified React docs example:
import React, {Component} from 'react';
import {AppState, Text} from 'react-native';
class AppStateExample extends Component {
state = {
appState: AppState.currentState,
};
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (
this.state.appState.match(/inactive|background/) &&
nextAppState === 'active'
) {
console.log('App has come to the foreground!');
this.forceUpdate();
}
this.setState({appState: nextAppState});
};
render() {
return <Text>Current state is: {this.state.appState}</Text>;
}
}
Functional component (untested):
import React, { useState, useEffect } from 'react';
import { AppState } from 'react-native';
const App = () => {
const [updateVal, setUpdateVal] = useState(false);
const forceUpdate = newState => {
if (newState === 'active')
setUpdateVal(!updateVal); // forces a rerender
}
useEffect(() => {
AppState.addEventListener('change', forceUpdate);
return () => AppState.removeEventListener('change', forceUpdate);
}, []);
// return your contents
};
However, if you're actually closing it (not just leaving the app), it should rerender.
Apps have a different concept of "exiting" that might be unintuitive. If you close out of an app it is still open in the background, which is why your app doesn't start up from scratch the next time you open it.
To handle that situation you need to watch for activity instead and you cannot depend on React lifecycle events like you would in a browser app.
For example if you use React-navigation you could use their lifecycle events: https://reactnavigation.org/docs/en/navigation-lifecycle.html
Which include things like willFocus, willBlur, didFocus and didBlur. Then, based on those events you can run whatever code you need, such as updating some state or fetching new data.
I'm forced to use class based component, so how can I replace useEffect with component lifecycle like componentDidMount, componentDidUpdate and componentWillUnmount in my React component.
Please help
const App = () => {
useEffect(() => {
store.dispatch(loadUser());
}, []);
As React team mentioned in this doc
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
So if you want to use life cycles you have to use class
.
Use class that extends Component and it must be like this :
import React from 'react';
class App extends React.Component {
//you can use components lifecycle here for example :
componentDidMount() {
store.dispatch(loadUser());
}
}
After delcaring the constructor you should put the componentDidMount method just ahead, like this:
class yourComponent extends Component {
constructor(props){
// constructor taks...
}
componentDidMount() {
// what you want to do when the component mount
}
}
I currently have a hero image component in React, and i'd like to move to a shopping page when the user attempts to scroll down the page, as if they were connected when in reality they aren't. this would make my other navigation a lot easier, while still keeping all components and routes seperate.
class Home extends Component {
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
}
handleScroll() {
let history = useHistory();
history.push('/categories');
console.log('done');
}
render() {
return (
<Hero onScroll={this.handleScroll}>
my console.log never gets executed.
Let's change onScroll with onWheel
<Hero onWheel={this.handleScroll}>
In the code below _updateQID method is triggered on emitting event 'Questions.updateHeader' by another component. when emitted event and triggered _updateQID i get below warning in console:
Warning: forceUpdate(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
import React, { Component } from 'react'
import { subscribe, model } from '~/lib'
import { ContextMenu } from 'project-components'
import _ from 'lodash'
import EventEmitter from 'eventemitter3'
import './Headers.styl'
class Headers extends React.Component {
constructor (props) {
super(props)
this.ee = new EventEmitter()
this._updateQID = this._updateQID.bind(this)
}
componentDidMount () {
model.on('Questions.updateHeader', this._updateQID.bind(this))
}
componentWillUnmount () {
model.removeListener('Questions.updateHeader', this._updateQID.bind(this))
}
_updateQID = () =>{
this.forceUpdate()
}
render () {
return (<div className="header">DUMMY TEST FOR HEADER</div>)
}
}
export default Headers
Please help
Okay we solved it... We were triggering the event to forceupdate Headers component from render method of another component.... After putting it into componentDidUpdate instead of render that warning was gone...
Short question I hope you can help.
Why does this work?
constructor() {
super();
this.loader;
}
componentDidMount(){
this.loader = document.getElementById('loaderTest');
console.log(this.loader);
}
But this does not?
constructor() {
super();
this.loader = document.getElementById('loaderTest');
}
componentDidMount(){
console.log(this.loader);
}
The second method works in vanilla javascript but returns null in the console with React. I tried googling but wasn't sure what I was searching for exactly.
What it seems like is that the constructor works before react has rendered any of the component. Am I right in assuming this?
I'm only starting to understand object oriented javascript so if my question is dumb, I apologise.
Any help is much appreciated.
Thanks
Moe
EDIT: In context
import React from 'react';
import ReactDOM from 'react-dom';
class Site extends React.Component {
constructor() {
super();
this.loader;
}
componentDidMount(){
this.loader = document.getElementById('loaderTest');
}
render() {
return(
<div id="loaderTest" className="site_container__loader loading block--fullpage">
</div>
)
}
}
module.exports = Site;
constructor of a React component is executed once the first time the component is instantiated. You should avoid DOM manipulations in contructor as the component is not yet mounted in the actual DOM.
Life cycle during component initialization(Mounting):
constructor(props)
The constructor is the right place to initialize state and bind methods
componentWillMount
Invoked Once (client and server)
Can change state here with this.setState() (will not trigger addition render)
Called just before render()
render
Component render() is called
componentDidMount
Invoked Once
You can do DOM manipulations and other things like API calls etc.
Called just after render()