Defining internal class variables inside class methods using Javascript? - javascript

This questions is actually React JS related. Is it OK to define internal class variables inside one of the class methods and then use it in other methods? I mean to do something like this:
class Something extends React.Component {
state = {
value: 'doesnt matter'
};
something = () => {
//a lot is happening in here and at some point I define this.thing
this.thing = 1;
}
increase = () => {
if(this.thing) {
this.thing += 1;
}
}
decrease = () => {
if(this.thing && this.thing > 0) {
this.thing -= 1;
}
}
render() {
return (
<span>this.state.value</span>
);
}
}
thing is that I don't need to put that this.thing as a state value, because I only need it internally. Please be aware that this code is just an example, real code is a bit more complicated, but the main question, is it OK to define class internal variables(this.thing) like I do in my example? Or maybe I should do this differently? What would be the best practice?

It's not a problem to use the constructor to do such a thing but based on the react theory and UI rendering this kind of usage will not re-render or follow the react pattern of trigger and re-render, it will just server as a storage for a value that has nothing to do with the react life cycle.
class Something extends React.Component {
constructor(props) {
super(props);
// Your thing
this.thing = 0;
this.state = {
value: "doesnt matter."
};
}
something = () => {
//a lot is happening in here and at some point I define this.thing
this.thing = 1;
};
increase = () => {
if (this.thing) {
this.thing += 1;
}
};
decrease = () => {
if (this.thing && this.thing > 0) {
this.thing -= 1;
}
};
render() {
this.something();
console.log(this.thing); // will equal 1.
return <span>{this.state.value}</span>;
}
}

I don't need to put that this.thing as a state value, because I only need it internally.
A React component's state should also only be used internally.
What would be the best practice?
You can use instance variables (ivars) instead of state to increase performance because you may reduce the burden on the event queue. Aesthetically, ivars often require less code. But state updates are usually preferred because they will trigger a re-render; this guarantee makes your code easier to think about, as the render is never stale. In your case, the render function is independent of this.thing, so it's okay to store it in an ivar.
Generally, it's best to initialize ivars in the constructor because it runs first, so this.thing is guaranteed to be ready for consumption by other methods:
constructor(props) {
super(props);
this.thing = 0;
}

Related

React JS memory leak with periodic calculations using setInterval

I'm trying to periodically run a calculation (every 5 seconds) and update a component's state with the calculated value using a setInterval timer. What I've seen is that the updateCalculation() function does get called every 5 seconds but when monitoring the memory usage using the Chrome devtools it just keeps on growing endlessly on every call by setInterval. The memory never seems to get released.
Snapshot 1:
Snapshot 2:
What could be a possible workaround for running calculations periodically?
I'm still pretty new to React and am really not sure what I'm doing wrong.
class MyComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
calculated: []
};
}
componentDidMount() {
this.calculationUpdater = setInterval(() => this.updateCalculation(), 5000);
}
componentWillUnmount() {
clearInterval(this.calculationUpdater);
}
// Memory leak here
// The function that gets called by setInterval to calculate data and update the state
updateCalculation() {
let data = [];
for (let i = 0; i < 60000; i++) {
data.push({x: i, y: i, z: i});
}
this.setState({
calculated: data
});
}
render() {
return (
<React.Fragment>
<Child calc={this.state.calculated} />
</React.Fragment>
);
}
}
I'm not doing anything special with the Child component at the moment. This is what it looks like:
class Child extends React.PureComponent {
render() {
return (
<div></div>
);
}
}
EDIT: Check following post: Does JavaScript setInterval() method cause memory leak?
You are not clearing the interval because you are are not setting or reading state correctly. So if your component keep getting mounted and unmounted, you set a new interval but do not clear the interval on unmount.
this.calculationUpdater = setInterval(() => this.updateCalculation(), 5000);
This should be
const calculationUpdater = setInterval(() => this.updateCalculation(), 5000);
console.log(calculationUpdater )
this.setState({calculationUpdater : calculationUpdater})
And you access the state as following:
console.log(this.state.calculationUpdater);
clearInterval(this.state.calculationUpdater);
The only thing I can suggest is to try switching from PureComponents to Components; I noticed my initial snapshot was laggier for PureComponents, though either way my results were not like yours.

React: Array not rendering in correct order after first time

The first time my array is rendered it is in the correct order, however, if it is changed the rendered order remains the same.
For example:
construct() {
this.state = {
test_array: [1,2,3,4]
}
let self = this;
setTimeout(function(){
self.scramble();
}, 5000);
}
scramble() {
this.state.test_array = [3,1,2,4];
this.setState(self.state);
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
Results in:
On the console (the current order, correct):
3
1
2
4
Rendered as DOM (the original order, incorrect):
1
2
3
4
Any idea why this is failing to render in the correct order?
You were very close. Here's a few things I changed to fix it:
construct should be constructor
You always need to call super() as the first line of a constructor. (You don't really need to worry about this, it's an Object Oriented thing; google it if you're curious)
Use "arrow functions" instead of "keyword functions" or .bind(this) to prevent this from changing contexts
Do not modify this.state; always call this.setState if you want it to change
class OrderThing extends React.Component {
constructor() {
super()
this.state = {
test_array: [1,2,3,4]
}
setTimeout(() => {
this.scramble();
}, 5000);
}
scramble() {
this.setState({
test_array: [3,1,2,4]
});
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
}
const div = document.createElement('div')
document.body.appendChild(div)
ReactDOM.render(<OrderThing />, div)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
A few suggestions here.
First of all, there is no construct() in js, but there is constructor().
Secondly, you should always call super method with props as an argument in constructor, like this:
constructor(props) {
super(props);
...
}
Finally, react developers highly recommend to modify state only using setState() method.
So you should rewrite your scramble method.
scramble() {
this.setState({test_array: [3,1,2,4]});
}
This changes should help you a little bit.

React componentWillReceiveProps not updating state

I've got this React parent component here. The children components at this point are just returning dropdown menus. I expected that componentWillReceiveProps would update the state here, which in turn should be passed to StopList as props. However, when state.selectedSub is changed through handleSubSelect, nothing happens and StopList doesn't receive any props.
Is my mistake with the asynchronous nature of componentWillReceiveProps? Is it in the wrong place in my code? Am I using the wrong lifecycle method?
// We're controlling all of our state here and using children
// components only to return lists and handle AJAX calls.
import React, { Component } from 'react';
import SubList from './SubList';
import StopList from './StopList';
class SubCheck extends Component {
constructor (props) {
super(props);
this.state = {
selectedSub: '--',
selectedStop: null,
stops: ['--'],
};
this.handleSubSelect.bind(this);
this.handleStopSelect.bind(this);
}
// We want the user to be able to select their specific subway
// stop, so obviously a different array of stops needs to be
// loaded for each subway. We're getting those from utils/stops.json.
componentWillReceiveProps(nextProps) {
var stopData = require('../utils/stops');
var stopsArray = [];
var newSub = nextProps.selectedSub
for(var i = 0; i < stopData.length; i++) {
var stop = stopData[i];
if (stop.stop_id.charAt(0) === this.state.selectedSub) {
stopsArray.push(stop.stop_name);
}
}
if (stopsArray.length !== 0 && newSub !== this.state.selectedSub) {
this.setState({stops: stopsArray});
}
}
handleSubSelect(event) {
this.setState({selectedSub:event.target.selectedSub});
}
handleStopSelect(event) {
this.setState({selectedStop:event.target.selectedStop})
}
render() {
return (
<div>
<SubList onSubSelect={this.handleSubSelect.bind(this)}/>
<StopList stops={this.state.stops} onStopSelect={this.handleStopSelect.bind(this)}/>
</div>
);
}
}
export default SubCheck;
You are duplicating data, and causing yourself headaches that aren't necessary.
Both selectedSub and selectedStop are being stored as props and as state attributes. You need to decide where this data lives and put it in a singular location.
The problem you are encountering entirely revolves round the fact that you are changing the state attribute and expecting this to trigger a change to your props. Just because they share a name does not mean they are the same value.

Reactjs: using for-loop inside componentDidMount

I am having trouble understanding why my component state does not change inside the for-loop.
Here's an example:
class Example extends React.Component {
constructor() {
super()
this.state = {
labelCounter: 1,
}
}
componentDidMount() {
for (let i = 0; i < 10; i++) {
this.setState({ labelCounter: this.state.labelCounter + 1 })
console.log(this.state.labelCounter) // this.statelabelCounter = 1
}
}
}
Whereas if I changed the code slightly to this, it seems to be changing as expected:
class Example extends React.Component {
constructor() {
super()
this.state = {
labelCounter: 1,
}
}
componentDidMount() {
for (let i = 0; i < 10; i++) {
this.setState({ labelCounter: ++this.state.labelCounter })
console.log(this.state.labelCounter)
}
}
}
I think the issue you're having is that react batches updates to the state. This means that instead of it working synchronously, it just applies setState({ labelCounter: this.state.labelCounter + 1}) after the loop, and this.state.labelCounter + 1 is resolved to a fixed number (1 in this case), that is reapplied 10 times. So the labelCounter is set to 1 10 times.
In the last example you are updating by changing the property yourself (and not having react do it), which is why it works.
I would guess the best way to do it is to wait for a batch to have been applied (for example with setTimeout(x, 0)) and doing the rest after that, or trying to avoid this altogether.
Correct me if I am wrong, but "this.state.labelCounter + 1" is not the same as "this.state.labelCounter ++" because it is evaluating the state value before making the changes to the state value.
Another option would be "this.state.labelCounter += 1"
Source:
(Not exactly similar)
Difference between ++ and +=1 in javascript

React: How to read from the DOM after mount?

In my React component, I need to read data from the DOM when it's available for later parts of my application to work. I need to persist the data into state, as well as send it through Flux by dispatching an action. Is there a best practice for doing this?
To be more specific, I need the data from the DOM in at least two cases:
Visually immediate, as in the first thing the user sees should be "correct," which involves calculations based on data read from the DOM.
Data used at a later time (such as the user moving the mouse), but based on the initial DOM state.
Please consider the following:
I need to read from the DOM and save the data. This DOM is available in componentDidMount, however I cannot dispatch actions in componentDidMount because this will case a dispatch during a dispatch error. Dispatching in component*mount is an anti-pattern in React for this reason.
The usual workaround (hack) for the above is to put the dispatch inside componentDidMount in a setTimeout( ..., 0 ) call. I want to avoid this as it seems purely like a hack to bypass Flux's errors. If there truly is no better answer I will accept it, but reluctantly.
Don't answer this question with don't read from the DOM. That's not related to what I'm asking. I know this couples my app to the DOM, and I know it's not desired. Again, the question I'm asking is unrelated to whether or not I should be using this method.
Here is the pattern I use in reflux.
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getAllState();
}
componentDidMount = () => {
// console.log('AppCtrl componentDidMount');
this.unsubscribeApp = AppStore.listen(this.appStoreDidChange);
this.unsubscribeGS = GenusSpeciesStore.listen(this.gsStoreDidChange);
Actions.setWindowDefaults(window);
}
componentWillUnmount = () => { this.unsubscribeApp(); this.unsubscribeGS(); }
appStoreDidChange = () => { this.setState(getAppState()); }
gsStoreDidChange = () => { this.setState(getGsState()); }
shouldComponentUpdate = (nextProps, nextState) => {
return nextState.appStoreChanged && nextState.gsStoreChanged;
}
}
In app.store.js
function _windowDefaults(window) {
setHoverValues();
let deviceTyped = 'mobile';
let navPlatform = window.navigator.platform;
switch (navPlatform) {
case 'MacIntel':
case 'Linux x86_64':
case 'Win32': deviceTyped = 'desktop'; break;
}
//deviceTyped = 'mobile';
_appState.deviceType = deviceTyped;
_appState.smallMobile = (deviceTyped == 'mobile' && window.screen.width < 541);
//_appState.smallMobile = (deviceTyped == 'mobile');
if (deviceTyped == 'desktop') {
let theHeight = Math.floor(((window.innerHeight - 95) * .67));
// console.log('_windowDefaults theHeight: ', theHeight);
_appState.commentMaxWidth = theHeight;
_appState.is4k = (window.screen.width > 2560);
Actions.apiGetPicList();
} else {
React.initializeTouchEvents(true);
_bodyStyle = {
color: "white",
height: '100%',
margin: '0',
overflow: 'hidden'
};
AppStore.trigger();
}
}

Categories