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.
Related
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'
I'm starting with React and I tried to create a simple form that says Hello name!
However I feel having 2 state elements isn't the right way to do this.
Does anyone knows or believes there's a better way to do this?
By the way, I know I can just bind the name state to the h2, but I want it to happen on click.
var Name = React.createClass({
getInitialState:function(){
return {
inputname:'',
h2name:''
};
},
showName:function(event){
event.preventDefault();
this.setState({h2name:this.state.inputname});
},
updateName:function(event){
this.setState({inputname:event.target.value});
}
,
render:function(){
return (
<div>
<form onSubmit={this.showName}>
<input onChange={this.updateName} placeholder="Enter your name"></input>
<button type="submit">Show</button>
</form>
<h2>Hello {this.state.h2name}!</h2>
</div>
);
}
});
ReactDOM.render(<Name />,document.getElementById('mount-point'));
one state is enough.
var Name = React.createClass({
getInitialState: function () {
return {
inputname: ''
};
},
showName: function (event) {
event.preventDefault();
this.setState({ inputname: this.refs.inputname.value });
},
render: function () {
return (
<div>
<input ref="inputname" placeholder="Enter your name"></input>
<button onClick={this.showName}>Show</button>
<h2>Hello {this.state.inputname}!</h2>
</div>
);
}
});
ReactDOM.render(<Name />, document.getElementById('root'));
you can use refs to get the input value.
I think you want this effect, here is the demo
here is the document of refs more-about-refs
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
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>
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>
);
}
});