I'm using React and Redux. I have a question. I know when I need "this" in a function I should bind it. But without binding my code works. Why ?
Function :
onSubmit() {
this.props.dispatch({type: 'ADD_TO_LIST', payload: this.state.inputValue});
}
And this is my render input :
<input type="text" placeholder="Enter Your Text ..." onChange={(e) => {
this.setState({inputValue: e.target.value})}} onKeyDown={(e) => {
if (e.key === 'Enter') {
this.onSubmit()
}
}}/>
You have idea about "when binding is required", but you missed one thing, "calling function will have the this (object by which it was called)". The value of this is determined by how a function is called.
Here:
this.onSubmit()
You are calling submit with this (class instance), so this (inside submit) will refer to class instance.
Check these references:
MDN Doc
Why is JavaScript bind() necessary?
Because you used an arrow function to call it, it's not the event handler itself. The arrow function is already bound correctly.
While function is binding (it changes this), ES6 arrow functions are non-binding. An example:
this.a = 1;
obj = {
a: 2,
bfunction: function() { return this.a; },
barrow: () => { return this.a; },
}
console.log(obj.bfunction()) // 2
console.log(obj.barrow()) // 1
Related
Uncaught TypeError: Cannot read property 'target' of undefined
Handler for dropdown :
handlePortfolioChange = data => (event) => {
this.resetForm();
this.setState({ selectedPortfolio: event.target.value });
}
Actual SelectBox
<Select value={this.state.sp}
onChange={e =>
this.setState({sp: e.target.value
}, this.handleChange(data, e))}
>
You have a curried function there (it takes multiple sets of arguments). In your case data and event.
You can invoke it like this with both particular sets of arguments at once: handlePortfolioChange(data)(event) (instead of handlePortofolioChange(data,e)).
Maybe it's of help to mention that you can also define a more narrow function with a particular data object to use every time to only have to vary the event like this:
//say, you had data-object that doesn't change in this scope like constantData
constantData = {
"some": 1,
"data": 2,
"here": "idk"
}
// then you could define a more particular function that handlePortfolioChange, by passing //only the first argument to the general function:
handleConstantPortfolioData = handlePortfolioData(constantData);
You could then invoke it anywhere but only worry about the event:
...onChange={e => this.handleConstantPortfolioData(e)}...
I'm new to react coming from a .Net background and I was trying to create a class, I tried numerous ways of doing so but in this instance I was unable to create a constructor in this variation and came to the conclusion that this maybe isn't a class, I've searched around the web but haven't found any info
Here is an example:
export default ViewTestStuff => {
constructor(){
// errors
}
return (
<div>
<p>Hello</p>
</div>
)
}
so my question is what is the "=> {}" in this example, is this a class? and why can't I create a constructor in it if it is indeed a class
It is a Arrow Function from es6, and has nothing to do with React.js
const add = (a, b) => a+b;
it is just a function.
calling add(2, 3) returns 5
One important thing to remember is, that arrow functions do not have the prototype chain. You also cannot call them with new.
Another thing to notice is, that this is bound to the context where the arrow function is defined.
const obj = {
name: "Lukas",
method: function() {
var self = this;
console.log(this.name === "Lukas");
return [
function() {
console.log(this !== self)
},
() => {
console.log(this === self)
}
];
}
}
const [func, arrow] = obj.method();
func();
arrow();
see the docs
It is an arrow function! A nice feature on ES6, that is already implemented on most modern browsers.
Something => {} Means a function with Something as it's parameter and an empty body. It is similar to:
function (Something) {
}
In your case, it's the same as:
export default function (ViewTestStuff) {
constructor(){
// errors
}
return (
<div>
<p>Hello</p>
</div>
)
}
And it's indeed invalid.
This is not a React thing... arrow functions are new in es6 javascript. More info can be found here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
I'm passing the following functions to the following component...
<Main css={this.props.css} select={this.selectedCSS.bind(this)} save={this.saveCSS.bind(this)} />
Then inside the Main component I am using these functions...
<h1>Select the stylesheet you wish to clean</h1>
{
this.props.css.map(function(style){
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
})
}
</div>
<button className="cleanBtn" onClick={this.props.save}>Clean!</button>
Notice in my map function, I am passing this.props.select(style). This is a function from the parent, and I am trying to pass to it a parameter. But when I do this, I get an error...
Error in event handler for runtime.onMessage: TypeError: Cannot read property 'props' of undefined
Every other function I pass works. I've already tested them. In fact, the code works, the only issue is when I try to pass a function inside map. What is the reason for this? How can I fix it?
I tried to add .bind(this) but it runs in an infinite loop when I do so.
The problem is that Array.prototype.map doesn't bind a this context unless explicitly told to.
this.props.css.map(function(style) {
...
}, this) // binding this explicitly
OR
this.props.css.map((style) => { // arrow function
...
})
OR
const self = this;
this.props.css.map((style) => {
... // access 'self.props.select'
})
I also see another problem with your code. Inside map you're doing this
if (style) {
return (
<div className="inputWrap">
<input type="radio" name="style_name" onClick={this.props.select(style)}/>
<span>something</span>
<a key={style}>{style}</a>
</div>
);
}
Here input element is expecting a function for its onClick, but you're actually evaluating the function by calling this.props.select(style) and passing its return value (if at all it returns something) to onClick. Instead you may need to do this:
this.props.css.map((style) => {
if (style) {
return (
<div className="inputWrap">
<input type="radio" name="style_name" onClick={() => this.props.select(style)}/>
<span>something</span>
<a key={style}>{style}</a>
</div>
);
}
})
In your mapping function, this does no longer point to the react component.
Bind the context manually to resolve this:
{
this.props.css.map((function(style) {
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
}).bind(this))
}
Alternatively, use an ES6 arrow function, which preserves the surrounding context:
{
this.props.css.map(style => {
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
})
}
You mentioned passing a parameter when calling a parent's function? As onClick wants a reference to a function (but realising that you need to pass a parameter), you could try the following:
onClick={() => { this.props.select(style) }}
I have this method inside a React component (which I later pass to the render() method):
renderNutrientInputs: function (num) {
var inputs = [];
for (var i =0; i < num; i++) {
inputs.push(<div key={i}>
<label>Nutrient name: </label><input type="text"/>
<label>Nutrient value: </label><input type="text" />
</div>);
}
return inputs;
}
I'm trying on each change of the "Nutrient value" textbox, to also grab the current value of the "Nutrient name" textbox. I first though of assigning "ref" to both of them, but I figured there might be multiple pairs of them on the page (and the only way to identify them would be by key). I also tried something like this:
<label>Nutrient name: </label><input type="text" ref="nutName"/>
<label>Nutrient value: </label><input type="text" onChange={this.handleNutrientValueChange.bind(null, ReactDOM.findDOMNode(this.refs.nutName))}/>
but got a warning from React:
Warning: AddForm is accessing getDOMNode or findDOMNode inside its
render(). render() should be a pure function of props and state. It
should never access something that requires stale data from the
previous render
Is there some way to attach onChange event listener to Nutrient value text box and access the current value of "Nutrient name" textbox in the event listener function?
You don't want to access DOM elements directly. There is no need to do so... Work with your data, forget about DOM!
What you want is to "listen to changes to n-th nutritient. I want to know it's name and it's value". You will need to store that data somewhere, let's say in state in this example.
Implement getInitialState method. Let's begin with empty array, let user to add nutritients.
getInitialState() {
return { nutritients: [] };
},
In render method, let user add nutrition by click on "+", let's say
addNutritient() {
const nutritients = this.state.nutritients.concat();
nutritients.push({ name: "", value: undefined });
this.setState({ nutritients });
},
render() {
return (
<div>
<div onClick={this.addNutritient}>+</div>
</div>
)
}
Okay, let's focus on rendering and updating nutritients:
addNutritient() {
const nutritients = this.state.nutritients.concat();
nutritients.push({ name: "", value: undefined });
this.setState({ nutritients });
},
renderNutritients() {
const linkNutritient = (idx, prop) => {
return {
value: this.state.nutritients[idx][prop],
requestChange: (value) {
const nutritients = this.state.nutritients.concat();
nutritients[idx][prop] = value;
this.setState({ nutritients });
},
}
};
const nutritients = [];
return (
this.state.nutritients.map((props, idx) => (
<div>
<input valueLink={linkNutritient(idx, "name")} />
<input valueLink={linkNutritient(idx, "value")} />
</div>
))
)
},
render() {
return (
<div>
{ this.renderNutritients() }
<div onClick={this.addNutritient}>+</div>
</div>
)
}
Coding by hand, sorry for syntax error or typings.
Edit:
Take a look at this working Fiddle https://jsfiddle.net/Lfrk2932/
Play with it, it will help you to understand what's going on.
Also, take a look at React docs, especialy "valueLink" https://facebook.github.io/react/docs/two-way-binding-helpers.html#reactlink-without-linkedstatemixin
I prefer not to use 2 way binding with React which is kind of a flux anti-pattern. Just add a onChange listener to your input element and setState.
Your state will be something like this:
{0: {nutrientName: xyz, nutrientValue: 123},
1: {nutrientName: abc, nutrientValue: 456}}
So when you change the nutrientvalue 456 to say 654, you can say its corresponding name is abc and vice versa.
The whole thing about React is about handling the data not the DOM :)
<Multiselect label='Select College' ref="collegeList" onChange={this.handleChange} multiple >
<option value='college1'>college1</option>
<option value='college2'>college2</option>
</Multiselect>
This component is from https://github.com/skratchdot/react-bootstrap-multiselect
What should be written inside the handleChange() function ?
Here's a much cleaner, es6 way to do it :)
let selected = [...this.refs.select]
.filter(option => option.selected)
.map(option => option.value)
There you go, all of the selected options!
Unfortunately, react-bootstrap-multiselect doesn't seem to expose any sort of API for getting the currently selected items, so you'll have to query them from the DOM directly. Try something like this:
handleChange: function () {
var node = React.findDOMNode(this.refs.collegeList);
var options = [].slice.call(node.querySelectorAll('option'));
var selected = options.filter(function (option) {
return option.selected;
});
var selectedValues = selected.map(function (option) {
return option.value;
});
console.log(selectedValues);
}
If you're using jQuery, this can be simplified a bit to:
handleChange: function () {
var node = $(React.findDOMNode(this.refs.collegeList));
var selectedValues = node.children('option:selected').map(function(option) {
return option.value;
});
console.log(selectedValues);
}
I would suggest to have a state in your component called selectedItems
The onChange callback then, takes as parameters element and checked, from the Bootstrap Multiselect docs. Element has the val() method, which returns the value assigned to the option.
Therefore handleChange could be implemented in the following way
handleChange: function (element, checked) {
var newSelectItems = _.extend({}, this.state.selectItems);
newSelectItems[element.val()] = checked;
this.setState({selectItems: newSelectItems})
},
getInitialState: function () {
return {selectItems: {}};
}
In this way, every time an element is clicked, its checked attribute is saved in the component state, which is quite handy if you need to change anything based on the MultiSelect selected values.
Please note that for the above code you need either the Underscore or the Lodash library. This is necessary as React cannot merge nested objects, as answered here.
A much simpler and a direct way to get the values:
handleChange: function () {
var selectedValues = this.refs.collegeList.$multiselect.val();
console.log(selectedValues);
}
you can use selectedOption that return a htmlCollection like this
onchange(e){
let selected=[];//will be selected option in select
let selected_opt=(e.target.selectedOptions);
console.log(selected_opt)
for (let i = 0; i < selected_opt.length; i++){
selected.concat(selected_opt.item(i).value)
}
}
and our select
<select onChange={this.onchange.bind(this)} className="selectpicker w-100 rtl" multiple >
{this.state.list.map(obj=><option value={obj[this.props.val]}>{obj[this.props.name]}</option>)}
</select>
clean es6 code using selectedOptions
let selected = [...this.refs.collegeList.selectedOptions].map(o => o.value);
React 16 with TS.
Note: refs are deprecated, that's why I used callback function to set Ref.
private selectInput: any;
private setSelectRef = element => {
this.selectInput = element;
};
private handleMultiSelectChange = () => {
const selected = [...this.selectInput.refs.MultiselectInternal.selectRef].filter(option => option.selected)
.map(option => option.value);
// there you can update your state using this.setState()
};
...
<Multiselect data={data}
onChange={this.handleMultiSelectChange}
multiple={true}
ref={this.setSelectRef}
buttonClass="btn btn-default"
/>