React state nested attributes - javascript

I'm playing around with a Fluxxor tutorial (example at the very top) and there's a simple todo list built with React. It's very basic and I wanted to add a simple validation to get better understand of data binding.
var Application = React.createClass({
mixins: [FluxMixin, StoreWatchMixin("TodoStore")],
getInitialState: function() {
return { newTodoText: "" };
},
getStateFromFlux: function() {
var flux = this.getFlux();
return flux.store("TodoStore").getState();
},
render: function() {
var todos = this.state.todos;
return (
<div>
<ul>
{Object.keys(todos).map(function(id) {
return <li key={id}><TodoItem todo={todos[id]} /></li>;
})}
</ul>
<form onSubmit={this.onSubmitForm}>
<input type="text" size="30" placeholder="New Todo"
value={this.state.newTodoText}
onChange={this.handleTodoTextChange} />
<input type="submit" value="Add Todo" />
</form>
<button onClick={this.clearCompletedTodos}>Clear Completed</button>
</div>
);
},
So if I want to disable submit if the input field is empty, I can compare the value of input with the initial state:
<input type="submit" value="Add Todo" disabled={!#state.newTodoText}/>
Now I want to disable Clear Completed until at least one TodoItem is marked as completed. When this happens this.state.todos is updated with a with a completed key set to true and it's available in my application component:
Object {todo: Object}
todo: Object
complete: true
id: 1
text: "text"
How I should handle this logic? Thanks in advance.

You can write a function which returns the number of completed todos in your object.
countCompleted: function(todos) {
var todoKeys = Object.keys(todos);
var completed = todoKeys.filter(function(key) {
var todo = todos[key];
return todo.complete;
});
return completed.length;
}
Then call that function from render to conditionally disable the button.
<button
onClick={this.clearCompletedTodos}
disabled={this.countCompleted(todos) > 0}>
Clear Completed
</button>

Related

Reactjs props not showing up. How can I improve my code?

// initial state and other properties
var ReviewControl = React.createClass({
getInitialState: function() {
return {
name:'',
feedback:'',
movie:'',
reviews:[]
};
},
onChangeName:function(el){
this.setState({name:el.target.value})
},
onChangeFeedback:function(el) {
this.setState({feedback:el.target.value})
},
onChangeMovie:function(el){
this.setState({agent:el.target.value})
},
submitReview:function(el) {
//preventing default for the form
el.preventDefault();
//storing data into the reviews array created earlier
this.state.reviews.push(
{name:this.state.name, feedback:this.state.feedback, movie:this.state.movie});
this.setState({name:'', feedback:''});
},
render: function() {
var options = this.props.list.map(function(agent){
return (<option key={agent.id} value={agent.agentName}>{agent.agentName}</option>);
});
//rendering the form for feedback
return (
<div>
//creating form
<form onSubmit = {this.submitReview}>
<lable> Agent Name : </lable>
<input type="text" placeholder='Enter The Agent Name' value={this.state.name} onChange={this.onChangeName} />
<br/><br/>
<lable> Feedback : </lable>
<input type="text" placeholder='Enter Your Feedback' value={this.state.feedback} onChange={this.onChangeFeedback} />
<br/><br/>
<select onChange={this.onChangeMovie}>
{options}
</select>
<br/><br/>
<input type="submit" value="Submit"/>
</form>
//collecting the reviews
<ReviewCollection reviews = {this.state.reviews} />
</div>
)
}
});
// collecting all the reviews from the user and storing it
var ReviewCollection = React.createClass ({
render:function () {
var reviews = this.props.reviews.map(function(review) {
return <Review key={review.id} movie = {review.movie} name = {review.name} feedback = {review.feedback} />
});
return (<div> {reviews} </div>)
}
});
//showing the stored values from the array review
var Review = React.createClass({
render:function(){
return <div>
<span> Agent Name </span>
{this.props.name}
<br/>
<span> Movie Name </span>
{this.props.movie}
<br/>
<span> Feedback </span>
{this.props.feedback}
<br/>
</div>
}
});
// rendering the form
var finalReview = <ReviewControl list={agentList} />;
ReactDOM.render(finalReview, document.getElementById('myid'));
You aren't setting the new review state in submitReview. Just pushing into the old array isn't actually updating state. You have to explicitly update it.
this.state.reviews.push({
name: this.state.name,
feedback: this.state.feedback,
movie: this.state.movie
});
this.setState({
name: '',
feedback: '',
reviews: this.state.reviews
});
Another small thing I noticed is that you're using el as the argument for events. Typically it's just e or event. el is usually short for element, usually a dom node.

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 print out an array of user inputs in ReactJs

Currently my code will print out what ever the user types into the input box after they have submitted it. If I type 'Dave', that gets printed out. If I then type 'Paul'. Paul replaces where Dave has been outputted. I want a way where if I type out another name instead of replacing it, it will print it out underneath, unlimited times. I am thinking of using an array instead of the current string but not sure how to do this. here is my code so far:
var ToDoForm = React.createClass({
getInitialState: function() {
return {text: '', submittedValue: ''};
},
handleChange: function(event) {
this.setState({text: event.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
this.setState({submittedValue: this.state.text});
console.log("ToDO: " + this.state.text);
},
render: function() {
return (
<div>
<h1> todos </h1>
<form className="todoForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Type out a task"
value={this.state.text}
onChange={this.handleChange}
/>
<input
type="submit"
value="Submit todo"
/>
</form>
<h2> Data should appear here </h2>
<p>{this.state.submittedValue}</p>
</div>
);
}
});
So far text gets assigned to submittedValue and then submitted value is what gets printed out.
I have started with this
getInitialState: function() {
return {text: '', submittedValue: []};
},
but now I am stuck as to what to do next
Once sumittedValue is an array in state. You should be able just to push to it:
handleSubmit: function(e) {
e.preventDefault();
this.state.subittedValue.push(this.state.text);
this.setState({submittedValue: this.state.subittedValue});
console.log("ToDO: ", this.state.submittedValue);
},
Of course then you have to loop through the array (map) in order to render:
<h2> Data should appear here </h2>
{this.state.submittedValue.map(
function(value, i) {
return (<p key={'val-' + i}>{value}</p>);
}
)}
key is required to uniquely identify the looped <p>. Otherwise, a warning would show on render.

Create rich form components to expose value to parent component

After having worked with React.js a couple of days, I have written most of my forms like the following (as exemplified in the official tutorial):
React.createClass({
handleSubmit: function (event) {
event.preventDefault();
var value = React.findDOMNode(this.refs.text).value;
// do something with the value
},
render: function () {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref="text" defaultValue="foo" />
<input type="submit" value="Save"/>
</form>
);
}
}
However, I have recenctly discovered a weakness of this approach where I am not capable to write rich form components to be used in forms that are built up by such components such as:
var RichInput = React.createClass({
render: function() {
return (
<div className="someStyle">
<input type="text" ref="text" defaultValue="foo" />
</div>
);
}
}
React.createClass({
handleSubmit: function (event) {
event.preventDefault();
var value = React.findDOMNode(this.refs.text).value;
// do something with the value
},
render: function () {
return (
<form onSubmit={this.handleSubmit}>
<RichInput />
<input type="submit" value="Save"/>
</form>
);
}
}
Now I am wondering. After looking through available resources, I found the following approach to be used to overcome this limitation:
var RichInput = React.createClass({
render: function() {
return (
<div className="someStyle">
<input type="text" value="foo" onChange={this.props.callback} />
</div>
);
}
}
React.createClass({
handleSubmit: function (event) {
event.preventDefault();
var value = this.state.text
// do something with the value
},
getInitialState() {
return {text: 'foo'};
}
updateText: function(value) {
this.setState({text: value});
}
render: function () {
return (
<form onSubmit={this.handleSubmit}>
<RichInput callback={this.updateText} />
<input type="submit" value="Save"/>
</form>
);
}
}
Is this the canonical solution to writing modularized form components? I am wondering as this does seem like a lot of overhead to me. I need to write extra functions and I need to make the component state-full what drives me a bit away from adapting this solution. Also, I wonder about performance as I really do not need to update the value on every change but only on (and in case of) form submission.
One possibility I found was to use:
React.findDOMNode(this.refs.rich.refs.text);
given that the RichInput has ref="rich" defined on it. But then again, the documentation of React says that refs should not be considered puplic API and be accessed outside of a component.
This is the way I built my abstract Input component. I use it for various purposes (whenever I require the user to input something and I want to handle the action later) (example with ES6/7 with some bootstrap styling):
import React, { PropTypes, Component } from 'react';
export default class Input extends Component {
static propTypes = {
placeholder: PropTypes.string,
buttonText: PropTypes.string,
onButtonClick: PropTypes.func
}
constructor() {
super();
this._handleClick = this._handleClick.bind(this);
this._handleKeyUp = this._handleKeyUp.bind(this);
}
render() {
return (
<div className='Input'>
<div className='input-group'>
<input type='text' className='form-control' placeholder={this.props.placeholder}
ref='inputBox' onKeyUp={this._handleKeyUp}
/>
<span className='input-group-btn'>
<form onSubmit={this._handleClick}>
<button className='btn btn-success' type='submit'>{this.props.buttonText}</button>
</form>
</span>
</div>
</div>
);
}
_handleClick(e) {
e.preventDefault();
let value = this.refs.inputBox.getDOMNode().value;
this.props.onButtonClick(value);
value = null;
}
_handleKeyUp(e) {
e.preventDefault();
if (e.keyCode === 13) {
this._handleClick(e);
}
}
}
And then in the Parent component you can initialize it like:
<Input placeholder='Enter something'
buttonText='Submit'
onButtonClick={this._handleButtonClick}
/>
and handle the _handleButtonClick:
_handleMakeSearch(text) {
console.log(text);
}
It is a commonly solution to create very small components and one wrapper (parent component) that handles all states of his subcomponents, because the states of these subcomponents often depends on the state of other subcomponents.
So you are right, the wrapper/parent component will have a (lot of) overhead, but this way your "real" components are more modular.
var RichInput = React.createClass({
render() {
return (
<div className="someStyle">
<input type="text" value={this.props.value} onChange={this.props.onChange} />
</div>
);
}
}
React.createClass({
handleSubmit: function (event) {
event.preventDefault();
var value = this.state.value;
// do something with the value
},
getInitialState() {
return {value: 'foo'};
}
updateValue(value) {
this.setState({value});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<RichInput value={this.state.value} onChange={this.updateValue} />
<input type="submit" value="Save"/>
</form>
);
}
}
Here is an other question/answer, where you can see an example/use case. A parent component that handles all states of his subcomponents, where each state depends on each other. Maybe it helps to understand the benefits of this approach.

How to get a reference to target element inside a callback

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>
);
}
});

Categories

Resources