I have created the form validation with a structure like this
var Signin = React.createClass({
render: function() {
return (
<Form>
<Input type="text" name="email" labelName="Email" rules="isEmail" error:"Email not valid" />
<Input type="password" name="password" labelName="Password" rules="isLength:6" error:"Passowrd not valid"/>
</Form>
);
}
});
because, for example, the "Email" input will be used in different part of application, I would avoid to add the same attributes (name, type, labelName, rules and error) every time. So I would create something like this
var InputEmail = React.createClass({
render: function () {
return (
<Input type="text" name="email" labelName="Email" rules="isEmail" error="Email not valid"/>
)
}
});
var InputPassword = React.createClass({
render: function () {
return (
<Input type="password" name="password" labelName="Password" rules="isLength:6" error="Passwordnot valid"/>
)
}
});
So the Signin Component should be
var Signin = React.createClass({
render: function() {
return (
<Form>
<InputEmail />
<InputPassword />
</Form>
);
}
});
but in this way, I get two errors:
I can't find anymore in the Form the props.name of Input because
there isn't in InputEmail;
in the render function of Input the state is null
How could I create a reausable/inherits components? I failed using both the composition pattern and the mixins
I added my full code: Form
var Form = React.createClass({
getInitialState: function () {
return {
isValid : false,
isSubmitting: false
}
},
componentWillMount: function(){
this.model = {};
this.inputs = {};
this.registerInputs(this.props.children);
},
registerInputs: function(children){
React.Children.forEach(children, function (child) {
if (child.props.name) {
child.props.attachToForm = this.attachToForm;
child.props.detachFromForm = this.detachFromForm;
child.props.validate = this.validate;
}
if (child.props.children) {
this.registerInputs(child.props.children);
}
}.bind(this));
},
attachToForm: function (component) {
this.inputs[component.props.name] = component;
this.model[component.props.name] = component.state.value;
this.validate(component);
},
detachFromForm: function (component) {
delete this.inputs[component.props.name];
delete this.model[component.props.name];
},
validate: function (component) {
var isValid = true;
// validation code
component.setState({
isValid: isValid,
}, this.validateForm);
},
validateForm: function () {
var formIsValid = true;
var inputs = this.inputs;
Object.keys(inputs).forEach(function (name) {
if (!inputs[name].state.isValid) {
formIsValid = false;
}
});
this.setState({
isValid: formIsValid
});
},
updateModel: function (component) {
Object.keys(this.inputs).forEach(function (name) {
this.model[name] = this.inputs[name].state.value;
}.bind(this));
},
submit: function (event) {
event.preventDefault();
this.setState({
isSubmitting : true
});
this.updateModel();
console.log(this.model);
},
render: function () {
return (
<form className="ui form" onSubmit={this.submit}>
{this.props.children}
<button className="ui button" type="submit" disabled={this.state.isSubmitting}>Accedi</button>
</form>
);
}
});
Input
var Input = React.createClass({
getInitialState: function(){
return {
value : this.props.value || "",
isValid: true
}
},
setValue: function (event) {
this.setState({
value: event.target.value
}, function () {
this.props.validate(this);
}.bind(this));
},
componentWillMount: function () {
if (this.props.required) {
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
this.props.validations += 'isLength:1';
}
// ERROR: TypeError: this.props.attachToForm is not a function
this.props.attachToForm(this);
},
componentWillUnmount: function () {
this.props.detachFromForm(this);
},
render: function () {
var className = "field";
if(this.props.className){
className += " " + this.props.className;
}
if(this.props.required){
className += " required";
}
var Label;
if(this.props.labelName){
Label = (<label htmlFor={this.props.name}>{this.props.labelName}</label>);
}
var Error;
if(!this.state.isValid){
Error = (<div className="ui">{this.props.error || this.props.name + " not valid"}</div>);
};
return (
<div className={className}>
{Label}
<input type={this.props.type || "text"} id={this.props.name} name={this.props.name} onChange={this.setValue} value={this.state.value} />
{Error}
</div>
);
}
});
With this works
ReactDOM.render(
<Form>
<Input type="text" name="email" labelName="Email" rules="isEmail" error:"Email not valid"/>
</Form>,
document.getElementById('app')
);
In this way I get:
"TypeError: this.props.attachToForm is not a function
this.props.attachToForm(this);"
ReactDOM.render(
<Form>
<InputEmail/>
</Form>,
document.getElementById('app')
);
P.S: I tried to add this code on jsfiddle but I get "!TypeError: can't define property "attachToForm": Object is not extensible"
jsfiddle
There are 2 main issues with your setup:
Your <Form> is set up in such a way, that the children of the form need to have props, otherwise it does not work.
The <InputEmail> wrapper is incomplete. It needs to pass along all props to the <Input>, including the Form functions passed down.
Ad 1: Fix the form, to ensure validation methods are added
The reason you get the error is because the children of your <Form> need to have props.name. It then registers the functions of the form (including attachToForm), by adding them to the children. This is done in the method registerInputs().
In the original variant, the <Input> component has props, so all goes well.
In the adapted variant, the wrapper <InputEmail> no longer has props, so the attachToForm() and other functions are not added to props, and you get the error when the <Input> tries to invoke the function.
Simplest way to fix this: add at least 1 prop in the render function, and check this in the registerInputs(), e.g.:
ReactDOM.render(
<Form>
<InputEmail initialValue={'name#domain.com'}/>
</Form>,
document.getElementById('app')
);
And in registerInputs(), change the line:
if (child.props.name) {
to:
if (child.props.initialValue) {
2. Extend <InputEmail> wrapper to pass down functions as well
Simplest way to do this is to add {...this.props}, like this:
var InputEmail = React.createClass({
render: function () {
return (
<Input {...this.props}
type="text" name="email" labelName="Email" rules="isEmail" error="Email not valid"/>
)
}
});
That way, the functions passed down by <Form> to the <InputEmail> component (as well as any other props), will be passed down to the <Input> component.
PS: The code inside registerInputs() that checks for child.props.children does not work as intended: at the time it is invoked, the <InputEmail> component does not have children. Like the name implies, it checks for children passed down as props. And the only prop passed was initialValue.
As minor issues I would suggest to make 2 more changes:
In registerInputs(), you directly modify props. This is generally not a good idea. Better is to make a copy of props, and add your form-methods to the copy. You can use React.Children.map to do this. See official docs here.
Instead of hard-coding the name="email" etc of your <Input> component, inside <InputEmail>, better is to put the default values of these in default values of props, using propTypes, as explained here in official docs.
Related
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 just started using React.js, and I'm just not sure whether there is a special way to get the value of a textbox, returned in a component like this:
var LoginUsername = React.createClass({
render: function () {
return (
<input type="text" autofocus="autofocus" onChange={this.handleChange} />
)
},
handleChange: function (evt) {
this.setState({ value: evt.target.value.substr(0, 100) });
}
});
As described in documentation You need to use controlled input. To make an input - controlled you need to specify two props on it
onChange - function that would set component state to an input value every time input is changed
value - input value from the component state (this.state.value in example)
Example:
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
More specifically about textarea - here
just update your input to the value
var LoginUsername = React.createClass({
getInitialState:function(){
return {
textVal:''
}
},
render: function () {
return (
<input type="text" value={this.state.textVal} autofocus="autofocus" onChange={this.handleChange} />
)
},
handleChange: function (evt) {
this.setState({ textVal: evt.target.value.substr(0, 100) });
}
});
Your text input value is always in the state and you can get the same by this.state.textVal
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.
I've just started learning React and have a question.
I want to do the following:
If a user clicks on a paragraph I want to change the element to an input field that has the contents of the paragraph prefilled.
(The end goal is direct editing if the user has certain privileges)
I'm come this far but am totally at a loss.
var AppHeader = React.createClass({
editSlogan : function(){
return (
<input type="text" value={this.props.slogan} onChange={this.saveEdit}/>
)
},
saveEdit : function(){
// ajax to server
},
render: function(){
return (
<header>
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<h1>{this.props.name}</h1>
<p onClick={this.editSlogan}>{this.props.slogan}</p>
</div>
</div>
</div>
</header>
);
}
});
How can I override the render from the editSlogan function?
If I understand your questions correctly, you want to render a different element in case of an "onClick" event.
This is a great use case for react states.
Take the following example
React.createClass({
getInitialState : function() {
return { showMe : false };
},
onClick : function() {
this.setState({ showMe : true} );
},
render : function() {
if(this.state.showMe) {
return (<div> one div </div>);
} else {
return (<a onClick={this.onClick}> press me </a>);
}
}
})
This will change the components state, and makes React render the div instead of the a-tag. When a components state is altered(using the setState method), React calculates if it needs to rerender itself, and in that case, which parts of the component it needs to rerender.
More about states
https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html
You can solve it a little bit more clear way:
class EditableLabel extends React.Component {
constructor(props) {
super(props);
this.state = {
text: props.value,
editing: false
};
this.initEditor();
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
initEditor() {
this.editor = <input type="text" defaultValue={this.state.text} onKeyPress={(event) => {
const key = event.which || event.keyCode;
if (key === 13) { //enter key
this.save(event.target.value)
}
}} autoFocus={true}/>;
}
edit() {
this.setState({
text: this.state.text,
editing: true
})
};
save(value) {
this.setState({
text: value,
editing: false
})
};
componentDidUpdate() {
this.initEditor();
}
render() {
return this.state.editing ?
this.editor
: <p onClick={this.edit}>{this.state.text}</p>
}
}
//and use it like <EditableLabel value={"any external value"}/>;
I am not so familiar with react, I try to build the following abstraction over sign in / forms etc.
Take a look at this:
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log(this.refs.iitu_id.getDOMNode().value.trim())
// iitu_id = this.refs.iitu_id.getDOMNode().value.trim();
// password = this.refs.password.getDOMNode().value.trim();
var error = UserValidator.valid({iitu_id: iitu_id, password: password});
if (error) {
this.setState({"errors": error });
// console.log(error);
} else {
// console.log(error);
}
},
getInitialState: function() {
return {
'errors': {
iitu_id: null,
password: null
}
};
},
render: function() {
return (
/*jshint ignore:start */
<form className="form-horizontal" onSubmit={this.handleSubmit} >
<FormGroup label="iitu id" error_msg={this.state.errors.iitu_id} fieldName="iitu_id" fieldType="text" />
<FormGroup label="password" error_msg={this.state.errors.password} fieldName="password" fieldType="password" />
<ButtonGroup text="Войти"/>
</form>
/*jshint ignore:end */
);
}
});
var FormGroup = React.createClass({
render: function() {
var formGroupCss = 'form-group';
if (this.props.error_msg){
formGroupCss = 'form-group has-error';
}
return (
/*jshint ignore:start */
<div className={formGroupCss}>
<Label fieldName={this.props.fieldName}>{this.props.label}</Label>
<InputField type={this.props.fieldType}>{this.props.label}</InputField>
<label className="control-label" for="inputError1">{this.props.error_msg}</label>
</div>
/*jshint ignore:end */
);
}
});
var ButtonGroup = React.createClass({
render: function () {
return (
/*jshint ignore:start */
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-default">{this.props.text}</button>
</div>
</div>
/*jshint ignore:end */
);
}
});
var InputField = React.createClass({
render: function() {
return (
/*jshint ignore:start */
<div className="col-sm-5">
<input className="form-control" type={this.props.fieldType} placeholder={this.props.children}/>
</div>
/*jshint ignore:end */
);
}
});
exports.SignUpForm = SignUpForm;
there is a bit too much code, but I think it is pretty easy to read. The question is how can I get the value of my InputField class when I press the submit button (simply get form value)? There is problem that my input tag deep inside DOM. Additional question is it good to have the following code design I mean describe each logical component as new 'class' ?
If you give your form inputs name attributes, you can use the form's .elements collection to access its inputs.
I recently split the code newforms uses for this out into a reusable get-form-data module, which would let you do this assuming your inputs have name attributes:
var getFormData = require('get-form-data'); // Or just use the browser bundle
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var data = getFormData(e.target, {trim: true});
var error = UserValidator.valid(data);
if (error) {
this.setState({errors: error});
} else {
// ...
}
},
// ...
Or, if you need to get input as it's given, you can add an onChange handler to the <form> or some other component which contains all the form inputs, instead of having to do each one individually:
handleChange: function(e) {
var form = e.target.form;
var name = e.target.name;
var data = getFormData.getNamedFormElementData(form, name, {trim: true});
// ...
},
render: function() {
return <form onSubmit={this.handleSubmit} onChange={this.handleChange}>
{/* ... */}
</form>
}
});
In the absence of a framework like Angular or Flux, shoot data between your components using callbacks.
The technique used here is explained in detail on React's website. Another solution, however, is to use a form library. I took a similar route and tried to build form components from scratch -- it works, but it's a path others have already cleared. Check out newforms and react-forms.
Note that the code I shared is untested, though it should be very close to working.
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log(this.iitu_id + ' and ' + this.password + ' should work.');
var error = UserValidator.valid({iitu_id: iitu_id, password: password});
if (error) {
this.setState({"errors": error });
// console.log(error);
} else {
// console.log(error);
}
},
getInitialState: function() {
return {
'errors': {
iitu_id: null,
password: null
}
};
},
updateForm: function(field, value) {
/* Or do something like this using underscore.js:
var form = _.clone(this.form);
form[field] = value;
this.setState({ form: form });
This approach let's you grab the form object on
submit instead of hunting for the form values
in SignUpForm's state.
*/
this.setState({ field: value });
},
render: function() {
return (
/*jshint ignore:start */
<form className="form-horizontal" onSubmit={this.handleSubmit} >
<FormGroup update={this.updateForm} label="iitu id" error_msg={this.state.errors.iitu_id} fieldName="iitu_id" fieldType="text" />
<FormGroup label="password" error_msg={this.state.errors.password} fieldName="password" fieldType="password" />
<ButtonGroup text="Войти"/>
</form>
/*jshint ignore:end */
);
}
});
var FormGroup = React.createClass({
handleChange: function(event) {
this.props.update(this.props.fieldName, event.target.value);
},
render: function() {
var formGroupCss = 'form-group';
if (this.props.error_msg){
formGroupCss = 'form-group has-error';
}
return (
/*jshint ignore:start */
<div className={formGroupCss}>
<Label fieldName={this.props.fieldName}>{this.props.label}</Label>
<InputField handleChange={this.handleChange} type={this.props.fieldType}>{this.props.label}</InputField>
<label className="control-label" for="inputError1">{this.props.error_msg}</label>
</div>
/*jshint ignore:end */
);
}
});
var ButtonGroup = React.createClass({
render: function () {
return (
/*jshint ignore:start */
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-default">{this.props.text}</button>
</div>
</div>
/*jshint ignore:end */
);
}
});
var InputField = React.createClass({
render: function() {
return (
/*jshint ignore:start */
<div className="col-sm-5">
<input onChange={this.props.handleChange} className="form-control" type={this.props.fieldType} placeholder={this.props.children}/>
</div>
/*jshint ignore:end */
);
}
});
exports.SignUpForm = SignUpForm;
Specifically, take note of the callback functions being passed from parent to child.
SignUpForm gives updateForm to FormGroup
FormGroup gives handleChange to InputField
Then to get the data back to the top, you just reverse the process.
When <input/> changes, it runs handleChange
When handleChange runs, it passes the FormGroup's fieldName and value to SignUpForm
SignUpForm updates its state with the new field value
Concerning your second question - it is a bad approach to add a component for every logical element.
In your example - all of the components, (besides the first) has no logic, only 'render'. this design adds many useless lines of code and "this.props" repetitions.
In your example, I would have just add everything to the render of the first component.
Now, let say you have two components, and want to reach the child from the parent:
var SignUpForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
//now - reach your inner comp with refs
console.log(this.refs.inner.refs.username.getDOMNode().value.trim());
},
render: function() {
return (
//and pass the submit function as a callback
<InnerForm ref="inner" submit={this.props.handleSubmit}/>
);
}
});
var InnerForm = React.createClass({
render: function() {
return (
<form>
<input type="text" ref="username"/>
<input type="submit" onClick={this.props.submit} >
</form>
);
}
});
In your example there was to many components inside components to do what I did.
Still, there are cases when you need long linage of child components. Using this.refs.a.refs.b.refs.c etc - might be pretty ugly, and also prevents re-usability.
in those cases try using any MVC architecture (I'm using reflux and loving it)
I would suggest an onChange event listener to your inputs that use callbacks to relay the information all the way back up to your top level component.
var SignUpForm = React.createClass({
getInitialState: function() {
return {
inputs: {
id: '',
password: ''
}
};
},
render: function() {
return (
<form className="form-horizontal" onSubmit={this.handleSubmit} >
<FormGroup ... callback={this.handleIdChange} />
<FormGroup ... callback={this.handlePasswordChange} />
<ButtonGroup text="Войти"/>
</form>
);
},
handleIdChange: function(newId) {
this.setState({
inputs: {
id: newId.target.value,
password: this.state.inputs.password
}
});
},
handlePasswordChange: function(newPassword) { ... }
});
then pass that callback down to the base level form fields and use them for handling the onChange event.
var InputField = React.createClass({
render: function() {
return (
/*jshint ignore:start */
<div className="col-sm-5">
<input ... onChange={this.props.callback}/>
</div>
/*jshint ignore:end */
);
}
});
then with your handleSubmit just output the values in the this.state.inputs object.