React: this.setState is not a function [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 1 year ago.
I'm working on a workaround for another problem I'm having but with this I got a "this.setState is not a function" error. I found this answer which advises to bind it within the constructor, which I did.
This is part of my constructor:
this.newProject = this.newProject.bind(this);
this.openProject = this.openProject.bind(this);
this.saveProject = this.saveProject.bind(this);
And this is my function:
// Open a file, set data as session item and reload page
openProject(FileObject) {
var read = new FileReader();
read.readAsBinaryString(FileObject);
read.onloadend = function() {
//sessionStorage.setItem("reloading", "true");
//sessionStorage.setItem("data", read.result);
//document.location.reload();
// Fix for missing data.
var jsonData = JSON.parse(read.result);
for (var i = 0; i < jsonData.blocks.length; i++) {
var name = jsonData.blocks[i].name;
var id = jsonData.blocks[i].id;
var ip = jsonData.blocks[i].ip;
var port = jsonData.blocks[i].port;
this.setState( { blockCount: (i + 1), });
// Add block to the list
this.setState({
blocks: this.state.blocks.concat({
id: id,
name: name,
ref: React.createRef(),
positionX: window.innerWidth*0.4 - 125 / 2,
positionY: 75 + ( 50 * this.state.blocks.length),
links:[],
requires: this.state.parameters.blockRequires
})
});
}
}
}
What would be a solution to this?

this belongs to the closest function context, which in your case is read.onloadend = function()...NOT the class.
You can solve this problem by assigning the class-level this to a new variable before you enter that ad-hoc function:
openProject(FileObject) {
var read = new FileReader();
read.readAsBinaryString(FileObject);
var that = this;
read.onloadend = function() {
// ...
that.setState(/*... etc*/
And of course you'll want to change all instances of this within your onloadend callback function to that (or whatever variable name you choose).
*Edit: as #MDTabishMahfuz describes, you can also use an arrow function, because unlike the function declaration, an arrow function:
Does not have its own bindings to this or super

Binding this is necessary for functions of the class but you have an additional callback (onloadend function) which is a different function and the component's this is not available there.
Conventional Functions in JS have their on this in their context. You can either use the method suggested by #David784 or use an arrow function for the onloadend handler like so:
read.onloadend = () => {
....
this.setState(....);
....
}
Arrow functions have this from the parent's context, that is the React component in your case.

Related

Why am I getting "potentially invalid reference access to a class field via this in a nested function" error

In vanilla JS, my code would work fine. For this case, I'd like to componentize my Wall class which's supposed to display the image in the browser that the user has uploaded. Again, this works normally in vanilla JS but not JSX.
I'm getting a potentially invalid reference access to a class field via this in a nested function on the document.querySelector("#file-input").addEventListener("change", this.previewImages); line which I think is causing the issue.
What am I doing wrong and how can I fix it?
import React, {Component} from 'react';
class Wall extends Component {
constructor(props) {
super(props);
this.previewImages = this.previewImages.bind(this);
}
previewImages() {
let preview = document.createElement("div");
if (this.files) {
[].forEach().call(this.files, readAndPreview());
}
function readAndPreview() {
if (!/\.(jpe?g|png|gif)$/i.test(file.name)) {
return alert(file.name + " is not an image");
}
let reader = new FileReader();
reader.addEventListener("load", () => {
let image = new Image();
image.height = 100;
image.title = file.name;
image.src = this.result;
let date = Date.now();
let d = new Date(parseInt(date, 10));
let ds = d.toString("MM/dd/yy HH:mm:ss");
console.log(ds);
let initialCountOfLikes = 0;
let zeroLikes = document.createElement("h1");
let zeroLikesTextNode = zeroLikes.createTextNode(initialCountOfLikes + " likes");
zeroLikes.appendChild(zeroLikesTextNode);
preview.appendChild(image); // makes image appear
preview.appendChild(zeroLikes); // makes like count appear
image.ondblclick = function() {
if (initialCountOfLikes === 0) {
console.log("Inside if block");
initialCountOfLikes++;
console.log("initialCountOfLikes++ => " + initialCountOfLikes);
} else if (initialCountOfLikes === 1) {
console.log("inside second else if block");
initialCountOfLikes--;
console.log("initialCountOfLikes-- => " + initialCountOfLikes);
}
zeroLikesTextNode.nodeValue = initialCountOfLikes + " likes";
};
});
reader.readAsDataURL(file);
document.querySelector("#file-input").addEventListener("change", this.previewImages);
}
}
render() {
return (
<div id="file-input-wrapper">
<input type="file" />
<label htmlFor="file-input" id={"LblBrowse"} />
</div>
);
}
}
export default Wall;
The warning is telling you that using this in JavaScript frequently has confusing implications, specifically when used inside a function nested inside another function. this stops referring to the class, and instead refers to the scope of your nested function.
In your case, this probably is a legitimate problem (I think) because you have your class, Wall, which has a method previewImages() and a property files. Within that function, you have instantiated a new function, readAndPreview(), inside which you specify this.previewImages as a function callback to the addEventListener function.
They're saying you're potentially using this.previewImages incorrectly, because you're writing functions in traditional JavaScript syntax, function foo() { ... }, where this keeps being redefined in each child function call. In your case, I believe that this is referring to the context of readAndPreview(), and hence cannot access the method this.previewImages() since this doesn't refer to your parent class, Wall.
People used to do things like, make a var that = this; on the parent class, and you'd know that that always meant the parent class.
But now, with ES6 lambda functions using the "fat arrow" syntax () => { } you can access this and know it's referring to the parent scope.
I believe you can refactor your class to change previewImages() { into previewImages = () => { and know that this will refer to the class. You'll have to do the same with function readAndPreview() {. Change it to const readAndPreview = () => {. If you're setting it to a variable, though, I think you'll have to move it above the place you call it, though. e.g. above
if (this.files) {
[].forEach().call(this.files, readAndPreview());
}
I faced this error in Angular 8.
I used the Arrow function instead of regular functions to solve.
In your case.
readAndPreview = () => { ... }
This might solve your problem.
Using of arrow function may help you. Arrow functions don't have their own bindings to this, arguments or super.

JavaScript constructor Methods calling other methods in same constuctor [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 years ago.
I'm trying to call an object method from another method within the same constructor, and it doesn't seem to be working - I just get the error TypeError: undefined is not a function (evaluating 'this.uiDisplayOptions()').
I'm instantiating the object using using the new keyword var ui = new _ui().
Anyone know why it's not working? I've seen examples of this kind of setup being suggested.
Here's the code:
function _ui() {
this.uiDisplayOptions = function() {
var len = channels[currentChannel].stepsChannel;
$("select#length option")
.prop('selected', false)
.filter('[value="' + len + '"]')
.prop('selected', true);
var rand = channels[currentChannel].randomAmtChannel;
$("select#randomness option")
.prop('selected', false)
.filter('[value="' + rand + '"]')
.prop('selected', true);
var dir = channels[currentChannel].readDirection;
$("select#readdirection option")
.prop('selected', false)
.filter('[value="' + dir + '"]')
.prop('selected', true);
}
this.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(function() {
$(".channelselect").removeClass("green");
$(this).addClass("green");
currentChannel = $(this).data("channel");
displayUpdateChannel();
this.uiDisplayOptions();
});
// Select Row
$("#selectRow0").addClass("red");
$("#selectRow0").click(function() {
currentRow = 0;
$("#selectRow1").removeClass("red");
$(this).addClass("red");
});
$("#selectRow1").click(function() {
currentRow = 1;
$("#selectRow0").removeClass("red");
$(this).addClass("red");
});
// Increment/Decrement Selected Row Pattern
$("#patternInc").click(function() {
selectPatternRow(1);
displayPattern();
});
$("#patternDec").click(function() {
selectPatternRow(-1);
displayPattern();
});
// Shift Left/Right Selected Row Pattern
$("#shiftLeft").click(function() {
selectShiftRow(-1);
displayPattern();
});
$("#shiftRight").click(function() {
selectShiftRow(1);
displayPattern();
});
// Handle Row 'Pattern Locks'
$(".lock").click(function() {
var step = $(this).data("lockstep");
switch(toggleLockBit(step)) {
case 0:
$(this).removeClass("red green");
break;
case 1:
$(this).addClass("red");
break;
case 2:
$(this).removeClass("red").addClass("green");
break;
}
displayPattern();
});
// Handle Channel Length change
$("#length").change(function() {
selectCurrentChannelLength($(this).val());
displayChannelLength();
});
// Handle Channel Randomness change
$("#randomness").change(function() {
selectCurrentChannelRandomAmt($(this).val());
displayRandomAmt();
});
}
}
this.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(function() {
$(".channelselect").removeClass("green");
// this here does not refer to the this of the object being created.
// it refers to the anonymous function being created in the click call.
// jQuery is probably invoking this and binding this to undefined,
// but even if it wasn't then this code would behave incorrectly.
$(this).addClass("green");
currentChannel = $(this).data("channel");
displayUpdateChannel();
this.uiDisplayOptions();
});
});
When inside a function the this's value may change. It has it's own binding called a ThisContext and cannot be guaranteed to be pointing at the object you are calling this from within (especially with the introduction of bind, apply and call). Inside uiSetListeners, this is generally bound to the function (which in turn is bound to the object, assuming you are invoking the constructor correctly, and not using any bind magic).
However inside your click, handler, you are delegating the function to jQuery. jQuery doesn't know about your object so it doesn't bind this (or binds it to undefined), and it isn't associated with an object by default (as the function is being declared anonymously and not bound to an object). In other words, your click handler is pointing to a different this than your this.uiSetListeners statement is.
The way to fix this is by using a var that = this; kind of mechanism. If you take this approach, you should probably define var that = this at the top of your constructor function (so others can see what's going on) and replace any incidence of this inside of the constructor function with that.
This ensures that should another user call your constructor with call, bind et al, the object will be bound correctly to the supplied this.
var that = this;
that.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(function() {
$(".channelselect").removeClass("green");
$(this).addClass("green");
currentChannel = $(that).data("channel");
displayUpdateChannel();
that.uiDisplayOptions();
});
});
Note that ES6 fixes this with the fat arrow notation.
this.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(() => {
$(".channelselect").removeClass("green");
$(this).addClass("green");
currentChannel = $(this).data("channel");
displayUpdateChannel();
this.uiDisplayOptions();
});
});
You should be perfectly able to invoke other methods from within the constructor as long as you remember to take care with your this.
It is generally preferable to use YourConstructor.prototype.methodName instead, as this will first of all reduce nesting but also uses the prototype chain. Assigning functions to this in the constructor does not assign them to the prototype chain, which also means they will be recreated each time a new object is created. You only really need to assign functions to this inside of a Constructor if their implementation is dependent on the values passed into the constructor and it is not appropriate to capture those values in the constructor as state on the created object.
you can't call a function like this inside a constructor, this will refer to global object window until you call your constructor function using new keyword.
var ui = new _ui();
refer the current object context on the top of you constructor function.
function _ui() {
var _that = this;
}
and refer all current constructor function using _that reference.
I hope it will solve your problem.

Javascript Scoping Issue in Backbone Model / Collection

I'm working with a tree structure, so i need to do some pretty wonkey finds whenever I want to work my way from the leaves to the trunk, but I'm basically trying to create a function that I can pass a function to and apply / call / bind / something the original context so that I can see the variables that i had originally. An explaination would be awesome.
layerListView = Backbone.Marionette.CollectionView.extend({
updateSelectedModelsInTree: function () {
var col = myApp.request('someOtherCollection');
this.collection.startFromLeaves(function (m) {
this.updateSelected(m);
// col is undefined in here
}, this);
}
});
layerCollection = Backbone.Collection.extend({
startFromLeaves: function (doToModel, context) {
if (!this.models) return;
for (var i = this.models.length - 1; i >= 0; i--) {
var model = this.models[i],
tag = this.models[i].get('tag');
if (tag == 'branch') this.startFromLeaves(arguments);
doToModel.call(context, model);
}
}
});
so i'm stuck here, and all I want to do is to be able to see the col variable inside of the top function that is passed into startFromLeaves. I have no idea how to use call / bind / apply, but I'm guessing that my context is what is throwing everything off.
Check out the bind function on underscore.js
This allows you to pass a function that has the this context set. So for instance you could do
updateSelectedModelsInTree: function () {
var col = myApp.request('someOtherCollection');
this.collection.startFromLeaves(_.bind(function (m) {
this.updateSelected(m);
// col is undefined in here
}, this));
}
"this" inside your function will now always be the "this" that contains "updateSelectedModelsInTree"

Javascript Object custom functions [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
Javascript infamous Loop problem?
I have a base function, that I want to control what ends up being like a "bumpbox". My goal is to instantiate multiple instances of this and give each of the declared variables a custom config.
The object looks like this:
Project.Modules.bumpbox = function(in_trigger, container) {
var config = {
'speed': 500,
'easing' : false,//will
'in_callback': false,
'out_callback' : false,
'visible': false,
'out_trigger' : $('#bumpbox_out_trigger'),//this is set by default only change if you know what you are doing!
};
this.test = function() {
//this should be the default function.
};
And then, from another file, I want to instantiate an instance like new Project.Modules.Bumpbox() and overwrite the test function.
var bumpbox_controllers = {
"map" : new Project.Modules.bumpbox($('#navigation_top li.map'), $('.bumpbox.map')),
"contact" : new Project.Modules.bumpbox($('#navigation_top li.contact'), $('.bumpbox.contact')),
"about" : new Project.Modules.bumpbox($('#navigation_left li.about'), $('.bumpbox.about')),
"team" : new Project.Modules.bumpbox($('#navigation_left li.team'), $('.bumpbox.team')),
"careers" : new Project.Modules.bumpbox($('#navigation_left li.careers'), $('.bumpbox.careers')),
"services" : new Project.Modules.bumpbox($('#navigation_left li.services'), $('.bumpbox.services'))
};
and then I want to loop through each of those and set a custom test() function in each like this:
bumpbox_controllers[i]['test'] = function() {
alert(i);
}
But when I run this code, it will switch all of the elements to the last i value called, in this case "service", not giving each a unique element.
You seem to need a closure for your loop:
for (var controller in bumpbox_controllers) {
bumpbox_controllers[controller] = (function(i) {
// creating a new context for i
return function() {
alert(i); // access the i in scope, not the controller
}
})(controller);
}
To allow each test() to have its own unique i, try:
bumpbox_controllers[i]['test'] = (function (i) {
return function () {
alert(i);
};
}(i));​

Understanding Scope of 'this' in jQuery on change event

I wrote a quick custom extension for jQuery for a project I am working on. I am having a hard time understanding the scope of 'this' in a custom onChange method I would like implement. If left out the middle of my code where I am calling the webservice but if you checkout the last two methods, you will see where my problem is. I want to call the updateDetails method with the selected value changes. However, when that method is called within the onchange event, I obviously lose the scope of "this" as this.materialListResponse comes back as undefined in this context. Any help on helping me understand this would be greatly appreciated.
$.fn.appendMaterials = function (options) {
this.options = options;
//Set Default Options
this.defaults = {
typeID: '66E1320D-51F9-4900-BE84-6D5B571F9B80'
};
this.options = $.extend({}, this.defaults, options);
//
Code here to call web service and generate response XML
//
this.materialListResponse = $.xml2json(
$.parseXML($(this.materialListWebservice()).find("GetMaterialTreeResponse").text())).Materials.Material;
this.appendOptionString = function () {
var i = 0;
this.optionString = '<option>'
for (i = 0; i < this.materialListResponse.length; i++) {
this.optionString += '<option>' + this.materialListResponse[i].MaterialCode + '</option>';
};
this.append(this.optionString);
return this;
};
this.appendOptionString();
this.updateDetails = function () {
for (i = 0; i < this.materialListResponse.length; i++) {
if (this.materialListResponse[i].MaterialCode === this.val()) {
$('#table1 #MaterialDescription').val(this.materialListResponse[i].Description);
}
}
}
this.change(this.updateDetails)
};
pass the object this as data to the event:
this.change({that: this}, this.updateDetails)
and then you can access that in the scope of the event callback
this.updateDetails = function(event) {
var that = event.data.that;
...
}
RESOURCES
http://api.jquery.com/event.data/
The event handler will be called later, when you have exited the extension. It's called in the scope of the element, so this will be the element that has changed.
Copy the reference to the list to a local variable, and use that in the event handler:
var list = this.materialListResponse;
this.updateDetails = function() {
for (i = 0; i < list.length; i++) {
if (list[i].MaterialCode === this.val()) {
$('#table1 #MaterialDescription').val(list[i].Description);
}
}
}
By using the local variable in the function, the variable will be part of the closure for the function, so it will survive the scope of the extension method where it is declared.
When a method is called in JavaScript as a callback it behaves as a function. In this case "this" refers to the owner of this function, usually the Window object in a Web browser.

Categories

Resources