Submitting big forms with react.js - javascript

It's a pain to work with react.js when it comes to form. I was from angular, because of 2 ways binding things are great, it's fast to integrate stuff. But when in react I admit I'm lost.
Says it's a user profile, I got this data from API
var profile = {
name:"Gaila",
age:22,
skills: [{id:1,name:"react"},{id:1,name:"angular"}],
club: [{id:1,name:"dancing"},{id:1,name:"hiking"}],
description: "some long string"
};
on the UI I have text input, textarea, checkbox and select.
How would I handle it when user clicked to save? Do I have to bind every single input elements with onChange? like handleNameChange, handleAgeChange, handleSkillsChange.. omg it's ridiculous.
So ref came into my mind, easy, just do ref="name" and I can get it by ReactDOM.findDOMNode(this.refs.name).value, but wait, it doesn't work on <select>, it's bad sometime I use ref, sometime I go with handle function.
Guys, I seriously, really need help!

Here is an example of reusing an event handler and picking up the differences from the event. See it in action at codepen.
const FormFunc = () => {
const changeHandler = (e) => {
console.log(`e.target.name, name: `, e.target.name, e.target.value)
}
return (
<form>
<input onChange={changeHandler} type='text' name='firstName' />
<input onChange={changeHandler} type='text' name='surname' />
<input onChange={changeHandler} type='phone' name='phone' />
<input onChange={changeHandler} type='email' name='email' />
</form>
)
}

If you only need to extract form values, you can use form-serialize - it's available as a package through npm.
In your component, add a submit event to your form:
<form onSubmit={this.handleSubmit}>
Your handler extracts the form like so:
handleSubmit = (event) => {
event.preventDefault()
const values = serializeForm(event.target, { hash: true })
console.log(values)
}

Related

what does it mean by form state living inside the DOM?

while learning the concept controlled components in Reactjs I came across this statement
controlled component: "A component which renders a form, but the source of truth for that form state lives inside of the component state rather than inside of the DOM"
what is a form state? and what does it mean by form state living inside of DOM or component?
could you please elaborate on it?
what is a form state?
The form state just means "the current values entered into the individual input elements of a form". They can be accessed through the individual input elements value property or through the <form> elements form data itself.
what does it mean by form state living inside of DOM or component
It just means that if you do not explicitly store those values in react state and pass them to the <input> during render it will just be kept in the HTML input elements itself. If you inspect an <input> element in the developer console you will see, that it has a value prop. Uncontrolled just means that you do not explicitly pass that value to the <input> and that you do not handle changes to the value yourself. If you need to access that value in your code you would have to get a ref to the <input> and read that value.
function UncontrolledForm() {
const handleSubmit = event => {
event.preventDefault();
// values are stored in the form but they can be accessed through the DOM node
const formData = new FormData(event.target);
console.log(Object.fromEntries(formData.entries()));
};
return (
<form onSubmit={handleSubmit}>
<label>
foo
<input name="foo" />
</label>
<label>
bar
<input name="bar" />
</label>
<button>submit</button>
</form>
);
}
Controlled on the other hand means that you "control" the value yourself by passing it to the <input> during render all the time.
function ControlledForm() {
// values are kept in react state
const [values, setValues] = useState({foo: '', bar: ''});
const handleChange = event =>
setValues(state => ({
...state,
[event.target.name]: event.target.value
}));
const handleSubmit = event => {
event.preventDefault();
console.log(values);
};
return (
<form onSubmit={handleSubmit}>
<label>
foo
{/* we explicitly pass a value and onChange handler and manage the values ourself */}
<input name="foo" value={values.foo} onChange={handleChange} />
</label>
<label>
bar
<input name="bar" value={values.bar} onChange={handleChange} />
</label>
<button>submit</button>
</form>
);
}

Stripe Card Element default postcode

I'm integrating Stripe Elements in my React project, using this simple component:
render () {
return (
<form onSubmit={event => this.onSubmit(event)}>
<label>
<CardElement className='Input' />
</label>
</form>
)
}
Problem is that I already have another form above where the user enters its billing info (address, zip code, country...). is it possible to pre-fill this field with the Zip code already entered before?
I've looked into Elements source code and see no value option possible. Is it possible somehow to use ref option to do so?
Thanks
According to this: https://stripe.com/docs/stripe-js/reference#elements-create (in options), you can pass the option value, I see here: https://github.com/stripe/react-stripe-elements/blob/master/src/components/Element.js#L26 that any viable option is allowed:
render () {
return (
<form onSubmit={event => this.onSubmit(event)}>
<label>
<CardElement
className='Input'
value={ { postalCode: this.state.postcode } }
/>
</label>
</form>
)
}

How to get Material-UI's <TextField/> to return correctly with ref='' like <input/> in ReactJS?

With the following method:
handleClick(event) {
const inputText = this.refs.inputText
console.log(inputText.value.trim())
}
I am trying to get Material-UI's <TextField/> to return the input text correctly with ref like the <input/> can with <button/> triggering it:
<input
className='form-control'
placeholder='Input Text'
ref='inputText'
type='text'
/>
<button
onClick={(event) => this.handleClick(event)}
>
And I attempted the following with <TextField/>, but it returns as undefined. How can I get it to return inputted text correctly like the <input/> above?
<TextField
hint='Enter text'
className='form-control'
ref='inputText'
type='text'
/>
I would suggest this approach:
Set up your textfield with a value and onChange function that are hooked into redux itself, where the onChange function just updates the value.
So you'd have something like this :
<TextField
value={this.props.textFieldValue}
onChange={this.props.textFieldChange}
Where the textFieldChange is an action that simply updates the textFieldValue. Most forms in redux will work something like this. Keep in mind the names i made up for those props and action are just for example. If you have a big form you might want to consider have part of the state tree dedicated to the form itself where you have :
state: {
form: {
textField: ...your textfield value here,
name: ...,
whateverElse: ...
}
};
I like doing this with redux because I can make that architect form part of the state to look like the json payload of wherever I'm sending it to, so there I can just send the form went I want to send it.
Anyways, back to this example. When you click your handleClick now. All you need to do is this to get the value:
handleClick(event) {
console.log(this.props.textFieldValue.trim());
}
Because the textfield is updated with every change, you always have access to it in your state. This also gives you flexibility over the refs approach, because if you use refs you will have a lot harder of a time getting access to that form in other components. With this approach, all the information is on your state so you can access it anytime, as long as you manage your props.
You should use the onChange={} to get the value:
_onChange = (e) => {
console.log(e.target.value);
}
<TextField
onChange={this._onChange}
/>
Here's a better solution than using onchange event, we get directly the value of the input created by material-ui textField :
create(e) {
e.preventDefault();
let name = this.refs.inputText.input.value;
alert(name);
}
constructor(){
super();
this.create = this.create.bind(this);
}
render() {
return (
<form>
<TextField ref="inputText" hintText="" floatingLabelText="Your name" /><br/>
<RaisedButton label="Create" onClick={this.create} primary={true} />
</form>
)}
hope this helps.

How to disable HTML validation in React using formsy-react?

I'm trying to get rid of the browser's default validation logic using formsy-react, and according to the documentation the "formNoValidation" attribute should do the trick. But I can't get it to work.
What am I doing wrong?
var React = require('React');
var Formsy = require('formsy-react');
var Input = require('./forms/Input.js');
module.exports = React.createClass({
render: function () {
return (
<Formsy.Form>
<Input ref="phonenumber" id="phonenumber" value={this.state.phonenumber.value} name="phonenumber" required validations="isNumeric" validationError="Please provide a valid phone number" />
</Formsy.Form>
);
}
});
Input.js
var Formsy = require('formsy-react');
var React = require('React');
module.exports = React.createClass({
mixins: [Formsy.Mixin],
changeValue: function (event) {
this.setValue(event.currentTarget.value);
},
render: function () {
var className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
var isReadOnly = this.props.readOnly;
var errorMessage = this.getErrorMessage();
return (
<div className={className}>
<input type="text" onChange={this.changeValue} value={this.getValue()} readOnly={isReadOnly} required={this.isRequired()} formNoValidate />
<span>{errorMessage}</span>
</div>
);
}
});
The formNoValidate attribute is only intended for elements that submit the form. So, placing it on a "text" type of input will work if it is the only input in the form (no submit button).
Imagine having a form for writing an article, It could have two submit buttons, one for "Save draft" that doesn't need to run native validation, and one for "Publish" that does.
Adding noValidate on the form tag should disable native validation on the form completely, however this isn't possible until issue issue 89 is resolved (scheduled for the next release).
write "novalidate" in form tag.
Example
<form method="post" novalidate>...</form>
Example
To disable HTML validation, use noValidate, it`s camel case sensitve:
<form onSubmit={this.handleSubmit} noValidate>
// code
</form>
Apparently, this is a way around the problem:
<Formsy.Form>
<Input ref="phonenumber" id="phonenumber" name="phonenumber" validations="isNumeric" validationError="Oppgi et gyldig telefonnummer"/>
<input type="submit" formNoValidate value="Submit"/>
</Formsy.Form>

React input defaultValue doesn't update with state

I'm trying to create a simple form with react, but facing difficulty having the data properly bind to the defaultValue of the form.
The behavior I'm looking for is this:
When I open my page, the Text input field should be filled in with the text of my AwayMessage in my database. That is "Sample Text"
Ideally I want to have a placeholder in the Text input field if the AwayMessage in my database has no text.
However, right now, I'm finding that the Text input field is blank every time I refresh the page. (Though what I type into the input does save properly and persist.) I think this is because the input text field's html loads when the AwayMessage is an empty object, but doesn't refresh when the awayMessage loads. Also, I'm unable to specify a default value for the field.
I removed some of the code for clarity (i.e. onToggleChange)
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message
{awayMessage: {}}
onTextChange: (event) ->
console.log "VALUE", event.target.value
onSubmit: (e) ->
window.a = #
e.preventDefault()
awayMessage = {}
awayMessage["master_toggle"]=#refs["master_toggle"].getDOMNode().checked
console.log "value of text", #refs["text"].getDOMNode().value
awayMessage["text"]=#refs["text"].getDOMNode().value
#awayMessage(awayMessage)
awayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
console.log "AWAY_MESSAGE", this.state.awayMessage
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else "Placeholder Text"
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input ref="master_toggle" type="checkbox" onChange={this.onToggleChange} defaultChecked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} defaultValue={awayMessageText} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
my console.log for AwayMessage shows the following:
AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}
Another way of fixing this is by changing the key of the input.
<input ref="text" key={this.state.awayMessage ? 'notLoadedYet' : 'loaded'} onChange={this.onTextChange} defaultValue={awayMessageText} />
Update:
Since this get upvotes, I will have to say that you should properly have a disabled or readonly prop while the content is loading, so you don't decrease the ux experience.
And yea, it is most likely a hack, but it gets the job done.. ;-)
defaultValue is only for the initial load
If you want to initialize the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Personally I like to just use defaultValue if I'm just initializing it and then just use refs to get the value when I submit. There's more info on refs and inputs on the react docs, https://facebook.github.io/react/docs/forms.html and https://facebook.github.io/react/docs/working-with-the-browser.html.
Here's how I would rewrite your input:
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else ''
<input ref="text" onChange={this.onTextChange} placeholder="Placeholder Text" value={#state.awayMessageText} />
Also you don't want to pass placeholder text like you did because that will actually set the value to 'placeholder text'. You do still need to pass a blank value into the input because undefined and nil turns value into defaultValue essentially. https://facebook.github.io/react/tips/controlled-input-null-value.html.
getInitialState can't make api calls
You need to make api calls after getInitialState is run. For your case I would do it in componentDidMount. Follow this example, https://facebook.github.io/react/tips/initial-ajax.html.
I'd also recommend reading up on the component lifecycle with react. https://facebook.github.io/react/docs/component-specs.html.
Rewrite with modifications and loading state
Personally I don't like to do the whole if else then logic in the render and prefer to use 'loading' in my state and render a font awesome spinner before the form loads, http://fortawesome.github.io/Font-Awesome/examples/. Here's a rewrite to show you what I mean. If I messed up the ticks for cjsx, it's because I normally just use coffeescript like this, .
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
{ loading: true, awayMessage: {} }
componentDidMount: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message, loading: false
onToggleCheckbox: (event)->
#state.awayMessage.master_toggle = event.target.checked
#setState(awayMessage: #state.awayMessage)
onTextChange: (event) ->
#state.awayMessage.text = event.target.value
#setState(awayMessage: #state.awayMessage)
onSubmit: (e) ->
# Not sure what this is for. I'd be careful using globals like this
window.a = #
#submitAwayMessage(#state.awayMessage)
submitAwayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
if this.state.loading
`<i className="fa fa-spinner fa-spin"></i>`
else
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input type="checkbox" onChange={this.onToggleCheckbox} checked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} value={this.state.awayMessage.text} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
That should about cover it. Now that is one way to go about forms which uses state and value. You can also just use defaultValue instead of value and then use refs to get the values when you submit. If you go that route I would recommend you have an outer shell component (usually referred to as high order components) to fetch the data and then pass it to the form as props.
Overall I'd recommend reading the react docs all the way through and do some tutorials. There's lots of blogs out there and http://www.egghead.io had some good tutorials. I have some stuff on my site as well, http://www.openmindedinnovations.com.
it's extremely simple, make defaultValue and key the same:
<input defaultValue={myVal} key={myVal}/>
This is one of the recommended approaches at https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
To force the defaultValue to re-render all you need to do is change the key value of the input itself. here is how you do it.
<input
type="text"
key={myDynamicKey}
defaultValue={myDynamicDefaultValue}
placeholder="It works"/>
Maybe not the best solution, but I'd make a component like below so I can reuse it everywhere in my code. I wish it was already in react by default.
<MagicInput type="text" binding={[this, 'awayMessage.text']} />
The component may look like:
window.MagicInput = React.createClass
onChange: (e) ->
state = #props.binding[0].state
changeByArray state, #path(), e.target.value
#props.binding[0].setState state
path: ->
#props.binding[1].split('.')
getValue: ->
value = #props.binding[0].state
path = #path()
i = 0
while i < path.length
value = value[path[i]]
i++
value
render: ->
type = if #props.type then #props.type else 'input'
parent_state = #props.binding[0]
`<input
type={type}
onChange={this.onChange}
value={this.getValue()}
/>`
Where change by array is a function accessing hash by a path expressed by an array
changeByArray = (hash, array, newValue, idx) ->
idx = if _.isUndefined(idx) then 0 else idx
if idx == array.length - 1
hash[array[idx]] = newValue
else
changeByArray hash[array[idx]], array, newValue, ++idx
Related issue
Setting defaulValue on control din't not update the state.
Doing reverse works perfectly:
Set state to default value, and the control UI gets updated correctly as if defaulValue was given.
Code:
let defaultRole = "Owner";
const [role, setRole] = useState(defaultRole);
useEffect(() => {
setMsg(role);
});
const handleChange = (event) => {
setRole(event.target.value );
};
// ----
<TextField
label="Enter Role"
onChange={handleChange}
autoFocus
value={role}
/>
Define a state for your default value
Surround your input with a div and a key prop
Set the key value to the same value as the defaultValue of the input.
Call your setDefaultValue defined at the step 1 somewhere to re-render your component
Example:
const [defaultValue, setDefaultValue] = useState(initialValue);
useEffect(() => {
setDefaultValue(initialValue);
}, false)
return (
<div key={defaultValue}>
<input defaultValue={defaultValue} />
</div>
)
Give value to parameter "placeHolder".
For example :-
<input
type="text"
placeHolder="Search product name."
style={{border:'1px solid #c5c5c5', padding:font*0.005,cursor:'text'}}
value={this.state.productSearchText}
onChange={this.handleChangeProductSearchText}
/>
Use value instead of defaultValue and change the value of the input with the onChange method.

Categories