I'm rewriting the UI for my web-app in react.js, and I'm a bit stumped by the following problem.
I have a page which displays data obtained via an AJAX request, and below that, a form to submit new data is displayed. All good.
Now, I want to add a <select>
element to the form, and fetch the values from a different location (url).
The current code (without the <select>
) looks like this (simplified a bit, but all the working details are the same; it mostly follows the tutorial on the react.js website):
var tasks_link = $('#tasks_link');
var getDataMixin = {
loadDataFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadDataFromServer();
}
};
var sendDataMixin = {
handleDataSubmit: function(senddata) {
$.ajax({
url: this.props.url,
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: senddata,
success: function(data) {
var curr_d = this.state.data;
var curr_d_new = curr_d.concat([data]);
this.setState({data: curr_d_new});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}
};
var taskForm = React.createClass({
handleSubmit: function() {
var name = this.refs.task_name.getDOMNode().value.trim();
if (!name) {
return false;
}
this.props.onTaskSubmit(JSON.stringify({name: name}));
this.refs.task_name.getDOMNode().value = '';
return false;
},
render: function () {
return (
<form className="well base_well new_task_well" onSubmit={this.handleSubmit}>
<div className="form-group">
<div className="input-group">
<span className="input-group-addon no_radius">Task name</span>
<input type="text" className="form-control no_radius" id="add_new_project_input" ref="task_name"/>
</div>
</div>
<button type="button" className="btn btn-default no_radius add_button" id="add_new_task_btn" type="submit">Add task</button>
</form>
);
}
});
var taskBox = React.createClass({
mixins: [getDataMixin, sendDataMixin],
render: function () {
return (
<div id="project_box" className="taskBox"> <taskList data={this.state.data} />
<taskForm onTaskSubmit={this.handleDataSubmit}/> </div>
);
}
});
tasks_link.click(function() {
React.renderComponent(
<taskBox url="/api/tasks/" />,
document.getElementById('content_container')
);
});
Now, I can add a select
element by adding a getDataMixin
to TaskForm
, fetching the data and building a list of possible options, but I will need to have forms with many lists, and the approach doesn't seem to scale (due to naming collisions; or I'll need to use something other than mixins).
So I though of creating a separate React class
, which would just have the getDataMixin
, receive the API url via the parent setting its props
, and render the <select>
element; and use this class inside the form.
But I have no idea how to access the selected value (since a parent cannot access it's child's refs
).
So I need another way to pass the selected value "up".
Or, in case this isn't possible, a nudge in the correct direction – I don't want to end up with a ton of un-reusable code (part of the reason why I switched to react was to use mixins and keep the code to a sane and readable minimum).
We will use HTML <select> tag and <option> tag. A constant array will be created which will have all the options. Under <select> tag we will iterate “options” array to display drop-down options. On <select> tag, we will use “onChange” property which will hold reference of “onOptionChangeHandler” function.
Ctrl + A can be used to select all files in the current directory. The fileSelect event will be triggered when the items of file manager control is selected or unselected. import * as React from 'react'; import * as ReactDOM from 'react-dom'; import App from './App'; ReactDOM.
A parent can access it's child's refs as long as you set the child as a ref inside the parent, you can access any refs inside that child. So for example something like this.refs.childRef.refs.nestedChildRef
. However this is a really bad idea unless you are just reading information (and handling errors such as the refs not existing correctly since the parent doesn't know if the child's ref is set correctly).
But luckily there is a very simple solution. You are right, you will want to create a child component which gets it's own data, rather than a mixin. But how you handle giving the selected item back to the parent is simply pass an onChange function from the parent like you would to an in-built <select>
in react.
Here's an example of this parent child relationship:
var MyParent = React.createClass({
getInitialState: function() {
return {
childSelectValue: undefined
}
},
changeHandler: function(e) {
this.setState({
childSelectValue: e.target.value
})
},
render: function() {
return (
<div>
<MySelect
url="http://foo.bar"
value={this.state.childSelectValue}
onChange={this.changeHandler}
/>
</div>
)
}
});
var MySelect = React.createClass({
propTypes: {
url: React.PropTypes.string.isRequired
},
getInitialState: function() {
return {
options: []
}
},
componentDidMount: function() {
// get your data
$.ajax({
url: this.props.url,
success: this.successHandler
})
},
successHandler: function(data) {
// assuming data is an array of {name: "foo", value: "bar"}
for (var i = 0; i < data.length; i++) {
var option = data[i];
this.state.options.push(
<option key={i} value={option.value}>{option.name}</option>
);
}
this.forceUpdate();
},
render: function() {
return this.transferPropsTo(
<select>{this.state.options}</select>
)
}
});
The above example shows a parent component MyParent
including a child component MySelect
which it passes a url to, along with any other props that are valid for a standard react <select>
element. Using react's built in this.transferPropsTo()
function to transfer all props over to the child's <select>
element in it's render function.
This means that the parent defining changeHandler
as the onChange
handler for MySelect
passes this function directly to the child's <select>
element. So when the select changes, the event gets handled by the parent instead of the child. And by the way this is a prefectly legal and legit way to do things in React - since it's still following the top down philosophy. If you think about it, when you use a normal built-in <select>
from react, this is exactly what's going on. onChange
is just a property for <select>
.
It's also worth noting that if you like, you can "hyjack" the change handler to add your own custom logic. For example what I tend to do is wrap my <input type="text" />
fields with a custom input component that takes any and all valid <input>
props, but also takes validator
function as a property which allows me to define a function that returns true/false based on the value inputted in to the <input>
field. I can do this by defining onChange
in the parent, for the wrapper child, but then in the child I define my own onChange
internally which checks for this.props.onChange
to be defined, and a function, then runs my validator and passes the event chain back up to the parent by calling this.props.onChange(e, valid)
. Giving the parent the very same event object in it's onChange
handler, but also with an added boolean valid
argument. Anyway, I digress....
Hope this helps you out :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With