React changing parent state during input onChange getting stuck - javascript

I am building a huge form which is made of atleast 50 inputs.
I have wrote a function in the form component that will save the value of the input to the form state:
PARENT FUNCTION
saveToState(details) {
const { company } = this.state;
company[details.part][details.element] = details.value;
this.setState({ company });
}
PASSING TO CHILD COMPONENT (INPUT)
<FieldInput
label="Name (as shown) *"
part="information"
element="displayName"
saveToState={this.saveToState}
/>
Here is the Input component:
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onChange={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}
In result whenever I type something in the input It's taking it 200-300ms to really display what I wrote in the input, the state is getting updates instantly but whenever I type a character I set the new state of the parent form and update it which updates the whole component. The only way i found around it is to use saveToState within the parent component without passing it down. but that would require 1000's of line of code, Is there any way around this? Thanks!

There are alot of ways you can solve this problem. The easiest one and the fastest one is to use onBlur instead of onChange that way setState will happen not when you key pressing in the input but when the input loses focus.
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onBlur={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}

Related

Changing state of one component from another component in another file

I am new to React and web dev in general.
I have created a Component containing list of calculator like buttons which is stored in Buttons.js.
There is another component called as Submit stored in Submit.js. Submit component is basically a textbox in which we type a mathematical expression which I want to process later.
Both of these components are then called in another component call Leftwindow.js.
So my question is,
How can I make clicking in Buttons component affect the textbox in Submit component. I know it could be done easily had the buttons and input box been the part of a single component.
Basically if I press the '1' button I want it to be added to the input box.
A snapshot of how it looks -
Overview
Code for Buttons.js -
class Buttons extends Component {
constructor(props){
super(props);
this.state = {
//buttonrows//
};
}
render(){
const row1elems = this.state.row1.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row2elems = this.state.row2.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row3elems = this.state.row3.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row4elems = this.state.row4.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
return (
<div className="center">
<ButtonGroup>
{row1elems}
</ButtonGroup>
<ButtonGroup>
{row2elems}
</ButtonGroup>
<ButtonGroup>
{row3elems}
</ButtonGroup>
<ButtonGroup>
{row4elems}
</ButtonGroup>
</div>
);
}
}
export default Buttons;
Code for Submit.js -
class Submit extends Component{
constructor(props){
super(props);
this.state = {
fx: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event){
const target = event.target;
const val = target.val;
const name = target.name;
this.setState({
[name]: val
})
}
handleSubmit(event){
}
render(){
return(
<div>
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Col md={12}>
<Input type="text" id="fx" name="fx" placeholder="Type Here" value = {this.state.fx} onChange={this.handleInputChange} />
</Col>
</FormGroup>
</Form>
<Button type="submit" color="primary">Differentiate</Button>
</div>
)
}
}
export default Submit;
Code for LeftWindow.js --
import React, { Component } from 'react';
import Buttons from './Buttons';
import './Custom.css';
import Submit from './Submit';
class LeftWindow extends Component{
constructor(props){
super(props);
}
render(){
return(
<div className="col-3 bg-dark fullheight">
<Buttons/>
<h3 className="center">Enter the function to be differentiated</h3>
<Submit/>
</div>
);
}
}
export default LeftWindow;
This is how your common ancestor file will look like -
Added state having input.
Added a callback "handleInputChange" which updates this input state.
Passed this callback to Both the components.
In your Submit.js file you will need to change your input tag with this
<Input
type="text"
value={this.props.input}
onChange={e => {
this.props.handleInputChange(e.target.value);
}}
/>
Also in your Buttons.js file call this.props.handleInputChange on Button click.
<Button
onClick={() => {
this.props.handleInputChange(button.label)
}}
color={colours[button.type]}
className="buttonsize"
>
{button.label}
</Button>
That's it.
Hope I could help!
In React you work with tree of components where data can travel from top to bottom. It is possible to notify parent component about changed data via passed callbacks. If you have two components that have common ancestor, you can share data between them through this common ancestor.
Let's say you have component Parent which renders your Buttons and Submit components. If you store your data (or state) in Parent and pass this data as props with callbacks, your components then can notify Parent about things happened and parent can change it's state and pass new state as props to children.
There is a "state management" solutions when your data lives detached of your components and injected on one by one basis. In such you won't need parent to store the data, but if we talk about pure react - to share data between branches in react tree, this branches should have common ancestor somewhere in the tree.

Loose focus on react input box after one character input

When I enter a character in the input box
The state updates with the new character
Then I loose focus on the input box
so I can only modify the box 1 keypress at at time
The input box is nested in 4 other components which includes 1 higher Order component (see below)
Page component
header(Modify)
InputForm
When I move the form code to the Page component it works.
How I keep the components separate (and reusable) and have the functionlity I need?
The form code is below
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
The full code for the entiure component is here
mport React, { Fragment } from 'react';
const InputForm = (props) => {
//prepare props
console.log('currentObject', props.currentObject);
const { editorState, stateModifier, userFunctions } = props.editorEssentials;
// const urlFormVisible = props.urlFormVisible;
//Styles
const componentStyle = 'container-flex-column';
// console.log('MOdify: currentState', editorState);
// console.log('MOdify: targetObject', currentObject);
// console.log('MOdify: stateModifier', stateModifier);
console.log('currentObject.name', props.currentObject.name);
return (
<Fragment>
<form
className={componentStyle}
// onSubmit={(event) =>
// userFunctions.submitItem(editorState, currentObject, stateModifier, event)
// }
>
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
{props.urlFormVisible && (
<input
type='url'
value={props.currentObject.url}
onChange={(event) =>
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'url'
)
}
/>
)}
</form>
</Fragment>
);
};
export default InputForm;
The function operates on the state and is bound in the master component
WHAT I HAVE TRIED
There are some similar posts on stack overflow but they do not seem to answer my problem.
Changed the Key value in the input (although my original version of this had a no ket defined)
Double checked that the modifyItem function is bound correctly - it looks like it is and I guess if it wasn't the state would not update at all
Tried simplifing code to reduce the number of functions needed to run
MOVING THE COMPONENT THE CODE HAS MADE
I am not sure why but the Higher order component was the problem
when I changed the config from
Page component
header(Modify)
InputForm
to
Page component
header
Modify
InputForm
/header
It worked
Any ideas why I had this problem?

Send selected options from react dual listbox with post request

I'm trying to implement in my react app, two react double listbox in my component. At the moment the listboxes are filled automatically after a get request when component mounts. I need some help on how to get the selected options in each double listbox and send them to the server as json data.
I need two arrays from these lists.
This is my dual listbox classes:
import React from 'react';
import DualListBox from 'react-dual-listbox';
import 'react-dual-listbox/lib/react-dual-listbox.css';
import 'font-awesome/css/font-awesome.min.css';
export class FirstList extends React.Component {
state = {
selected: [],
};
onChange = (selected) => {
this.setState({ selected });
};
render() {
const { selected } = this.state;
return (
<DualListBox
canFilter
filterPlaceholder={this.props.placeholder || 'Search From List 1...'}
options={this.props.options}
selected={selected}
onChange={this.onChange}
/>
);
}
}
export class SecondList extends React.Component {
state = {
selected: [],
};
onChange = (selected) => {
this.setState({ selected });
};
render() {
const { selected } = this.state;
return (
<DualListBox
canFilter
filterPlaceholder={this.props.placeholder || 'Search From List 2...'}
options={this.props.options}
selected={selected}
onChange={this.onChange}
/>
);
}
}
In my component I started importing this:
import React, { useState, useEffect } from 'react'
import LoadingSpinner from '../shared/ui-elements/LoadingSpinner';
import ErrorModal from '../shared/ui-elements/ErrorModal';
import { FirstList, SecondList } from '../shared/formElements/DualListBox';
import { useHttpClient } from '../shared/hooks/http-hook';
const MyComponent = () => {
const { isLoading, error, sendRequest, clearError } = useHttpClient();
const [loadedRecords, setLoadedRecords] = useState();
useEffect(() => {
const fetchRecords = async () => {
try {
const responseData = await sendRequest(
process.env.REACT_APP_BACKEND_URL + '/components/get'
);
setLoadedRecords(responseData)
} catch (err) { }
};
fetchRecords();
}, [sendRequest]);
...
...
return (
<React.Fragment>
<ErrorModal error={error} onClear={clearError} />
<form>
<div className="container">
<div className="row">
<div className="col-md-6">
<fieldset name="SerialField" className="border p-4">
<legend className="scheduler-border"></legend>
<div className="container">
<p>SERIALS</p>
{loadedRecords ? (
<FirstList id='Serials' options={loadedRecords.firstRecordsList} />
) : (
<div>
<label>List is loading, please wait...</label>
{isLoading && <LoadingSpinner />}
</div>
)}
</div>
</fieldset>
</div>
<div className="col-md-6">
<fieldset name="SystemsField" className="border p-4">
<legend className="scheduler-border"></legend>
<div className="container">
<p>SYSTEMS</p>
{loadedRecords ? (
<SecondList options={loadedRecords.secondRecordsList} />
) : (
<div>
<label>List is loading, please wait...</label>
{isLoading && <LoadingSpinner />}
</div>
)}
</div>
</fieldset>
</div>
...
...
If anyone could guide me it'll be much appreciated.
Thanks in advance!
FirstList and SecondList are using internal state to show the selected values. Since a parent component should do the server request, it needs access to this data. This can be achieved by a variety of options:
Let the parent component (MyComponent) handle the state completely. FirstList and SecondList would need two props: One for the currently selected values and another for the onChange event. MyComponent needs to manage that state. For example:
const MyComponent = () => {
const [firstListSelected, setFirstListSelected] = useState();
const [secondListSelected, setSecondListSelected] = useState();
...
return (
...
<FirstList options={...} selected={firstListSelected} onChange={setFirstListSelected} />
...
<SecondList options={...} selected={secondListSelected} onChange={setSecondListSelected} />
...
)
Provide only the onChange event and keep track of it. This would be very similar to the first approach, but the lists would keep managing their state internally and only notify the parent when a change happens through onChange. I usually don't use that approach since it feels like I'm managing the state of something twice and I also need to know the initial state of the two *List components to make sure I am always synchronized properly.
Use a ref, call an imperative handle when needed from the parent. I wouldn't recommend this as it's usually not done like this and it's getting harder to share the state somewhere else than inside of the then heavily coupled components.
Use an external, shared state like Redux or Unstated. With global state, the current state can be reused anywhere in the Application and it might even exist when the user clicks away / unmounts MyComponent. Additional server requests wouldn't be necessary if the user navigated away and came back to the component. Anyways, using an external global state needs additional setup and usually feels "too much" and like a very high-end solution that is probably not necessary in this specific case.
By using option 1 or 2 there is a notification for the parent component when something changed. On every change a server request could be sent (might even be debounced). Or there could be a Submit button which has a callback that sends the saved state to the server.

REACT: Should HTML forms be Controlled or Uncontrolled components?

I am having a dilemma as to where Where My Form State Should Live.
React Docs mention:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy). Either the common
owner or another component higher up in the hierarchy should own the
state. If you can’t find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
I have a simple comment UI.
The user enters a comment in the text area.
The user enters name in input field.
The user clicks post and the comment is displayed below.
Components Highrarchy is as follows:
- CommentSection.jsx ---> main (should "Post" state (?))
-comment-post.jsx ---> User Comment Form
- comment-table.jsx
- comment.jsx
- comment-avatar.jsx
- comment-body.jsx
Issue: In comment-post.jsx the input and textarea fields have onChange() event handler, and there is also a click event handle on submit post button. I can choose to do one of two:
In comment-post.jsx when onChange is trigger send the commentBody to CommentSection.jsx The issue here would be I will be sending the commentBody as soon as user types, then sending name later when it triggers and so on
In comment-post.jsx when onChange is trigger SAVE the value in state, then name the name field in the state, when user clicks submit button send to CommentSection.jsx Benefit is Fields are set first in state, and when the user clicks post they are sent to the parent (As it should be right?)
Should the input fields of the comment i.e. commentBody and Name be
saved in the comment-post.jsx (form component) or the parent?
Right now the state I am doing the 2. Saving form fields in state and then sending the state values on submit.
I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?
class CommentSection extends Component {
state = {
posts: []
};
handleCommentPost = post => {
const posts = this.state.posts;
posts.push(post);
this.setState({ posts });
};
render() {
console.log("comment:", this.state.posts);
return (
<React.Fragment>
<h1>comments</h1>
<div className="row bootstrap snippets">
<div className="col-md-6 col-md-offset-2 col-sm-12">
<div className="comment-wrapper">
<Comment_Post onClick={this.handleCommentPost} />
<Comment_Table comments={this.state.posts} />
</div>
</div>
</div>
</React.Fragment>
);
}
}
class Comment_Table extends Component {
render() {
const posts = this.props.comments;
let count = 0;
return (
<div className="panel panel-info">
<hr />
<ul className="media-list">
{posts.map(post => (
<Comment key={count++} comment={post.commentBody} />
))}
</ul>
</div>
);
}
}
class Comment extends Component {
render() {
return (
<li className="media">
<Comment_Avatar userAvatar={this.props.commentAvatar} />
<Comment_Body userComment={this.props.comment} />
</li>
);
}
}
class Comment_Body extends Component {
render() {
const { userComment } = this.props;
return (
<div className="media-body">
<span className="text-muted pull-right">
<small className="text-muted">30 min ago</small>
</span>
<strong className="text-success">#MartinoMont</strong>
<p>
{userComment}
{}
</p>
</div>
);
}
}
class Comment_Post extends Component {
state = {
commentBody: null
};
onCommentChange = e => {
this.setState({ commentBody: e.target.value });
};
onCommentPost = e => {
const commentBody = this.state.commentBody;
if (commentBody !== null) {
this.props.onClick({ commentBody });
}
};
onNameInput = e => {};
onCommentPostError() {}
render() {
return (
<React.Fragment>
<div className="panel-heading p-heading">Comment panel</div>
<div className="panel-body">
<textarea
onChange={this.onCommentChange}
className="form-control"
placeholder="write a comment..."
rows="3"
/>
<label htmlFor="fname" onChange={this.onNameInput}>
Name:{" "}
</label>
<input id="fname" placeholder="John" />
<br />
<button
type="button"
className="btn btn-info pull-right"
onClick={this.onCommentPost}
>
Post
</button>
<div className="clearfix" />
</div>
</React.Fragment>
);
}
}
class Comment_Avatar extends Component {
render() {
return (
<a href="#" className="pull-left">
<img
src="https://bootdey.com/img/Content/user_1.jpg"
alt=""
className="img-circle"
/>
</a>
);
}
}
I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?
There are two types of Forms that we use in standard User interface design. First is when any change in the Input saves the change. Second, when after making changes to the Form Elements, you press a submit button and then the changes are saved.
Since you have implemented the second type, your onChange should deal with controlling your TextArea state and your onClick should deal with the submit. So your code is just fine.
I am having a dilemma as to where Where My Form State Should Live.
That depends...In your case you only have one Form, and two Form Elements none of which are re-usable. So these uncontrolled Forms work well for you. However, if you wanted to have a Form Component that is re-usable, or if you wanted to have a Form with 15 Fields in it, you would not want to write a separate onChange handler for each one of them. For this, you would want to make a controlled Form Component that can handle all these things for you. Here's an example of what that would look like.
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
values: {}
};
}
#boundMethod
handleSubmit(event) {
event.preventDefault();
this.props.submit(this.state.values);
}
#boundMethod
handleChange(event) {
const { name, value } = event.target;
const newValues = Object.assign(
{ ...this.state.values },
{ [name]: value }
);
this.setState({
values: newValues
});
}
public render() {
const { values } = this.state;
return (
<form onSubmit={this.handleSubmit} noValidate={true}>
<div>
{React.Children.map(
this.props.children,
child => (
{React.cloneElement(child, {
value: values[child.props.name],
onChange: this.handleChange
})}
)
)}
<div>
<button type="submit">
Submit
</button>
</div>
</div>
</form>
);
}
}
Then you will be able to use this Form class like so:
<Form
submit={values => {
/* work with values */
}}
>
<input type="hidden" name="name" />
<input type="hidden" name="rating" />
</Form>;

Cant get a text box state to refresh with user input, keeps going to default state despite using setState

I have a React file which displays a list of city data as a component. there is an input textbox above it which needs to accept user input. i am using state to display an initial string in the textbox, but i cannot get onChange to successfully use a function to setState. troubleshooting it with console.log i can see that when i attempt to change the state the function i am pointing to with onChange does work and changes one letter, but then the state snaps back to its default value. the problem seems to be with setState not saving the change and reverting back to the initial state after any changes are made. the text box content appears to not change at all, thought console.log shows a one letter change but then reverts back to the original state.
how do i update state? i want the user to be able to punch a number in and then compare it with the list.
import React, {Component} from 'react';
import Table from './Table';
import cities from './Cities';
class App extends Component {
state = {
userInput: "Your City Population"
}
popChanger = (event) => {
this.setState( {userInput: event.target.value} );
//console.log(event.target.value);
}
yourCity = (
<div>
<input
type='text'
onChange={this.popChanger}
value={this.state.userInput}
/>
</div>
)
render() {
return (
<div className = "App">
{this.yourCity}
<Table characterData = {cities} />
</div>
);
}
}
export default App;
setState() is saving your changes, just not in the right place,
popChanger() is an arrow function and updates the state of the App component,
yourCity has it's own this so it doesn't know about the App state.
you can either cahnge yourCity to an arrow function that returns the html you want like
class TodoApp extends React.Component {
state = {
a: ''
};
YourCity = () => (
<div>
<input type="text" onChange={this.handleChange} value={this.state.a} />
</div>
}
handleChange = e => this.setState({a : e.target.value})
render() {
return (
<div>
<this.YourCity />
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Or, create yourCity component outside and pass the handleChange as a prop :
const YourCity = props => (
<div>
<input type="text" onChange={props.handleChange} value={props.value} />
</div>
)
class TodoApp extends React.Component {
state = {
a: ''
};
handleChange = e => this.setState({a : e.target.value})
render() {
return (
<div>
<YourCity handleChange={this.handleChange} value={this.state.a}/>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
The state is updating but you can't see that because this.yourCity doesn't re-render
popChanger = (event) => {
this.setState( {userInput: event.target.value} );
console.log(event.target.value);
}
yourCity(){
return <div>
<input
type='text'
onChange={this.popChanger}
value={this.state.userInput}
/>
</div>
}
render() {
return (
<div className = "App">
{this.yourCity()}
</div>
);
}
}

Categories