How to get a reference to target element inside a callback - javascript

I'm building a component which displays a series of generic input fields. The backing store uses a simple array of key-value pairs to manage the data:
[
{fieldkey: 'Signs and Symptoms', value:'fever, rash'},
{fieldkey: 'NotFeelingWell', value:'false'},
{fieldkey: 'ReAdmission', value:'true'},
{fieldkey: 'DateOfEvent', value:'12/31/1999'}
]
In order to eliminate a lot of boilerplate code related to data binding, the component uses these same keys when generating the HTML markup (see 'data-fieldname' attribute).
var Fields = React.createClass({
handleOnChange:function(e){
Actions.updateField( {key:e.target.attributes['data-fieldname'].value, value:e.target.value})
},
setValue:function(){
var ref = //get a reference to the DOM element that triggered this call
ref.value = this.props.form.fields[ref.attributes['data-fieldname']]
},
render:function(){
return (<div className="row">
<Input data-fieldname="Signs and Symptoms" type="text" label='Notes' defaultValue="Enter text" onChange={this.handleOnChange} value={this.setValue()} />
<Input data-fieldname="NotFeelingWell" type="checkbox" label="Not Feeling Well" onChange={this.handleOnChange} value={this.setValue()} />
<Input data-fieldname="ReAdmission" type="checkbox" label="Not Feeling Great" onChange={this.handleOnChange} value={this.setValue()} />
<Input data-fieldname="DateOfEvent" type="text" label="Date Of Event" onChange={this.handleOnChange} value={this.setValue()} />
</div>)
}
})
My goal is to use the same two functions for writing/reading from the store for all inputs and without code duplication (i.e. I don't want to add a refs declaration to each input that duplicates the key already stored in 'data-fieldname') Things work swimmingly on the callback attached to the 'onChange' event. However, I'm unsure how to get a reference to the DOM node in question in the setValue function.
Thanks in advance

I'm not sure if I understand your question right, but to reduce boilerplate I would map your array to generate input fields:
render:function(){
var inputs = [];
this.props.form.fields.map(function(elem){
inputs.push(<Input data-fieldname={elem.fieldkey} type="text" label="Date Of Event" onChange={this.handleOnChange} value={elem.value} />);
});
return (<div className="row">
{inputs}
</div>)
}
This will always display your data in props. So when handleOnChange gets triggered the component will rerender with the new value. In my opinion this way is better than accessing a DOM node directly.

If you want to use dynamic information on the input, you need to pass it through the array, and make a loop.
Here is a little example based on Dustin code:
var fieldArray = [ //replace by this.props.form.fields
{
fieldkey: 'Signs and Symptoms',
value: 'fever, rash',
type: 'text',
label: 'Notes'
},
{
fieldkey: 'NotFeelingWell',
value: 'false',
type: 'checkbox',
label: 'Not Feeling Well'
},
];
var Fields = React.createClass({
handleOnChange:function(e){
var fieldKey = e.target.attributes['data-fieldname'].value;
Actions.updateField({
key: fieldKey,
value: e.target.value
})
},
render() {
var inputs = [];
fieldArray.map(function(field) { //replace by this.props.form.fields
inputs.push(
<Input
data-fieldname={field.fieldkey}
value={field.value}
type={field.type}
label={field.label}
onChange={this.handleOnChange} />
);
}.bind(this));
return (
<div className="row">
{inputs}
</div>
);
}
});

Related

How to render conditional form in reactJS?

Conditionally, when having in imported array attribute called 'entry', I want to render form 'write', but 'write' is not displayed in the browser (no errors in console). Should I use child component for this, or maybe you have ideas for other approaches?
The code:
render() {
var replyList = questions.map(reply => {
return (
reply.feedback.map(singleReply => {
return (
<div>
<button
key={singleReply.id}
value={singleReply.button}
goto={singleReply.goto}
onClick={this.onButtonClick}>
{singleReply.button}
</button>
</div>
);
})
);
});
var write = (evt) => {
//argument dla input
var toWrite = questions[this.state.currentDialog].entry;
//jeśli jest entry'
if (questions[this.state.currentDialog].entry)
return (
<form onSubmit={this.onInputSubmit}>
<label value={toWrite.label} />
<input
name={toWrite.name}
value={toWrite.value}
onChange={this.onInputChange}
/>
<input type='submit' />
</form>
);
};
return (
//questions - pytanie, replyList - lista odpowiedzi
<div className="App">
{questions[this.state.currentDialog].question}
<br /><br />
{replyList[this.state.currentDialog]}
{this.write}
<br /><br />
</div>)
}
Piece of my array:
{
//[0]
id: uuid.v4(),
question: 'dialog1',
feedback: [
{ button: 'reply1-1', goto: 1, id: uuid.v4() },
{ button: 'reply1-2', goto: 2, id: uuid.v4() },
{ button: 'reply1-3', goto: 3, id: uuid.v4() },
{ button: 'reply1-4', goto: 4, id: uuid.v4() }
],
entry: { label: 'input1-1', name: 'input1', value: '1', id: uuid.v4() }
},
Inorder to display the write you need to call it as
return (
<div className="App">
{questions[this.state.currentDialog].question}
<br /><br />
{replyList[this.state.currentDialog]}
{write()}
<br /><br />
</div>)
this is not required since the write is defined inside the render method.You should also keep in mind the problem with putting functions inside render method.
A function in the render method will be created each render which is a
slight performance hit. It's also messy if you put them in the render,
which is a much bigger reason, you shouldn't have to scroll through
code in render to see the html output. Always put them on the class
instead.
write is part of the local scope for render, no need to call this.write. simply call write. More on this you have to call the function as well: write()
To add to this, not really part of the question but you will get an error. Every component has to return a 'component-like' value. If the condition is not fulfilled the write function will return undefined which will throw an error. Returning null will not throw an error as it's 'component-like'

Trying to wrap child elements on React

I'm doing a form helper to react-native, I also want to implement a feature called namespace to the forms to get a modular result.
An example:
return(
<Form
ref="configForm"
>
<Form.TextInput
name="firstName"
/>
<Form.TextInput
name="lastName"
/>
<Form.Namespace name="address">
<Form.TextInput
name="city"
/>
<Form.TextInput
name="address"
/>
<Form.TextInput
name="zip"
/>
</Form.Namespace>
</Form>
);
In this case the final result must be:
{
firstName: 'John',
lastName: 'Tompson',
address: {
city: 'New York',
address: '3th Av 231',
zip: '32132'
}
}
To get fields values I map recursivery the children of FormNamespace, and I clone the elements that own the property isFormValue (this property is owned by elements that provide values to Form, wich are FormNamespace and FormField):
wrapChildren(child) {
if (!child) {
return child;
}
if (Array.isArray(child)) {
return child.map((c) => this.wrapChildren(c));
}
if (child.type.isFormValue) {
let ref = child.ref || child.props.name;
child = React.cloneElement(child, {
ref: ref,
style: {borderWidth: 1, borderColor: 'red'}, // for debug
onUpdateValue: this.handleFieldChange.bind(this, ref),
});
}
if (child.props.children && !child.type.isFormStopableWrapper) {
child.props.children = this.wrapChildren(child.props.children);
}
return child;
}
render() {
let childs = this.wrapChildren(this.props.children);
debugger;
return(
<View style={this.props.style}>
{childs}
</View>
);
};
All logic is in FormNamespace component, in fact, the Form component only renders a FormNamespace:
render() {
return(
<Namespace
ref="formData"
name="mainNamespace"
style={[styles.form, this.props.style]}
onUpdateValue={(data) => this._handleUpdateData(data)}
>
{this.props.children}
</Namespace>
);
};
The Form component works correctly on the first depth, but when must to clone the FormNamespace element, by some issue it does not work as it should.
Debugging, I checked the React.cloneElement is applied to the FormNamespace but in the final render the FormNamespace never is referenced (nor the border style is applied), unlike the FormField that works well.
I don't know what is wrong here, the final FormNamespace rendered has not define the onUpdateValue prop. The objetive is the FormNamespace be taken like a simple value provider to Form (like fields components) and this manage theirs own childs.
The complete component code are here:
FormNamespace: https://paste.kde.org/pyz5gdsgb
Form: https://paste.kde.org/p4kmc2k9j

Update data in form. Stay editable. (React with Redux, sans redux-form)

Problem
I have a list of people. I want to:
Select a user to edit by clicking on their name.
Edit that user's information, so I can click the submit button and update the list.
If I click on a different name, I want to switch to that person's information without having to deliberately close the form first.
Everything works until #3. When I click on another person, the form, itself, does NOT update.
My Code
Update Component for the update form:
const UpdateForm = ({ updatePerson, personToUpdate, handleInputChange }) => {
let _name, _city, _age, _id;
const submit = (e) => {
e.preventDefault();
updatePerson({
name: _name.value,
city: _city.value,
age: _age.value,
_id: _id.value
});
};
return (
<div>
<form onSubmit={submit}>
<h3>Update Person</h3>
<label htmlFor="_id">Some Unique ID: </label>
<input type="text" name="_id" ref={input => _id = input} id="_id" defaultValue={personToUpdate._id} onChange={input => handleInputChange(personToUpdate)} required />
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" ref={input => _name = input} id="name" defaultValue={personToUpdate.name} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" ref={input => _city = input} id="city" defaultValue={personToUpdate.city} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" ref={input => _age = input} id="age" defaultValue={personToUpdate.age} onChange={input => handleInputChange(personToUpdate)} />
<br />
<input type="submit" value="Submit" />
</form>
</div>
);
};
export default UpdateForm;
Relevant parts of Person Component:
class Person extends Component {
nameClick() {
if (this.props.person._id !== this.props.personToUpdate._id) {
this.props.setForUpdate(this.props.person);
this.forceUpdate();
}
else {
this.props.toggleUpdatePersonPanel();
}
}
render() {
return (
<div>
<span onClick={this.nameClick}>
{this.props.person.name} ({this.props.person.age})
</span>
</div>
);
}
}
export default Person;
Relevant parts of PeopleList, which holds Persons:
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person) => {
return <Person
key={person._id}
person={person}
updatePersonPanel={this.props.updatePersonPanel}
setForUpdate={this.props.setForUpdate}
personToUpdate={this.props.personToUpdate}
/>;
})}
</div>
);
}
} // end class
export default PeopleList;
Form Reducer, with just the relevant actions:
export default function formReducer(state = initialState.form, action) {
let filteredPeople;
switch (action.type) {
case TOGGLE_UPDATE_PANEL:
return Object.assign({}, state, { updatePersonPanel: false }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}});
case SET_FOR_UPDATE:
return Object.assign({}, state, { personToUpdate: action.person }, { updatePersonPanel: true });
case UPDATE_RECORD:
filteredPeople = state.people.filter((person) => {
return person._id === action.person._id ? false : true;
}); // end filter
return Object.assign({}, state, { people: [ ...filteredPeople, action.person] }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}}, { updatePersonPanel: false });
case HANDLE_INPUT_CHANGE:
return Object.assign({}, state, { personToUpdate: action.person });
default:
return state;
}
}
The relevant parts of my Initial State file:
form: {
people: [
{
_id: "adfpnu64",
name: "Johnny",
city: "Bobville",
age: 22
},
{
_id: "adf2pnu6",
name: "Renee",
city: "Juro",
age: 21
},
{
_id: "ad3fpnu",
name: "Lipstasch",
city: "Bobville",
age: 45
}
],
updatePersonPanel: false,
personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
},
}
Attempts at a Solution( so far)
I have attempted to make the component a completely controlled component, by switching the form attribute to value instead of defaultValue. When I do this, the names switch just fine, but the form becomes unchangeable and useless.
My Questions
Almost all of the solutions to these kind of issues either recommend using redux-form or supply two-way binding solutions that work fine in React without reduce. I want to know how to do this with Redux without using redux-form or anything extra if possible. Is there a way to resolve this without touching lifecycle methods?
Conclusion (For now)
Well, for now, I settled for making my form uncontrolled and used some classic Js DOM methods and a lifecycle method to control the form. For whatever reason, once I employed some of the answer suggestions my browser ate up my CPU and crashed, presumably because there was some kind of infinite loop. If anyone has some further recommendations, I'd really appreciate it. For now I settle for this:
class UpdateForm extends Component {
constructor(props){
super(props);
this.submit = this.submit.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.personToUpdate._id !== this.props.personToUpdate._id) {
document.getElementById("name").value = nextProps.personToUpdate.name;
document.getElementById("age").value = nextProps.personToUpdate.age;
document.getElementById("city").value = nextProps.personToUpdate.city;
}
}
submit(e) {
e.preventDefault();
this.props.updatePerson({
name: document.getElementById("name").value,
city: document.getElementById("city").value,
age: document.getElementById("age").value,
_id: this.props.personToUpdate._id
});
}
render() {
return (
<div>
<form onSubmit={this.submit}>
<h3>Update Person</h3>
Unique ID: {this.props.personToUpdate._id}
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" id="name" ref="name" defaultValue={this.props.personToUpdate.name} required />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" id="city" ref="city" defaultValue={this.props.personToUpdate.city} required />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" id="age" ref="age" defaultValue={this.props.personToUpdate.age} required />
<br />
<input type="submit" value="Update" />
</form>
</div>
);
}
} // end class
export default UpdateForm;
I'll be soon exploring redux-form because it is evident that forms as inputs and outputs are a wonky business. For now, my little app works.
Yes there is and you are on the right path. The way is to use value instead of defaultValue but you have to read the value from a state and then use the onChange handler to modify the state.
Something like
this.state = {inputText:''}
Then in the input field
<input value={this.state.inputText} onChange={this.handleChange}/>
And the handleChange function will be
handleChange(event){
this.setState({inputText:event.target.value})
}
Remember to bind the handleChange event in the constructor so you can pass it as this.handleChange in the input field's onChange prop.
Something like this
this.handleChange = this.handleChange.bind(this)
https://facebook.github.io/react/docs/forms.html - Here are the official docs regarding it
Also if you want to do it in redux the same sort of logic applies where this will be the input field
<input value={this.props.inputText} onChange={this.props.handleChange}/>
where inputText and handleChange are redux state and action respectively passed to the component via props
For your case I guess it has to be something like where you are 'reading' values from the people array and the action bound to the onChange modifies that value in the people array in the state.
<--EDIT-->
How it can be done for the case in point. Pass the people in the redux state as a people prop to the component. Pass an action changePeople(newPeople) to the component as a prop which takes an argument newPeople and changes the people in the redux state to have the value newPeople. Since people is nested in form you'll have to do some Object.assign etc to modify the state.
Now in the component using the people props populate the checkboxes using a map function. The map function takes a second parameter index so for each checkbox have a function which sets the local state variable currentPerson to the value of the index
this.props.people.map((person,index) =>
return <Checkbox onClick={()=>this.setState(currentPerson:index)}/>
)
So everytime you click on a checkbox the currentPerson points to the corresponding index.
Now the input fields can be
<input value={this.props.people[this.state.currentPerson].name} onChange={this.handleChange.bind(this,'name')}/>
This is for the 'name' input field. It reads from the currentPerson index of the people array which has been passed down as a prop.
This is how the handleChange will be like
handleChange(property,event){
const newPeople = [
...this.props.people.slice(0, this.state.currentPerson),
Object.assign({}, this.props.people[this.state.currentPerson], {
[property]: event.target.value
}),
...this.props.people.slice(this.state.currentPerson + 1)
]
this.props.changePeople(newPeople)
}
The handleChange takes a property (so you don't have to write separate handlers for each input field). The newPeople basically modifies the element at current index this.state.currentPerson in the people passed from props (ES6 syntax being used here. If you have doubts do ask). Then it is dispatched using the changePeople action which was also passed as props.

Meteor + React how to set the value of a lot of input elements and modify them after

I'm new to meteor and react. Let's say I have this in my react component:
getMeteorData() {
var myForm = ProjectForm.findOne({_id:this.props.router.params._id});
var projects = ProjectForm.find({});
return {myForm:myForm,projects:projects};
// doing a console.log(myForm); would give you something like
/*
input1:my text 1
input2:some other text
input3:something else
etc....
*/
},
renderListProjects() {
return this.data.projects.map(function(projectform,i) {
return <li key={"li"+i}><a href={Meteor.absoluteUrl()+'project/' + projectform.username +'/' +projectform._id} key={"a"+i}>Project {projectform._id}</a></li>;
});
},
getInitialState() {
return Projects.findOne({this.props.router.params._id}, {sort: {createdAt: -1}});
},
render() {
return (
<div>
<ul>{this.renderListProjects()}</ul>
<form>
<span>Hello this is some text</span>
<input type="text" ref="input1" />
<p>Blah blah this is boring</p>
<input type="text" ref="input2" />
<img src="image-of-a-kangaroo.png" />
<input type="text" ref="input3" />
<ul>
<li>Buy brocolli</li>
<li>Buy oregano</li>
<li>Buy milk</li>
</ul>
<input type="text" ref="input4" />
...
<textarea ref="input100"></textarea>
<input type="text" ref="input101" />
<p><strong>Yes, I like pizza!</strong> But my porcupine gets sick eating pizza.</p>
...
</form>
</div>
);
What I want to do is assign the values of this.data.myForm to each of the form fields in the render() function. But when I do something like <input type="text" ref="input1" value={this.data.myForm.input1} />, and I go to my web browser and put my cursor on that field, I am NOT able to modify the value. Pressing the keys on my keyboard will not change the value of that input field. Additionally, I have about 250 input fields in this html form. I really don't want to do any data entry. I would much rather just use some kind of loop to iterate through the this.data.myForm and assign it to the corresponding form fields. But when I tried to do this, I get problems about the DOM not being found or it's not loaded. So I tried writing some code in componentDidMount(), but then I got other errors.
Can anyone point me in the right direction on how to efficiently bind all my this.data.myForm data to my form fields AND allow me to edit the form fields after?
Additional Requirements
If someone clicks on link1 from the renderListProjects then clicks on link2 from the renderlistProjects, then the form must show the value of link2's projectform._id.
In the DOM, an href="project/_id" attribute must exist for SEO and for WCAG compliance.
Attempts
I tried to redefine renderListProjects as
renderListProjects() {
var pj = this
return this.data.projects.map(function(projectform,i) {
return <li key={"li"+i}><a onClick={pj.click(projectform._id)} href={Meteor.absoluteUrl()+'project/' + projectform.username +'/' +projectform._id} key={"a"+i}>Project {projectform._id}</a></li>;
});
},
click(id) {
var currentApp = ProjectForm.findOne({_id:id}, {sort: {createdAt: -1}});
this.setState({input36:input36});
},
But when I run my meteor+react project, my browser crashes because some kind of infinite loop is happening.
What you created is what React calls an controlled input. This means that React has declared your value in the inputs to correspond to whatever this.data.myForm.input1 points to.
What you need to do in order to be able to change an input field is to add an onChange handler that takes care of updating the value. The documentation is quite straight forward but I added a small example below to illustrate it.
class Form extends React.Component {
constructor() {
super();
this.state = {
myForm: {
inputs: [
{value: 1}, {value: 2}, {value: 3}
]
}
};
}
onChange = (index, event) => {
let inputs = this.state.myForm.inputs.slice(0);
inputs[index] = {
value: parseInt(event.target.value, 10)
};
this.setState({
myForm: {
inputs: inputs
}
})
}
render() {
return (<form>
{this.state.myForm.inputs.map((input, index) => {
return (<input onChange={this.onChange.bind(null, index)} value={input.value} key={index} />);
}, this)}
</form>);
}
};
ReactDOM.render(<Form />, document.querySelector('#c'));
This code solved the problem
getMeteorData() {
var myForm = ProjectForm.findOne({_id:this.props.router.params._id});
var projects = ProjectForm.find({});
return {myForm:myForm,projects:projects};
// doing a console.log(myForm); would give you something like
/*
input1:my text 1
input2:some other text
input3:something else
etc....
*/
},
clickLoadForm(appId)
{
var currentApp = Projects.findOne({appId}, {sort: {createdAt: -1}});
var state = new Object();
var refs = this.refs;
Object.keys(refs).map(function(prop,index){
state[prop] = typeof currentApp[prop] == 'undefined' ? "" : currentApp[prop];
});
this.setState(state);
},
handleChange: function(e) {
if(!e.target.id) return;
if(typeof e.target.id == 'undefined') return;
var state = new Object();
state[e.target.id] = e.target.value;
this.setState(state);
},
renderListProjects() {
return this.data.projects.map(function(projectform,i) {
return <li key={"li"+i}><a onClick={_this.clickLoadForm.bind(_this,projectform._id)} href={Meteor.absoluteUrl()+'project/' +projectform._id} key={"a"+i}>Project {projectform._id}</a></li>;
});
},
getInitialState() {
return Projects.findOne({this.props.router.params._id}, {sort: {createdAt: -1}});
},
render() {
return (
<div>
<ul>{this.renderListProjects()}</ul>
<form>
<span>Hello this is some text</span>
<input type="text" ref="input1" id="input1" onChange={this.handleChange} />
<p>Blah blah this is boring</p>
<input type="text" ref="input2" id="input2" onChange={this.handleChange} />
<img src="image-of-a-kangaroo.png" />
<input type="text" ref="input3" id="input3" onChange={this.handleChange} />
<ul>
<li>Buy brocolli</li>
<li>Buy oregano</li>
<li>Buy milk</li>
</ul>
<input type="text" ref="input4" id="input4" onChange={this.handleChange} />
...
<textarea ref="input100" id="input100" onChange={this.handleChange}/>
<input type="text" ref="input101" id="input101" onChange={this.handleChange} />
<p><strong>Yes, I like pizza!</strong> But my porcupine gets sick eating pizza.</p>
...
</form>
</div>
);
To summarize, my changes were:
created a renderListProjects() function
created the clickLoadForm() function and used it in the renderListProjects()
created the handleChange() function and made sure it's references via onChange for each element in render()
made sure every element in render() has an id that's the same as the ref

How to render checked radio button in React

I came of with a solution for pre checked radio button based on JSON data but I think the code can be improved. Or there should be a better way to implement it. The data is structure in following way:
{
radioA: 'Radio A',
radioB: 'Radio B',
radioC: 'Radio C',
selected: 'Radio A'
}
Not sure if this is the best way to do it. The data structure can be modified if needed.
Based on the data provided I am rendering radio button with one of them pre-selected. Following is what I have.
var App = React.createClass({
getInitialState: function(){
return {
radioA: 'Radio A',
radioB: 'Radio B',
radioC: 'Radio C',
selected: 'Radio A'
}
},
render: function(){
return(
<div>
{Object.keys(this.state).map(function(key) {
// Not displaying the selected data
if(key !== 'selected'){
// Checked radio button
if(this.state[key] == this.state['selected']){
return(
<div>
<label className="radio-inline" key={key} htmlFor={key}>
<input id={key} type="radio" checked value={key} /> {this.state[key]}
</label>
</div>
);
}
else{
return(
<div>
<label className="radio-inline" key={key} htmlFor={key}>
<input id={key} type="radio" value={key} /> {this.state[key]}
</label>
</div>
);
}
}
}, this)}
</div>
);
}
});
React.render(<App />, document.getElementById('container'));
The code is set up following jsfiddle.com
https://jsfiddle.net/rexonms/2x7ey2L5/
Is there a better solution to implement this? Thanks in advance
There is indeed a better way. In your render method, the JSX inside the two branches of your if statement are identical, except that one has checked and the other doesn't. Repetition like that can—and should—almost always be cleaned up.
Note: This answer was written with React 0.14 in mind. To see the code as written with more modern React idioms, see this revision.
In the case of boolean attributes like checked in JSX, you can give them a truthy or falsy value to control whether or not the rendered HTML element has the attribute or not. In other words, if you have <input checked={10 > 1}/>, you'll get <input checked/>. If you have <input checked={10 > 100}/>, you'll get <input/>.
With that in mind, we can reduce your code to something like this:
var App = React.createClass({
getInitialState: function() {
return {
radios: {
radioA: 'Radio A',
radioB: 'Radio B',
radioC: 'Radio C'
},
selected: 'radioA'
}
},
renderRadioWithLabel: function(key) {
var isChecked = key === this.state.selected;
return (
<label className="radio-inline" key={key} htmlFor={key}>
<input id={key} type="radio" checked={isChecked} value={key} />
{this.state.radios[key]}
</label>
);
},
render: function() {
return (
<div>
{Object.keys(this.state.radios).map(function(key) {
return this.renderRadioWithLabel(key);
}, this)}
</div>
);
}
});
React.render(<App />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container"></div>
As you can see, I've broken the logic that renders the radio buttons and their labels into its own function. You don't have to do that, but I find that it makes render functions with inner loops a lot easier to read, and makes your intent clearer.
Some people, myself included, would advocate breaking that logic out into its own component so you can have a stateful container component and stateless child components, which makes the whole thing much, much easier to test. Here's a good article on the topic. One more thing you might want to do is make the list of keys and labels in an array, since the order of an object's properties is not guaranteed in JavaScript. If you want to know what it might look like with those changes, take a look at this snippet:
var Radio = React.createClass({
render: function() {
var key = this.props.key;
return (
<label className="radio-inline" key={key} htmlFor={key}>
<input id={key} type="radio" checked={this.props.selected} value={key} />
{this.props.label}
</label>
);
}
});
var RadiosContainer = React.createClass({
getInitialState: function() {
return { selected: this.props.initialSelected };
},
render: function() {
return (
<div>
{this.props.radios.map(function(radioProps) {
var selected = this.state.selected === radioProps.key;
return <Radio {...radioProps} selected={selected} />;
}, this)}
</div>
);
}
});
var radios = [
{ key: 'radioA', label: 'Radio A' },
{ key: 'radioB', label: 'Radio B' },
{ key: 'radioC', label: 'Radio C' }
];
var selectedRadio = 'radioB';
React.render(
<RadiosContainer radios={radios} initialSelected={selectedRadio} />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container"></div>

Categories

Resources