How to get the Ref of input after a click - reactjs

I'm using an array of string categories to create input checkboxes. Checking the input box tells me you want to include that category and unchecking an input box tells me you don't want to include that category.
Problem is, I don't know what the ref of the object that is being clicked. How do I figure out the ref?
I'd like to pass this ref back up to my parent class so that it can find the index of the 'ref' in my array of categories, and splice that out. The result being that my filteredCategories array will remove the item when it is there and add it if it isn't
var HeaderCategories = React.createClass({
handleChange:function(e){
// I know what was clicked through e.target
// but I'd like to know the ref of what was clicked
// so that I can do somthing like the below:
this.props.filterTable(this.refs[e.target.ref])
},
render: function(){
var categories = this.props.allCategories.map(function(category){
return (
<label key={category}>{category}
<input type="checkbox" ref={category} onChange={this.handleChange}/>
</label>
)}.bind(this))
return (
<div className="categories __header" >{categories}</div>
);
}});
var Table = React.createClass({
allCategories: ["apples","oranges","bananas"]
componentDidMount:function(){
this.setState({filteredCategories:this.allCategories})
},
getInitialState:function(){
return {filteredCategories:this.allCategories}
},
filterTable:function(category){
// If I have the ref then I can use a splice and remove it from my filtered Categories
//this is pseudo code, havent checked if it works
var index = filteredCategories.indexOf(category)
var filteredCategories.splice(index,filteredCategories.length)
this.setState(filteredCategories:filteredCategories)
},
render:function(){
return <Header filterTable={this.filterTable} allCategories={this.allCategories}>
}
})

There is not a good way to get a reference to a component from the event param of the onClick callback. You best option is to curry either the category index or name to the callback like this (in this example, I'm passing back just the index)
handleChange:function(e, categoryIndex){
// `categoryIndex` was curried in the click handler
// of each checkbox in the render function below so
// the filterTable callback should accept the index.
this.props.filterTable(categoryIndex);
},
render: function(){
var categories = this.props.allCategories.map(function(category, index){
return (
<label key={category}>{category}
<input type="checkbox" ref={category} onChange={this.handleChange.bind(this, index)}/>
</label>
)}, this); // map's second param is thisArg
return (
<div className="categories __header" >{categories}</div>
);

Related

.filter() function creating loop in delete function - React

I've got a 'list' component, which allows you to add an item to a state array, and then display it from the state afterwards. These list items can then be removed by the user afterwards (or should be able to).
There's four state props in this component:
currentList: content in the input that's to be added to the list array
setCurrentList: what's used to change the content in the input
fullList: the full array list
setFullList: used to add the currentList content to the array, and removed
I'm using .filter() to create a copy of the state array, and then set the state afterwards in this function:
const deleteFromList = (e) => {
console.log("Delete button pressed")
console.log(e)
let fullList = props.fullListState
let setFullList = props.setFullListState
let filteredArray = fullList.filter(item => item)
setFullList(filteredArray)
}
However, every time I execute this function (i.e. when the delete button is pressed), it just creates a loop and the first two console.logs are just repeatedly done.
This is the full return function itself:
<>
<label className="setup-jobs-label">{props.label}</label>
<div className="setup-jobs-input-container">
<input className="setup-jobs-alt-input" type="text" onChange={onChange} value={props.currentListState} />
<button className="setup-jobs-add-button" onClick={addToList}>Add</button>
</div>
{ props.fullListState === [] ? null : props.fullListState.map(x => {
return <div className="setup-jobs-input-container" key={props.fullListState[x]}>
<p className="setup-jobs-input-paragraph">{x}</p>
<button className="setup-jobs-delete-button" onClick={deleteFromList(x)}>Delete</button>
</div>
}) }
</>
The important bit is the bottom conditional render, which checks to see if the state array is empty, and if so, not display anything. If it isn't, then it returns null.
Any advice would be appreciated - not sure what I'm doing wrong in the filter function.
In your onClick handler, you pass the result of the execution of deleteFromList, you should pass a reference to this function instead :
// note the '() =>'
<button className="setup-jobs-delete-button" onClick={() => deleteFromList(x)}>Delete</button>
See https://reactjs.org/docs/handling-events.html for more details about this.
Beside this, your filter logic does not seem right :
// this line only removes falsy values, but not the "e" values
let filteredArray = fullList.filter(item => item)
// you should implement something like this
let filteredArray = fullList.filter(item => [item is not "e"])
// this should work as we work on objects references
let filteredArray = fullList.filter(item => item !== e)

Accessing state of children from parent component?

I don't think I fully understand how the Parent/Child relationship works in React. I have two components, Column and Space. When a Column is rendered, it creates some Spaces. I thought that meant that the Column would be the parent to those Spaces, but either I'm wrong or I'm using some part of React.Children.count(this.props.children) incorrectly - it always tells me that any given Column has 0 children.
Simplified models:
var Column = React.createClass({
getInitialState: function() {
return {childCount: '', magicNumber: this.props.magicNumber};
},
componentDidMount: function() {
var newCount = React.Children.count(this.props.children);
this.setState({childCount: newCount});
},
render: function() {
return (
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
</div>
);
}
});
var Space = React.createClass({
render: function() {
return (
<div className="Space">
space here
</div>
);
}
});
It seems like no matter where I put React.children.count(this.props.children) in a Column, it tells me there are 0 children. I would expect the Column generated in the example code to have five children, since five Spaces are created within it.
I figured maybe I was trying to count the Children before they were fully loaded, so I tried writing a click handler like this:
//in Column
setChildCount: function () {
var newCount = React.Children.count(this.props.children);
this.setState({childCount: newCount});
}
Then adding a button like this in my Column:
...
render: function() {
return (
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
<button onClick={this.setChildCount} />
{this.state.childCount}
</div>
);
But my childCount is always and forever 0. Can anyone see where I'm going wrong?
Edit: Ultimately, I want to get a count of all children elements who have X attribute in their state set to Y value, but I am clearly a step or three away from that.
The Column component doesn't have any children on that code. Childrens are components which are wrapped by the parent component. So imagine:
<Column>
<Space/>
<Space/>
<Column/>
In this case the parent Column has two children Space
On your code:
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
</div>
You are creating new components inside a divnot inside the component Column
You are rendering Space components as part of the Column component. The parent/child relationship captured by this.props.children looks like this:
var Column = React.createClass({
render: function() {
<div className="column">
{this.props.children}
</div>
}
});
ReactDOM.render(
<Column>
<Space /> //little space children
<Space />
</Column>
);
To get at your specific problem, you don't need anything like this.props.children because you have everything right there in your render method.
So the answer to your question is: apply the same logic as when you render them.

Get element sibling value in React

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 :)

How to reset HTML input fields if I don't know beforehand how many inputs there will be?

Any ideas how to reset input fields when they are dynamically generated? Couldn't find any information about this. If I would know in advance how many fields there will be it would be easy with ref or just have unique value attributes on every input element and the call getInitialState().
https://jsfiddle.net/69z2wepo/15407/
Size of the product array can vary so how would you reset all of the input fields?
If you wrap your (uncontrolled) inputs in a HTML <form> element, there's always
<button type="reset">Reset</button>
Otherwise, you'll probably want to bind the input value to a prop or state item, and then use handleReset to retrieve and (re)set these values. When an item in state or prop changes, React will automatically update the affected parts of your DOM.
Read more about form inputs in the docs: http://facebook.github.io/react/docs/forms.html#controlled-components
you can set this.setState by array of values
https://jsfiddle.net/amb223fx/
Edit: As mgrim said, you can use HTML form reset. Or, if you need something more sophisticated, keep reading.
Take a look at this:
var products = [
{name: 'Chocolate'},
{name: 'Chips'},
{name: 'Candy'}
];
// Size of the products array can vary!
var Hello = React.createClass({
handleReset: function(){
var products = this.state.products.map(function (product) {
return Object.assign({}, product, {
value: 0
});
});
this.setState({products: products});
},
handleChange: function(index){
return function(e){
var products = this.state.products.slice();
var product = products[index];
products[index] = Object.assign({}, product, {
value: parseInt(e.target.value, 10)
});
this.setState({products: products});
}.bind(this);
},
getInitialState: function () {
return {
products: this.props.initialProducts.map(function (product) {
return {
name: product.name,
value: 0
}
})
}
},
render: function() {
var prods = this.state.products.map(function(item, id){
return (
<li key={id}>
{item.name}
<input type="number" value={item.value} onChange={this.handleChange(id)} />
</li>
)
}.bind(this));
return (
<ul>
{prods}
<button onClick={this.handleReset}>Reset</button>
</ul>
);
}
});
React.render(<Hello initialProducts={products} />, document.getElementById('container'));
You'll have to call setState every time there is a change in any of the input boxes, and setState when the user intends to reset all inputs.
Also, I did some refactoring. I renamed the products property to initialProducts property. This is because assigning a property to a state is an anti-pattern, unless we clarify that the inputed property will be a part of the state that will change over time.
The idea here is just to get all input and use a for to clean them up. Please note that I'm using Jquery to get all inputs.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
function cleanForm(){
var allInputs = $( ":input" );
for (i = 0; i < allInputs.length; i++) {
allInputs[i].value = "";
}
}
</script>
</head>
<body>
<form>
<input type="text" name="1"/>
<input type="text" name="2"/>
<input type="text" name="3"/>
<input type="text" name="4"/>
<input type="button" name="clean" onclick="cleanForm()" value="clean"/>
</form>
</body>

How to get multiple selected options value in React JS?

<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"
/>

Resources