I have been playing around with reactjs, and building some basic forms all works good but then it became complicated when i tried to break into smaller compoments
i have a simple component that has a form in it called XMLParser, and it has 2 smaller components inside called XMLInput and XMLResult.
XMLResult is pretty straight forward it just passes the value to as the props, but couldnt figure out what is the best approact to use XMLInput i couldnt get the binding to work with the child component, thanks for any pointers.
function EppResult(props) {
const resultXML = props.xmlData;
return <div style={styles.child}>
<textarea style={styles.outputBox} name="resultXML" value={resultXML} readOnly/>
</div>;
}
class EppInput extends Component{
render(){
return <textarea
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
value={this.props.value}
onChange={this.props.handleInputChange}
/>;
}
}
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = {xmlData : ""};
this.state = {resultXML : ""};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput value={this.state.xmlData} onChange={this.handleInputChange} />
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML}/>
</div>
</form>
</div>
);
}
}
I'm seeing a number of problems:
EppInput is the sub-component name, but XMLInput is used in the main component
You pass an onChange prop, but refer to it as
this.props.handleInputChange -- it should be
this.props.onChange
Just a note--I don't know where the styles object is, but I'm going
to assume it's available to all the components
I haven't tested this, but here's a basic cleanup, with some alterations to see some other ways of doing things:
// stateless functional component
const XMLResult = ({ xmlData }) => (
<div style={styles.child}>
<textarea
style={styles.outputBox}
name="resultXML"
value={xmlData}
readOnly
/>
</div>
);
// stateless functional component
// props are passed directly to the child element using the spread operator
const XMLInput = (props) => (
<textarea
{...props}
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
/>
);
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = { xmlData: "", resultXML: "" };
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput
value={this.state.xmlData}
onChange={this.handleInputChange}
/>
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML} />
</div>
</form>
</div>
);
}
}
Related
//App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
formVal: {},
showMaker: true,
showTemplate: false,
uniMakerArr: [],
uniTemplateArr: [],
this.handleChange = this.handleChange.bind(this);
this.onClickMaker = this.onClickMaker.bind(this);
this.onClickTemplate = this.onClickTemplate.bind(this);
this.addUniFn = this.addUniFn.bind(this);
handleChange = (e) => {
let formVal = this.state.formVal;
let name = e.target.name;
let val = e.target.value;
formVal[name] = val;
this.setState({formVal})
};
addUniFn(e) {
const {uniMakerArr, uniTemplateArr} = this.state;
uniMakerArr.push(<UniMaker key={uniqid()} />);
uniTemplateArr.push(<UniTemplate key={uniqid()} />)
this.setState({
uniMakerArr, uniTemplateArr
});
};
onClickMaker(e) {
this.setState({
showMaker: true,
showTemplate: false,
});
};
onClickTemplate(e) {
this.setState({
showMaker: false,
showTemplate: true,
});
};
render() {
const {firstName, lastName} = this.state.formVal;
const {showMaker, showTemplate, uniMakerArr, uniTemplateArr} = this.state;
return (
<div>
<header className="header">
Curriculum Vitae!
</header>
<nav className="nav">
<a onClick={this.onClickMaker}>Maker</a>
<div className='vl'></div>
<a onClick={this.onClickTemplate}>Preview</a>
</nav>
<div>
{showMaker ? <Maker
handleChange={this.handleChange} add={this.addUniFn}
uniMakerState={uniMakerArr} /> : null}
{showTemplate ? <Preview
uniMakerArr={uniMakerArr}
uniTemplateArr={uniTemplateArr}
//state value for object "formVal"
firstName={firstName}
lastName={lastName}/> : null}
</div>
</div>
);
}
}
export default App;
Here is component Maker:
class Maker extends Component {
constructor(props) {
super(props);
}
render() {
const {handleChange, formVal, add, uniMakerState} = this.props;
return(
<div className='maker'>
<p className="headerText">Personal Information</p>
<form className='personalInp'>
<label htmlFor='firstName'></label>
<input type="text" name="firstName" value={formVal} onChange={handleChange} placeholder="First Name"></input>
<label htmlFor='lastName'></label>
<input type="text" name="lastName" value={formVal} onChange={handleChange} placeholder="Last Name"></input>
{uniMakerState.map((component) => {
return <UniMaker formVal={formVal} handleChange={handleChange}
key={component.key} }/>
})}
<button type="button" className="addBtn" onClick={add}>Add</button>
</form>
<div/>
)
}
}
export default Maker;
Here is component Preview:
class Preview extends Component {
constructor(props) {
super(props);
}
render() {
const {firstName, lastName,uniTemplateArr} = this.props;
return (
<div className='template'>
<p className="name">{firstName || 'Your'} {lastName || 'Name'}</p>
{uniTemplateArr.map(component => <UniTemplate />)}
</div>
)
}
}
export default Preview;
So what this basically does is add two-component when clicking the add button to Maker and Preview tabs. These two components are child components to Maker and Preview components. And they are identical to these two components. This means They will add two more HTML input elements and the fields where the values can be shown.
But the problem is when I make changes in the form that is in the Maker tab. It only reads and changes the value for the first two fields that are in the Preview tab. But ignores the other dynamically added fields. How can I make changes to my code so that the dynamically added input fields relate to the other component that was also added dynamically and only changes values to the corresponding input elements to the divs field?
I've tried putting unique keys to the input elements. But I don't know how to use those keys to update the values for the correct fields. Lastly, I'm sorry if the code is not so readable and the question is confusing. This is my first question, and I tried to be as thorough and transparent as possible. Thanks!
I learn React and I faced some problems. I thought I understand controlled components, but well, it seems otherwise. Could you explain me, why after onChange event I receive props for rest Input components? If I want add label that depends on this.pops.name it gets messy (because of props I see in console.log). I would be grateful for explanation.
import React, { Component } from "react";
class Input extends Component {
handleChange = (e) => {
this.props.onInputChange(e);
};
chooseLabel = (props) => {
let { name } = props;
console.log(name);
if (name === "cost") {
return (
<label className="label" htmlFor={name}>
How much do you earn per hour?
</label>
);
} else if (name === "salary") {
return (
<label className="label" htmlFor={name}>
How much do you want to spend?
</label>
);
} else {
return null;
}
};
render() {
console.log("this.props.nevVal", this.props.newVal);
console.log("this.props.name", this.props.name);
return (
<div>
{this.chooseLabel(this.props)}
<input
className="Input"
value={this.props.newVal}
name={this.props.name}
placeholder={this.props.name}
onChange={this.handleChange}
/>
</div>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
salary: "",
cost: "",
tip: ""
};
}
handleInputChange = (e) => {
console.log("E.target.name", e.target.name);
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div className="App">
<Input
name="salary"
newVal={this.state.salary}
onInputChange={this.handleInputChange}
/>
<Input
name="cost"
newVal={this.state.cost}
onInputChange={this.handleInputChange}
/>
<Input
name="tip"
newVal={this.state.tip}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Link to Codesandbox.io
When one of the inputs is changed, you update the state of your App component. And when a state of a component is updated, it re-renders, and all its children too. So all the inputs are re-rendered (and their newVal are logged to the console), even if you change the value of a single input.
I tried to illustrate it in the following snippet :
class Input extends React.Component {
handleChange = (e) => {
this.props.onInputChange(e);
};
chooseLabel = (props) => {
let { name } = props;
// console.log(name);
if (name === "cost") {
return (
<label className="label" htmlFor={name}>
How much do you earn per hour?
</label>
);
} else if (name === "salary") {
return (
<label className="label" htmlFor={name}>
How much do you want to spend?
</label>
);
} else {
return null;
}
};
render() {
console.log(`${this.props.name} input renderd`)
return (
<div>
{this.chooseLabel(this.props)}
<input
className="Input"
value={this.props.newVal}
name={this.props.name}
placeholder={this.props.name}
onChange={this.handleChange}
/>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
salary: "",
cost: "",
tip: ""
};
}
handleInputChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
console.log("App component and all its children rendered :")
return (
<div className="App">
<Input
name="salary"
newVal={this.state.salary}
onInputChange={this.handleInputChange}
/>
<Input
name="cost"
newVal={this.state.cost}
onInputChange={this.handleInputChange}
/>
<Input
name="tip"
newVal={this.state.tip}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Was that your question ?
How can I do a 2 way binding of a variable from the parent component (Form.js), such that changes occurred in the child component (InputText.js) will be updated in the parent component?
Expected result: typing values in the input in InputText.js will update the state of Form.js
In Form.js
render() {
return (
<div>
<InputText
title="Email"
data={this.state.formInputs.email}
/>
<div>
Value: {this.state.formInputs.email} // <-- this no change
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
this.setState({
data: e.target.value
});
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.data} onChange={this.handleKeyChange} /> // <-- type something here
value: ({this.state.data}) // <-- this changed
</div>
)
}
}
You can manage state in the parent component itself instead of managing that on child like this (lifting state up):
In Form.js
constructor(props) {
super(props);
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
const { formInputs } = this.state;
formInputs.email = e.target.value
this.setState({
formInputs: formInputs
});
}
render() {
// destructuring
const { email } = this.state.formInputs;
return (
<div>
<InputText
title="Email"
data={email}
changed={this.handleKeyChange}
/>
<div>
Value: {email}
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
render() {
// destructuring
const { title, data, changed } = this.props;
return (
<div>
<label className="label">{title}</label>
<input type="text" value={data} onChange={changed} />
value: ({data})
</div>
)
}
}
You can also make your InputText.js a functional component instead of class based component as it is stateless now.
Update: (How to reuse the handler method)
You can add another argument to the function which would return the attribute name like this:
keyUpHandler(e, attribute) {
const { formInputs } = this.state;
formInputs[attribute] = e.target.value
this.setState({
formInputs: formInputs
});
}
And from your from you can send it like this:
<input type="text" value={data} onChange={ (event) => changed(event, 'email') } />
This assumes that you have different inputs for each form input or else you can pass that attribute name also from parent in props to the child and use it accordingly.
You would need to lift state up to the parent
parent class would look something like
onChangeHandler(e) {
this.setState({
inputValue: e.target.value // you receive the value from the child to the parent here
})
}
render() {
return (
<div>
<InputText
title="Email"
onChange={this.onChangeHandler}
value={this.state.inputValue}
/>
<div>
Value: {this.state.inputValue}
</div>
</div>
)
}
children class would look something like
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.value} onChange={this.props.onChange} />
value: ({this.state.value})
</div>
)
}
}
You can simply pass a callback from Form.js to InputText and then call that callback in InputText on handleKeyChange
The cursor keeps going to the end. How to keep the cursor position when editing from the the middle of the string?
Code that I am using is:
const rootElement = document.getElementById('root');
class MyFancyForm extends React.Component {
constructor(props) {
super(props);
this.state = {myValue: ""};
}
handleCommaSeparatedChange = event => {
const {value} = event.target;
this.setState({myValue: value});
};
render() {
return(
<form >
<div>
<label>
Cursor position looser
<br />
<input onChange={this.handleCommaSeparatedChange} value={this.state.myValue} />
</label>
</div>
</form>
)
}
}
const element = <MyFancyForm />;
ReactDOM.render(element, rootElement);
Any idea how could I achieve it?
just change value into defaultValue - it worked both in codepen and codesandbox for me
class MyFancyForm extends React.Component {
constructor(props) {
super(props);
this.state = {myValue: ""};
}
handleCommaSeparatedChange = event => {
const {value} = event.target;
this.setState({myValue: value});
};
render() {
return(
<form >
<div>
<label>
Cursor position looser
<br />
<input onChange={this.handleCommaSeparatedChange} defaultValue={this.state.myValue} />
</label>
</div>
</form>
)
}
}
ReactDOM.render(
<MyFancyForm />,
document.getElementById('root')
);
I know it's an old post but this might help someone.
I found this snippet in one github issue and helped me to get around this problem.
onChange={(event) => {
event.persist()
const caretStart = event.target.selectionStart;
const caretEnd = event.target.selectionEnd;
// update the state and reset the caret
this.updateState();
event.target.setSelectionRange(caretStart, caretEnd);
}}
Quote from:
https://github.com/facebook/react/issues/955#issuecomment-469344232
I solved this by creating a TextInput component that wraps <input type="text"> and proxying the value in internal state.
function TextInput({ value, onChange }) {
// Create a proxy value in internal state to prevent the caret from jumping to the end every time the value updates
const [currentValue, setCurrentValue] = useState<string>(value);
useEffect(() => {
setCurrentValue(value);
}, [value]);
return (<input
type="text"
value={currentValue}
onChange={(e) => {
setCurrentValue(e.target.value);
onChange(e.target.value);
}}
/>);
}
Then I use it in a parent component like so. It works well so far.
<TextInput
value={textValue}
onChange={(e) => {
setTextValue(e);
}}
/>
Not able to get values of input type using this.refs...
how to get that values from input type
export class BusinessDetailsForm extends Component {
submitForm(data) {
console.log(this.refs.googleInput.value)
}
}
reder() {
return(
<form onSubmit={this.submitForm}>
<Field type="text"
name="location"
component={GoogleAutoComplete}
id="addressSearchBoxField"
ref="googleInput"
/>
</form>
)
}
}
You should avoid ref="googleInput" as it is now considered legacy. You should instead declare
ref={(googleInput) => { this.googleInput = googleInput }}
Inside of your handler, you can use this.googleInput to reference the element.
Then inside of your submitForm function, you can obtain the text value with
this.googleInput._getText()
String refs are legacy
https://facebook.github.io/react/docs/refs-and-the-dom.html
If you worked with React before, you might be familiar with an older API where the ref attribute is a string, like "textInput", and the DOM node is accessed as this.refs.textInput. We advise against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases. If you're currently using this.refs.textInput to access refs, we recommend the callback pattern instead.
Edit
From React 16.3, the format for creating refs are:
class Component extends React.Component
{
constructor()
{
this.googleInput = React.createRef();
}
render()
{
return
(
<div ref={this.googleInput}>
{/* Details */}
</div>
);
}
}
using ref={ inputRef => this.input = inputRef } is considered legacy now. In React 16.3 onwards, you can use the code below,
class MyForm extends React.Component {
constructor(props) {
//...
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
EDIT: thanks for the comment #stormwild
In case any one is wondering how to implement ref with hooks :
// Import
import React, { useRef } from 'react';
const Component = () => {
// Create Refs
const exampleInput = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const inputTest = exampleInput.current.value;
}
return(
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" ref={exampleInput} />
</label>
<input type="submit" value="Submit" />
</form>
}
getValue: function() {
return this.refs.googleInput.value;
}
I think the more idiomatic way is to use state instead of refs, although it's a little more code in this case since you only have a single input.
export class BusinessDetailsForm extends Component {
constructor(props) {
super(props);
this.state = { googleInput: '' };
this.defaultValue = 'someValue';
this.handleChange = this.handleChange.bind(this);
this.submitForm = this.submitForm.bind(this);
}
handleChange(e) {
const { field, value } = e.target;
this.setState({ [field]: value });
}
submitForm() {
console.log(this.state.googleInput);
}
render() {
return (
<Formsy.Form onSubmit={this.submitForm} id="form_validation">
<Field type="text"
name="googleInput"
onChange={this.handleChange}
component={GoogleAutoComplete}
floatingLabelText="location"
hintText="location"
id="addressSearchBoxField"
defaultValue={this.defaultValue}
onSelectPlace={this.handlePlaceChanged}
validate={[ required ]}
/>
</Formsy.Form>
);
}
}
See https://facebook.github.io/react/docs/forms.html#controlled-components.
Using RN 0.57.8 when tried this.googleInput._getText(), It resulted in error _getText is not a function so i printed this.googleInput in console and found that _getText() is a function inside _root
this.googleInput._root._getText()
this.googleInput._root._lastNativeText - This will return the last state not the current state please be careful while using it.
In 2018 you should write in constructor this:
In constructor of class you should add something like
this.input = React.createRef()
Examples here:
https://reactjs.org/docs/uncontrolled-components.html
I tried the answer above (https://stackoverflow.com/a/52269988/1978448) and found it only worked for me when I put the refs in the state, but not when I just made them properties of the component.
Constructor:
this.state.refs={
fieldName1: React.createRef(),
fieldName2: React.createRef()
};
and in my handleSubmit I create a payload object to post to my server like this:
var payload = {
fieldName1: this.state.refs.fieldName1.current.value,
fieldName2: this.state.refs.fieldName2.current.value,
}
The react docu explains it very well: https://reactjs.org/docs/refs-and-the-dom.html
this is considered legacy:
yourHandleMethod() {
this.googleInput.click();
};
yourRenderCode(){
ref={(googleInput) => { this.googleInput = googleInput }}
};
whereas, this is considered the way to go:
constructor(props){
this.googleInput = React.createRef();
};
yourHandleMethod() {
this.googleInput.current.click();
};
yourRenderCode(){
<yourHTMLElement
ref={this.googleInput}
/>
};
From React 16.2, you can use: React.createRef
See more: https://reactjs.org/docs/refs-and-the-dom.html
1. using ref={ inputRef => this.input = inputRef }
Exam:
import React, { Component } from 'react';
class Search extends Component {
constructor(props) {
super(props);
this.name = React.createRef();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onSearch(`name=${this.name.value}`);
}
render() {
return (
<div>
<input
className="form-control name"
ref={ n => this.name = n }
type="text"
/>
<button className="btn btn-warning" onClick={ this.handleClick }>Search</button>
</div>
);
}
}
export default Search;
ref={ n => this.name = n } Use Callback Refs -> see
Or:
2. this.name.current.focusTextInput()
class Search extends Component {
constructor(props) {
super(props);
this.name = React.createRef();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onSearch(`name=${this.name.current.value}`);
}
render() {
return (
<div>
<input
className="form-control name"
ref={this.name}
type="text"
/>
<button className="btn btn-warning" onClick={ this.handleClick }>Search</button>
</div>
);
}
}
export default Search;
Hope it will help you.