Submit form on prop value changing - javascript

I am new to React and trying to modify this application: https://github.com/nice-table/bitmex-scaled-orders
My goal is, say the prop "instrumentData" found in "src/modules/orders/OrderForm.js" has "instrumentData.lastprice" value changing to a specific value in real-time in the backend. I want to submit the form on that page if the value reaches a specific value. In other words, I want to keep monitoring that prop untill it hits a number and it will submit the form upon that. Is that doable through states? I tried to research it but given I am new to React I am a bit lost as to what code to use and where exactly to add it.
Thanks.

Autosubmitting is simple
It's simple to run some action on data change. React components are data driven - autoupdating. You can just insert a function 'into data flow'.
Your data source is in DataContext then you should use <DataContext.Consumer /> to get data 'stream' - stream because it's frequently updated using socket connection.
<DataContext.Consumer>
{ (data, submitForm, isSubmitting) => {
console.log("context data", data );
// extract data from `data` object
// const someData = data.someProperty;
// if( someData > 12345 ) {
// if( !isSubmitting ) {
// submitForm()
// }
// return "Limit reached"
// }
// return null
}}
</ DataContext.Consumer>
This snippet can be placed almost anywhere after this code:
render={({
values,
errors,
touched,
setFieldValue,
submitForm,
isSubmitting,
isValid,
validateForm
}) => (
<React.Fragment>
// place it here
... and of course before end of this fragment (</ React.Fragment>).
You can pass and use almost all functions defined in this component (file), f.e. setFieldValue("priceUpper", to update form value before submitting.
Autosubmitting is NOT so simple
Problem is not trivial. You should create a component with internal logic to:
set limit (render input, onChange handler, useState) instead of hardcoded value
block(or not? checkbox?) autosubmitting in a loop (formik will submit but later it will clear isSubmitting flag - our component will autosubmit again)
render context consumer inside - optimize rerenderings
etc.
Good luck ;)

React has a really good write up on forms and handling onChange() which is an event that can fire when a field is changed https://reactjs.org/docs/forms.html.
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
submit = () => {
// submit form
// eg axios.post(URL, {
// value: this.state.value
// })
}
handleChange(event) {
this.setState({value: event.target.value});
if (this.state.value == 10) {
this.submit()
}
}
return (
<form>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</form>
);
}
}
I see the github repo is using hooks in lieu of component classes.
You can read about hooks here https://reactjs.org/docs/hooks-intro.html.
onChange can still call handle change and instead of this.state you may be using useState

Related

Why an input value should receive the state in react? [duplicate]

I cannot understand why we set the value={this.state.task} when it is just an empty string, and how exactly the flow of data goes from the input value and then to the state.
When we first set the value, it's basically an empty string. But when I try to actually set value='' , the input field does not function properly on the rendered page.
I get that onChange we set the state to the corresponding name and value, and that that's how the data is flowing into the state. But then why does it not work when, again, we just set value='' ?
import React, { Component } from 'react';
import uuid from 'uuid/v4';
export class NewTodoForm extends Component {
constructor(props) {
super(props)
this.state = {
task: ""
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e){
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit(e){
e.preventDefault();
this.props.createTodo({ ...this.state, id: uuid() });
this.setState({ task: "" });
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor='task'>New Todo</label>
<input
type='text'
placeholder='New Todo'
id='task'
name='task'
// why is this {this,state.task} ?
value={this.state.task}
onChange={this.handleChange}
/>
<button>Add Todo</button>
</form>
</div>
)
}
}
export default NewTodoForm
Because value is setting ... well the value of the input. By doing this value={this.state.task} basically you are connecting your input with the component's state and with the lifecycle of the React component. So basically whenever you change your component's state that has the input from anywhere (even programmatically), React will be able to update the input correctly and there won't be any bugs or weird stuff happening.
In the React docs it is explained very well. They are doing this controlled component ...
An input form element whose value is controlled by React in this way is called a “controlled component”.
... so that the state of the React component to be the only 'source of truth', meaning to prevent weird bugs and undesired behaviour.
Since the value attribute is set on our form element, the displayed value will always be this.state.value, making the React state the source of truth.
It is always a good practice to have one source of truth and not many. In this case if you leave the input's value to be different from the component's state, you are making more than one source of truths and thus exposing your app to bugs.

React form can be submitted twice despite conditional render

I have a React application coupled to Redux. There is a component rendering a form wrapper (a custom implementation of Formik), while the form inputs themselves are rendered by a child component.
(Not the exact code, but gets the point across.)
...
render() {
const {
config,
updateContactDetails,
errorMessages,
contactDetails,
previousFormValues,
isUpdating,
} = this.props;
const { apiBaseUrl, fetchTimeout, globalId } = config;
const initialValues = previousFormValues || getInitialContactDetailsValues(contactDetails);
if (isUpdating) return <Spinner />;
return (
<Form
initialValues={initialValues}
validate={(values) => validate(values, errorMessages)}
onSubmit={(values) => {
updateContactDetails(apiBaseUrl, globalId, values, fetchTimeout); // dispatch action
}}
>
<ContactDetailsForm content={content} />
</Form>
);
}
...
When you click the submit button in ContactDetailsForm, the value of isUpdating in the Redux store is set to true. As you can see above, that causes the the form to be replaced with a spinner component. However, it is somehow possible to submit the form twice by clicking the button twice.
How can this be? Could there be re-render happening before the one that replaces the form with the spinner? I know I can solve the problem by passing isUpdating into ContactDetailsForm and using it to disable the button, but I still want to illuminate the cause.
EDIT
The reducer looks something like this, in case it helps:
case UPDATE_CONTACT_DETAILS_START: {
return {
...state,
errorUpdatingContactMethods: {},
hasUpdatedContactDetails: false,
isUpdating: true,
contactDetailsValues: action.values,
};
}
You should instead set a disabled property on the button based on the isUpdating prop. It might be that it's just a race condition.

React JS Higher-Order-Component not execute function if state is being set outside that function

I can't think about a proper sentence for the question title, so first I would like to apologize if the title is somehow confusing.
So I was just finish learning React JS and trying to build an app for practice. In this app there is a login form that if the email or password entered is wrong, it will show an alert and blank the fields.
With the basic React JS implementation all is well, but when I tried to modify my code to implement higher-order-component, the app become buggy and I can't seem to find where the culprit is.
So the buggy thing is, if I enter the email or the password incorrectly, and click login, than the fields will be blanked for me to input / try again, but It seems the fields wont accept any input, it just stays blank no matter what keys I pressed on keyboard, so that I need to refresh page so that the fields can accept my input again.
Here Are My Code:
function ResponseAlert(props){
if(props.alertStatus!==undefined && props.alertStatus!==null){
const className = 'alert alert-' + props.alertStatus;
return <div className={className}>{props.alertMessage}</div>
}
return <div></div>;
}
function H2Title(props) {
return <h2>{props.title}</h2>;
}
class InputText extends React.Component{
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(evt){
this.props.onInputChange(evt.target.id, evt.target.value);
}
render(){
return (
<div className="form-group">
<label htmlFor={this.props.elemId}>{this.props.label}</label>
<input type={this.props.type} className="form-control" name={this.props.elemId} id={this.props.elemId} value={this.props.elemValue} onChange={this.handleChange} required={this.props.required} />
</div>
);
}
}
function withSetStateFormData(WrappedComponents, componentName){
return class extends React.Component{
constructor(props){
super(props);
this.state = { formdata: { email: '', password: '' } };
this.setStateFormData = this.setStateFormData.bind(this);
}
setStateFormData(field, value){
this.setState(function(state, props){
let tempData = state.formdata;
tempData[field] = value;
return {
formdata: tempData
};
});
}
render(){
return <WrappedComponents handleInputChange={this.setStateFormData} {...this.props} {...this.state} />
}
}
}
class FormLogin extends React.Component {
constructor(props){
super(props);
this.state = {
alertstatus: null,
alertmessage: '',
formdata: this.props.formdata
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(evt){
evt.preventDefault();
const self = this;
fetch("http://someurl.com/login.php",
{
mode: 'cors',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state.formdata)
})
.then(function (response) {
if (response.status === 200) {
response.json().then(function (resData) {
alert('Yeay! Successful login.');
});
}
else{
//here is where the problem lies, I think.
self.setState({
alertstatus: 'danger',
alertmessage: 'Login failed. Email or password is incorrect.',
formdata: { email: '', password: '' }
});
}
})
.catch(function (err) {
console.log(err);
});
return false;
}
render(){
const handleInputChange = this.props.handleInputChange;
return (
<div>
<ResponseAlert alertStatus={this.state.alertstatus} alertMessage={this.state.alertmessage} />
<H2Title title="Login"/>
<form onSubmit={this.handleSubmit}>
<InputText type="email" elemId="email" label="Email Address" elemValue={this.state.formdata.email} onInputChange={handleInputChange} required={true}/>
<InputText type="password" elemId="password" label="Password" elemValue={this.state.formdata.password} onInputChange={handleInputChange} required={true}/>
<button type='submit' className='btn btn-primary'>Login</button>
</form>
</div>
);
}
}
const EnhancedComponent = withSetStateFormData(FormLogin, "login");
ReactDOM.render(<EnhancedComponent />, document.querySelector('#app'));
So I think after the setState function executed to blank the email and password, the onChange event somehow refuse to work to update the fields on input change.
Anybody please enlighten me what causing this behaviour?
I just want to say that you don't need to use a higher order component here however, what you are trying to do can work.
There are a couple problems that I see.
First, you are setting the InputText value according to the FormLogin state. That's fine however, when the InputText changes, it calls onInputChange which is set to setStateFormData from the higher-oder component. The problem is that setStateFormData sets the state for the higher-order component, not the state for FormLogin. So the result is that the FormLogin state doesn't update which means the value for InputText doesn't update.
I know what you are thinking, "But the input does update before I click submit". Yes, and no.
This brings us to the second problem which has to do with JS references.
From the React docs
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.
If we look at setStateFormData you are technically directly mutating state by first referencing an object within state with let tempData = state.formdata; then modifying the referenced object with tempData[field] = value;. This is a big no-no in React because it leads to all sorts of confusing bugs, like this one.
You can build the new state from the old state but you are only allowed to copy values, not references. We can fix this by re-writing setStateFormData as follows:
setStateFormData(field, value){
this.setState(function(state, props){
let newFormData = {
email: state.formdata.email;
password: state.formdate.password;
};
newFormData[field] = value;
return {
formdata: newFormData
};
});
}
Now, we are deep copying the formdata from the old state. Great, one bug down. If we test the application again, now we see that the input never updates, before or after submit. This is because of the first problem mentioned above.
Before, the InputText value appeared to be updating because the FormLogin state appeared to be updating however, state.formdata in FormLogin was just a reference to state.formdata in the higher-order component which was set in the constructor of FormLogin with formdata: this.props.formdata. This meant that when setStateFormData directly mutated the state of the higher-order component, it was also directly muting the state of FormLogin. This made it look like everything was working but it was really just because of the references. As soon as a reference is lost, the app broke. So, when did we lose a reference? As you guessed, when we call setState in handleSubmit.
self.setState({
alertstatus: 'danger',
alertmessage: 'Login failed. Email or password is incorrect.',
formdata: { email: '', password: '' }
});
This assigned state.formdata in FormLogin to a new object, breaking the reference to state.formdata in the higher-order component.
Okay, so how to fix it. There are a few ways.
I would recommend removing formdata from the state of FormLogin completely. That way you don't need to worry about keeping the two formdata objects in sync. Simply create a clearFormData function on the higher-order component and pass it down to FormLogin to call in handleSubmit. Additionally, you need to set the TextInput value according to the props of FormLogin. I think this is the cleanest solution while still using the higher-order component.
The simplest solution, is of course just getting rid of the higher-order component but I'm not sure if that is an option if your goal is specifically to learn about higher-order components.
Lastly, you could implement a getDerivedStateFromProps function on FormLogin which would rebuild the FormLogin state every time its props change which coincidentally happens every time the higher-order state changes. This is a clean way to update the formdata in FormLogin everything time it changes in the higher-order component. The problem is you still need to worry about updating the formdata in the higher-order component every time it changes in FormLogin.
I hope this helps.

Input text inside a react component

I'm trying to create an input text inside a react component and then I realised that it's a bad praxis. So I investigated a little bit so I found Controlled-Components, so I think this is what I need, but looking at my Component I do not know how to create it.
I do not have an extends Redux.Component so a friend suggested me to create a Component but couldn't get succeed.
What I was trying is this :
Inside my component
<input
...
/>
{" "}
<input
...
/>
<span>
<myButton
...
arguments={[document.getElementById("id1").value, document.getElementById("id2").value]}
>
[ send ]
</myButton>{" "}
</span>
But I'm getting this error :
The given id must not be null!; nested exception is java.lang.IllegalArgumentException: The given id must not be null!
EDIT
On my component where I have all of those code I have this :
<myButton
id={id}
arguments={[intputStuff]}
>
So my problem is if I do what Tom's says I do not have the id in the other component.
So the thing should be create this component inside the other component and then get the values of the inputtexts and put them as an arguments
It's not clear from your post what exactly you're trying to accomplish.
It appears that you're trying to build a component with 2 text inputs and a button.
If you want the button to "submit" the values of the two inputs, you should do something like this:
class SomeComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
value1: props.initialValue1,
value2: props.initialValue2
}
}
onChangeText = (event) => this.setState({ [event.target.name]: event.target.value })
onClickSubmit = (event) => {
let { onSubmit } = this.props
if(typeof onSubmit !== 'function') return
let { value1, value2 } = this.state
return onSubmit([ value1, value2 ])
}
render() {
let {
initialValue1,
initialValue2,
onSubmit,
className,
...props
} = this.props
let {
value1,
value2
} = this.state
return (
<div className={`SomeComponent ${className}`} {...props}>
<input value={value1} name="value1" onChange={this.onChangeText} />
<input value={value2} name="value2" onChange={this.onChangeText} />
<button onClick={this.onClickSubmit}>
Submit
</button>
</div>
)
}
}
A few notes:
This example uses a bunch of futuristic JS: destructuring, rest/spread, class properties, computed property names, and arrow functions. Each feature is being leveraged for a specific purpose, not just because they're cool. If your environment doesn't support some of these features, you'll need to find a workaround that makes good on some additional constraints.
This is not a controlled component, but it does contain 2 controlled inputs. It uses the "initialValue" pattern: the owning component provides starting values, but is unaware of the blow-by-blow as the user types each character. The owning component is only notified of the new values when the button is clicked. This pattern can result in loss of data if the owner is re-rendered before the current value are submitted.
Generally, when using React, you want to avoid using native DOM methods to access or manipulate elements. (There are plenty of exceptions, of course.) One reason you want to avoid native DOM methods is that component lifecycle methods might execute before the React renderer has actually updated the DOM -- so document.getElementById('someid') might return undefined.

ReactJS - How to properly initialize state from props which is populated by fetching data?

Here is my editing component:
class EditField extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
}
edit(e) {
this.setState({ value: e.target.value });
if (e.keyCode === 13) {
this.props.onEdited(this.state.value);
}
}
render() {
return (
<div>
<input
type="text"
value={this.state.value}
onChange={this.edit.bind(this)}
/>
</div>
)
}
}
I need to populate state from props like this:
function Container({ entity, onEdited }) {
return (
<div>
<EditField onEdited={onEdited} value={entity.firstName} />
<EditField onEdited={onEdited} value={entity.lastName} />
</div>
);
}
The Container component get onEdited and entity props from redux store.
Container's parent will handle data fetching and onEdited (which will
only be triggered if user hit Enter) will dispatch request to the server.
My problem is how to initialize value props properly? Because if I use:
componentDidMount() {
this.setState({
value: this.props.value
});
}
I got empty state because fetching data is not finished when componentDidMount
called. And if I use:
componentWillReceiveProps(nextProps) {
this.setState({
value: nextProps.value
});
}
I got this warning:
Warning: EditField is changing a controlled input of type text to be
unncontrolled. Input elements should not switch from controlled to
uncontrolled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.
So, how to do this correctly?
This is what I recommend:
You could use getInitialState from EditField to populate the value state from the value prop. But this won't work, because getInitialState will only be called once, so subsequent renders will not update the state. Besides, this is an anti-pattern.
You should make the EditField component controlled. Always pass the current value as prop and stop dealing with state at all. If you want a library to help you link the input state with Redux, please take a look at Redux-Form.
The onEdited event you created, at least the way you did it, doesn't play well with controlled inputs, so, what you want to do is to have an onChange event that is always fired with the new value, so the Redux state will always change. You may have another event triggered when the user hits enter (e.g onEnterPressed), so you can call the server and update the entity values. Again. Redux-Form can help here.
Apparently entity.firstName and entity.lastName can only contain the values that the user has confirmed (hit enter), not temporary values. If this is the case, try to separate the state of the form from the state of the entity. The state of the form can be controlled by Redux-Form. When the user hits enter, you can trigger an action that actually calls the server and updates the state of the entity. You can even have a "loading" state so your form is disabled while you're calling the server.
Since Container subscribes to Redux store, I suggest make the EditField stateless functional component. Here's my approach:
const EditField = ({
onEdited,
value
}) => (
<div>
<input
type="text"
value={value}
onChange={onEdited}
/>
</div>
);
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
edit = (e) => {
this.setState({value: e.target.value});
e.keyCode === 13 ? this.props.onEdited(this.state.value) : null;
};
sendValue = (val) => val ? val : this.state.value;
render() {
this.props = {
firstName: "Ilan",
lastName: null
}
let { firstName, lastName, onEdited } = this.props;
return (
<div>
<EditField onEdited={this.edit} value={this.sendValue(firstName)} />
<EditField onEdited={this.edit} value={this.sendValue(lastName)} />
</div>
)
}
}
ReactDOM.render(<Container />, document.getElementById('app'));
A live demo: https://codepen.io/ilanus/pen/yJQNNk
Container will send either firstName, lastName or the default state...

Categories