React get value deep inside DOM - javascript

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.

Related

Is this the "React.js" way of doing this?

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

React: Search Results not being displayed?

So I am learning react.js, and I am developing a quick search engine using the GitHub API of users.
The API side of the project works fine (I have tested by manually entering names into the area)
Its the search build in react that is not working.
(FYI: I am using Plunker which has react support)
script.jsx
var Card = React.createClass({
getInitialState: function(){
return{};
},
componentDidMount: function(){
var component = this;
$.get("https://api.github.com/users/" + this.props.login, function(data){
component.setState(data);
});
},
render: function(){
return(
<div>
<img src={this.state.avatar_url} width="100"/>
<h3>{this.state.name}</h3>
<hr/>
</div>
);
}
});
var Form = React.createClass({
handleSubmit: function(e){
e.preventDefault();
var loginInput = React.findDOMNode(this.refs.login);
this.props.addCard(loginInput.value);
loginInput.value = '';
},
render: function(){
return(
<form onSubmit={this.handleSubmit}>
<input placeholder="Enter Github Name" ref="login"/>
<button>Search</button>
</form>
);
}
});
var Main = React.createClass({
getInitialState: function(){
return {logins: []};
},
addCard: function(loginToAdd){
this.setState({logins: this.state.logins.concat(loginToAdd)});
},
render: function() {
var cards = this.state.logins.map(function(login){
return (<Card login={login} />);
});
return(
<div>
<Form addCard={this.addCard} />
{cards}
</div>
)
}
});
ReactDOM.render(<Main />, document.getElementById("root"));
The problem was (if you check console), that you had a duplicate script tag in the <head> which you didn't need. And also, you were doing React.findDOMNode instead of ReactDOM.findDOMNode
Line 25 of your JSX file:
var loginInput = ReactDOM.findDOMNode(this.refs.login);
That said, you don't need to do ReactDOM.findDOMNode. You can just use this.refs.login

React JS: Reusable components

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.

Uncaught Invariant Violation on rendering search data

I am trying to implement search using React. I have 2 problems on my logic flow:
To set input as Params
To render the data I get from server
While I'm playing with it, I have encountered the error message
Uncaught Invariant Violation input is a void element tag and must not
have children or use props.dangerouslySetInnerHTML.
And this is my code:
import React, { PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Home.scss';
import AWS from 'aws-sdk';
var GetTech = React.createClass({
render: function() {
var createItem = function(item) {
var csd = new AWS.CloudSearchDomain({
endpoint: 'mycloudsearch.amazonaws.com',
region: 'us-east-1'
});
var params = {
query: {this.state.text}
}
csd.search(params, function (err, data) {
if (err) console.log(err, err.stack);
else {
console.log(JSON.stringify(data));
}
});
}
return (
{this.props.items.map(crateItem)}
)
}
});
var FilteredTechs = React.createClass({
getInitialState: function() {
return {
text: '',
items: []
};
},
handleChange: function(event) {
console.log(event);
this.setState({
text: event.target.value
});
},
handleSearch: function(event) {
event.preventDefault();
this.setState({
items: this.props.items,
text: ''
});
},
render: function() {
return (
<div>
<form onSubmit={this.handleSearch}>
<input
type="text"
value={this.state.text}
onChange={this.handleChange}
/>
<input type="button">Search</input>
</form>
<GetTech items={this.state.items} />
</div>
);
}
});
function Home({ techs }) {
<FilteredTechs />
}
Home.propTypes = {
techs: PropTypes.arrayOf(PropTypes.shape({
})).isRequired,
};
export default withStyles(Home, s);
I am new to React. Please advise me as you wish and your tips and comments are very appreciated. Thanks a lot!
The error is pretty clear: inputs must be void elements; that is, they must be self-closing.
This syntax is invalid: <input type="button">Search</input>
You want either: <input type="button" value="Search" />
Or: <button>Search</button>
Input is self-closing tag, However there some cases in which you can use <Input></Input>
You can install reactstrap package then import and use <Input></Input> tag instead of using <input></input>.
Also, you can check the link: https://reactstrap.github.io/components/input-group/
Also, you can check this for the type of input: reactstrap Forms

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.

Categories

Resources