I have a react component and I want to pass down onChange as a prop to a child component, I'm using atomic design, here's my code:
HeadComponent.js
class Headcomponent extends React.Component{
constructor (props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
}
handleUserInput = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},
() => { this.validateField(name, value) });
}
render(){
const fields= [
{
label: 'hhdghghd',
placeholder: 'fhfhhfhh 1',
ExampleMessage: this.state.formErrors.email ,
ErrorMessage: 'error message for input 1',
inputValue: this.state.email,
onChange: this.handleUserInput //this is where I'm passing my function
},
{
label: 'fffff',
placeholder: 'tttttt 2',
ExampleMessage: 'example message for second label',
ErrorMessage: 'error message for input 2',
onChange: this.handleUserInput
},
]
return (
<div>
<Form fields={fields} buttonText="Submit"/>
</div>
);
}
}
export default Headcomponent;
Form.js
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (<LabeledInput label={field.label}
placeholder={field.placeholder}
ExampleMessage={field.ExampleMessage}
ErrorMessage={field.ErrorMessage}
onChange= {(e)=> field.onChange}
inputValue= {field.inputValue}
key={i}
passwordError={props.passwordError}
/>))
}
<Button text={props.buttonText} />
</form>
);
Form.propTypes = {
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonText: PropTypes.string.isRequired,
};
export default Form;
LabeledInput
const LabeledInput = props => (
<div className={`form-group `} >
<Label text={props.label} />
<Input inputValue={props.inputValue} placeholder={props.placeholder} type="text" onChange={(e)=> props.onChange} />
<ErrorMessage text={props.ErrorMessage} />
<ExampleMessage ExampleMessage={ props.ExampleMessage} />
</div>
);
LabeledInput.propTypes = {
label: PropTypes.string.isRequired,
placeholder: PropTypes.string,
onChange: PropTypes.func.isRequired,
//value: PropTypes.string.isRequired,
exampleText: PropTypes.string,
};
export default LabeledInput;
Input.js
const Input = props => (
<input type={props.type} class="form-control form-control-success is-valid" placeholder={props.placeholder} value={props.inputValue} className="form-control form-control-success"
onChange={ (e)=> props.onChange } />
);
Input.propTypes = {
inputValue: PropTypes.string,
type: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
export default Input;
How to pass handleUserInput from HeadComponent.js down to Input.js, so far using props I can't get it to trigger while changing text.
You forgot to actually call field.onChange method. Instead of :
onChange= {(e)=> field.onChange}
You can change it to:
onChange= {field.onChange}
I also noticed that your handleUserInput set state at :
{ [name]: e.target.value }
However, you are not setting name to any of the inputs and that's not gonna work.
Please have a look at my working sample:
const LabeledInput = props => (
<div className="form-group">
<label>{props.label}</label>
<input
className="form-control"
type="text"
placeholder={props.placeholder}
onChange={props.onChange} // That's how you have to call onChange
name={props.name} // You are not setting this prop
/>
<div>{props.ErrorMessage}</div>
<div>{props.ExampleMessage}</div>
</div>
)
const Form = ({ buttonText, fields }) => (
<form className="Form">
{fields.map((field, i) => <LabeledInput key={i} {...field} />)}
<button className="btn btn-primary">{buttonText}</button>
</form>
)
class Headcomponent extends React.Component {
constructor() {
super()
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
this.handleUserInput = this.handleUserInput.bind(this)
}
handleUserInput(e) {
const name = e.target.name
const value = e.target.value
// Now that you have `name` and `value`, state updates are going to work
this.setState({
[name]: value
})
}
render() {
const fields = [
{
label: 'Email',
placeholder: 'email placeholder',
ExampleMessage: this.state.formErrors.email ,
ErrorMessage: 'error message for input 1',
inputValue: this.state.email,
onChange: this.handleUserInput,
name: 'email',
},
{
label: 'Password',
placeholder: 'password placeholder',
ExampleMessage: 'example message for second label',
ErrorMessage: 'error message for input 2',
onChange: this.handleUserInput,
name: 'password',
},
]
return (
<div>
<Form fields={fields} buttonText="Submit"/>
<div style={{ marginTop: 20 }}>
<div>state.email: {this.state.email}</div>
<div>state.password: {this.state.password}</div>
</div>
</div>
)
}
}
ReactDOM.render(
<Headcomponent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<div id="root"></div>
Related
I have is a div that contains three input elements and a remove button. what is required is when the user clicks on the add button it will add this div dynamically and when the user clicks on the remove button it will remove this div. I was able to add one input element (without div container) dynamically with the following method.
create an array in the state variable.
assign a name to the dynamic input field with the help of array indexing like name0, name1
How can I do with these many input fields? The problem grows further when I create this whole div as a separate component. I am using a class-based component.
handleChange=(event) =>
{
this.setState({[event.target.name]:event.target.values});
}
render()
{
return(
<div className="row">
<button type="button" onClick={this.addElement}>Add</button>
<div className="col-md-12 form-group">
<input type="text" className="form-control" name="name" value={this.state.name} onChange={this.handleChange} />
<input type="text" className="form-control" name="email" value={this.state.email} onChange={this.handleChange} />
<input type="text" className="form-control" name="phone" value={this.state.phone} onChange={this.state.phone} />
<button type="button" onClick={this.removeElement}>Remove</button>
</div>
</div>
)
}
I would approach this from a configuration angle as it's a little more scalable. If you want to eventually change across to something like Formik or React Form, it makes the move a little easier.
Have an array of objects that you want to turn into input fields. Your main component should maintain state whether the <Form /> component is showing, and if it's visible pass in the config and any relevant handlers.
Your form component should maintain state for the inputs, and when you submit it, passes up the completed state to the parent.
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
// The only state in the main component
// is whether the form is visible or not
this.state = { visible: false };
}
addForm = () => {
this.setState({ visible: true });
}
removeForm = () => {
this.setState({ visible: false });
}
handleSubmit = (form) => {
console.log(form);
}
render() {
const { visible } = this.state;
const { config } = this.props;
return (
<div>
<button
type="button"
onClick={this.addForm}
>Add form
</button>
{visible && (
<Form
config={config}
handleSubmit={this.handleSubmit}
handleClose={this.removeForm}
/>
)}
</div>
);
}
};
class Form extends Component {
constructor(props) {
super();
this.state = props.config.reduce((acc, c) => {
return { ...acc, [c.name]: '' };
}, {});
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = () => {
this.props.handleSubmit(this.state);
}
render() {
const { name, email, phone } = this.state;
const { handleClose, config } = this.props;
return (
<div onChange={this.handleChange}>
{config.map(input => {
const { id, name, type, required } = input;
return (
<div>
<label>{name}</label>
<input key={id} name={name} type={type} required={required} />
</div>
)
})}
<button type="button" onClick={this.handleSubmit}>Submit form</button>
<button type="button" onClick={handleClose}>Remove form</button>
</div>
);
}
}
const config = [
{ id: 1, name: 'name', type: 'text', required: true },
{ id: 2, name: 'email', type: 'email', required: true },
{ id: 3, name: 'phone', type: 'phone', required: true }
];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
input { display: block; }
label { text-transform: capitalize; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I hope this would be help for your question.
I made a child component which have three input tags.
// parent component
import React, { Component } from "react";
import TextField from "./TextField";
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: [
{
key: Date.now(),
name: "",
email: "",
phone: ""
}
]
};
}
onChange = (inputUser) => {
this.setState((prevState) => {
const newUsers = prevState.users.map((element) => {
if (element.key === inputUser.key) return inputUser;
return element;
});
return { users: newUsers };
});
};
addElement = () => {
const { name, email, phone } = this.state;
this.setState((prevState) => ({
users: prevState.users.concat({
key: Date.now(),
name,
email,
phone
})
}));
};
removeElement = (id) => {
this.setState((prevState) => ({
users: prevState.users.filter((user) => user.key !== id)
}));
};
render() {
const { users } = this.state;
return (
<div className="row">
<button type="button" onClick={this.addElement}>
Add
</button>
<div className="col-md-12 form-group">
{users.map((user) => (
<React.Fragment key={user.key}>
<TextField
value={user}
onChange={(inputUser) => this.onChange(inputUser)}
/>
<button
type="button"
onClick={() => this.removeElement(user.key)}
disabled={users.length <= 1}
>
Remove
</button>
</React.Fragment>
))}
</div>
</div>
);
}
}
export default App;
// child component
import { Component } from "react";
class TextField extends Component {
handleChange = (ev) => {
const { name, value } = ev.target;
this.props.onChange({
...this.props.value,
[name]: value
});
};
render() {
const { value: user } = this.props;
return (
<>
<input
className="form-control"
name="name"
value={user.name}
onChange={this.handleChange}
placeholder="name"
type="text"
/>
<input
className="form-control"
name="email"
value={user.email}
onChange={this.handleChange}
placeholder="email"
type="text"
/>
<input
className="form-control"
name="phone"
value={user.phone}
onChange={this.handleChange}
placeholder="phone"
type="text"
/>
</>
);
}
}
export default TextField;
You can also check the code in codesandbox link below.
https://codesandbox.io/s/suspicious-heisenberg-xzchm
It was a little difficult to write down every detail of how to generate what you want. So I find it much easier to ready a stackblitz link for you to see how is it going to do this and the link is ready below:
generating a dynamic div adding and removing by handling inputs state value
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
// The only state in the main component
// is whether the form is visible or not
this.state = { visible: false };
}
addForm = () => {
this.setState({ visible: true });
}
removeForm = () => {
this.setState({ visible: false });
}
handleSubmit = (form) => {
console.log(form);
}
render() {
const { visible } = this.state;
const { config } = this.props;
return (
<div>
<button
type="button"
onClick={this.addForm}
>Add form
</button>
{visible && (
<Form
config={config}
handleSubmit={this.handleSubmit}
handleClose={this.removeForm}
/>
)}
</div>
);
}
};
class Form extends Component {
constructor(props) {
super();
this.state = props.config.reduce((acc, c) => {
return { ...acc, [c.name]: '' };
}, {});
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = () => {
this.props.handleSubmit(this.state);
}
render() {
const { name, email, phone } = this.state;
const { handleClose, config } = this.props;
return (
<div onChange={this.handleChange}>
{config.map(input => {
const { id, name, type, required } = input;
return (
<div>
<label>{name}</label>
<input key={id} name={name} type={type} required={required} />
</div>
)
})}
<button type="button" onClick={this.handleSubmit}>Submit form</button>
<button type="button" onClick={handleClose}>Remove form</button>
</div>
);
}
}
const config = [
{ id: 1, name: 'name', type: 'text', required: true },
{ id: 2, name: 'email', type: 'email', required: true },
{ id: 3, name: 'phone', type: 'phone', required: true }
];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
input { display: block; }
label { text-transform: capitalize; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I have a react code
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import SelectBox from '../SelectBox';
import Icon from '../Icon';
import Input from '../Input';
import CountryItem from './components/CountryItem';
const PhoneNumber = props => {
const { children, className } = props;
const [selectedOption, setSelectedOption] = useState('');
const [inputParam, setInputParam] = useState('');
const inputParamChangeHandler = event => {
const inputChar = event.target.value;
setInputParam(inputChar);
console.log({ inputChar });
};
const selectedOptionChangeHandler = event => {
const valueSelected = event.target.value;
setSelectedOption(valueSelected);
console.log({ valueSelected });
};
console.log({ children });
const childrenWithPropsFromParent = React.Children.map(children, child => child);
const optionsWithImage = [
{
value: '+880',
label: <CountryItem />,
},
{
value: '+55',
label: (
<span>
<span className="phone-number__country-flag">
<Icon name="home" />
</span>
<span>
<span className="phone-number__country-name">Brazil</span>
<span className="phone-number__country-code">(+55)</span>
</span>
</span>
),
},
{
value: '+40',
label: (
<span>
<span className="phone-number__country-flag">
<Icon name="home" />
</span>
<span>
<span className="phone-number__country-name">Romania</span>
<span className="phone-number__country-code">(+40)</span>
</span>
</span>
),
},
{
value: '+44',
label: (
<span>
<span className="phone-number__country-flag">
<Icon name="home" />
</span>
<span>
<span className="phone-number__country-name">United Kingdom</span>
<span className="phone-number__country-code">(+44)</span>
</span>
</span>
),
},
{
value: '+1',
label: (
<span>
<span className="phone-number__country-flag">
<Icon name="home" />
</span>
<span>
<span className="phone-number__country-name">Bahamas</span>
<span className="phone-number__country-code">(+1)</span>
</span>
</span>
),
},
];
return (
<div className={classNames('phone-number', className)}>
<div>Phone Number</div>
<div className="phone-number__wrapper">
<SelectBox
size="small"
value={selectedOption}
touched={false}
onChange={selectedOptionChangeHandler}
isSearchable={false}
isDisabled={false}
isClearable={false}
options={optionsWithImage}
width="small"
/>
<Input value={inputParam} onChange={inputParamChangeHandler} placeholder="XXXX XXX XXX" />
</div>
{/* {childrenWithPropsFromParent} */}
</div>
);
};
PhoneNumber.defaultProps = {
children: null,
className: null,
};
PhoneNumber.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
};
export default PhoneNumber;
SelectBox Component:
import React, { Component } from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import Select from 'react-select';
import CreatableSelect from 'react-select/creatable';
import './select-box.styles.scss';
import colorOptions from '../../helpers/colorOptions';
import Tooltip from '../Tooltip';
import Icon from '../Icon';
const sizeBasedSelector = (element, size) => {
switch (size) {
case 'tiny':
return `select-box__${element}--tiny`;
case 'small':
return `select-box__${element}--small`;
case 'large':
return `select-box__${element}--large`;
default:
return null;
}
};
class SelectBox extends Component {
constructor(props) {
super(props);
this.state = {
selectedOption: props.value,
};
}
customTheme = theme => {
const { primary, primary75, primary50, primary25 } = this.props.colors;
return {
...theme,
colors: {
...theme.colors,
primary,
primary75,
primary50,
primary25,
},
};
};
renderPlaceHolder = () => {
const { preIcon, placeholderText, size } = this.props;
return (
<div className="select-box__placeholder">
{preIcon && (
<span className={classnames('select-box__pre-icon', sizeBasedSelector('pre-icon', size))}>
{preIcon}
</span>
)}
<span
className={classnames(
'select-box__placeholder-text',
sizeBasedSelector('placeholder-text', size)
)}
>
{placeholderText}
</span>
</div>
);
};
handleChange = selectedOption => {
this.setState({ selectedOption });
this.props.onChange(selectedOption);
};
handleInputChange = inputValue => {
this.props.onInputChange(inputValue);
};
getFlags = () => {
const { isClearable, isDisabled, isMulti, isSearchable } = this.props;
return {
isClearable,
isDisabled,
isMulti,
isSearchable,
};
};
render() {
const { selectedOption } = this.state;
const {
autoFocus,
className,
classNamePrefix,
colors,
errorMsg,
label,
name,
options,
size,
touched,
isMulti,
isDisabled,
isCreatable,
width,
required,
} = this.props;
const hasError = touched && errorMsg;
const selectProps = {
...this.getFlags(),
autoFocus,
className: classnames(
`${className} ${className}--${size}`,
sizeBasedSelector('width', width),
{
'select-box-container--notMulti': !isMulti,
'select-box-container--error': hasError,
}
),
classNamePrefix,
colors,
theme: this.customTheme,
value: selectedOption,
name,
options,
onInputChange: this.handleInputChange,
onChange: this.handleChange,
placeholder: this.renderPlaceHolder(),
required,
};
return (
<>
{label && (
<span
className={classnames(`select-box__label select-box__label--${size}`, {
'select-box__label--required': required,
'select-box__label--disabled': isDisabled,
})}
>
{label}
</span>
)}
<div className="select-box-wrapper">
{isCreatable ? <CreatableSelect {...selectProps} /> : <Select {...selectProps} />}
{errorMsg && (
<Tooltip
classNameForWrapper="select-box__tooltip-wrapper"
classNameForTooltip="select-box__tooltip"
content={errorMsg}
position="bottom-right"
gap={0}
>
<Icon name="invalid" />
</Tooltip>
)}
</div>
</>
);
}
}
SelectBox.defaultProps = {
autoFocus: false,
className: 'select-box-container',
classNamePrefix: 'select-box',
colors: {
primary: colorOptions.coolGray20,
primary75: colorOptions.coolGray45,
primary50: colorOptions.coolGray70,
primary25: colorOptions.coolGray95,
},
errorMsg: '',
isClearable: true,
isCreatable: false,
isDisabled: false,
isMulti: false,
isSearchable: true,
label: '',
name: 'select-box',
onChange: () => {},
onInputChange: () => {},
options: [],
placeholderText: 'Select...',
preIcon: null,
required: false,
size: 'small',
touched: false,
value: null,
width: 'small',
};
SelectBox.propTypes = {
autoFocus: PropTypes.bool,
className: PropTypes.string,
classNamePrefix: PropTypes.string,
colors: PropTypes.shape({
primary: PropTypes.string,
primary75: PropTypes.string,
primary50: PropTypes.string,
primary25: PropTypes.string,
}),
errorMsg: PropTypes.string,
isClearable: PropTypes.bool,
isCreatable: PropTypes.bool,
isDisabled: PropTypes.bool,
isMulti: PropTypes.bool,
isSearchable: PropTypes.bool,
label: PropTypes.string,
name: PropTypes.string,
onChange: PropTypes.func,
onInputChange: PropTypes.func,
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string,
})
),
preIcon: PropTypes.node,
placeholderText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
value: PropTypes.oneOf([
PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
})
),
]),
required: PropTypes.bool,
size: PropTypes.oneOf(['tiny', 'small', 'large']),
touched: PropTypes.bool,
width: PropTypes.oneOf(['tiny', 'small', 'large', 'full']),
};
export default SelectBox;
now I want to take the value of the selectedOption from the SelectBox component but this current code base giving me error while I select a option from the SelectBox. The error is
Uncaught TypeError: Cannot read property 'value' of undefined
at Object.selectedOptionChangeHandler [as onChange] (PhoneNumber.jsx:22)
at Object.onChange (SelectBox.jsx:71)
at StateManager.callProp (stateManager-04f734a2.browser.esm.js:98)
at StateManager._this.onChange (stateManager-04f734a2.browser.esm.js:38)
at Select._this.onChange (Select-9fdb8cd0.browser.esm.js:1079)
at Select._this.setValue (Select-9fdb8cd0.browser.esm.js:1106)
at Select._this.selectOption (Select-9fdb8cd0.browser.esm.js:1155)
at onSelect (Select-9fdb8cd0.browser.esm.js:1732)
at HTMLUnknownElement.callCallback (react-dom.development.js:336)
at Object.invokeGuardedCallbackDev (react-dom.development.js:385)
How to get the value from onChange from the SelectBox using the current implementation?
Your SelectBox component's handleChange callback outputs the selected value versus an event, so you should consume the value.
SelectBox
handleChange = selectedOption => {
this.setState({ selectedOption });
this.props.onChange(selectedOption); // <-- sends selected value!
};
PhoneNumber
const selectedOptionChangeHandler = valueSelected => { // <-- consume valueSelected
setSelectedOption(valueSelected);
console.log({ valueSelected });
};
I have a form that uses a child component with input fields. I have used props to get the data from the parent component, but it's not adding the prop value to the input field. There is no errors in the console.
Parent component:
const {
address,
errors
} = this.state;
return (
<form noValidate autoComplete="off" onSubmit={this.onSubmit.bind(this)}>
<LocationInputGroup
errors={errors}
address={address}
/>
<Button
type="submit"
style={{ marginRight: 10, marginTop: 20 }}
variant="callToAction"
> Submit</Button>
</form>
);
Child component:
constructor(props) {
super(props);
this.state = {
address: "",
errors: {}
};
}
componentDidMount() {
this.setState({
errors: this.props.errors,
address: this.props.address
});
}
render() {
const {
address,
errors
} = this.state;
return (
<div>
<InputGroup
value={address}
error={errors.address}
label="Address"
name={"address"}
onChange={e => this.setState({ address: e.target.value })}
placeholder={"Address"}
/>
</div>
);
}
InputGroup component:
class InputGroup extends Component {
constructor(props) {
super(props);
}
//TODO check if scrolling still changes with number inputs
//Bug was in Chrome 73 https://www.chromestatus.com/features/6662647093133312
//If it's no longer a bug these listeners can be removed and the component changed back to a stateless component
handleWheel = e => e.preventDefault();
componentDidMount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).addEventListener("wheel", this.handleWheel);
}
}
componentWillUnmount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).removeEventListener("wheel", this.handleWheel);
}
}
render() {
const {
disabled,
classes,
error,
value,
name,
label,
placeholder,
type,
isSearch,
onChange,
onBlur,
onFocus,
multiline,
autoFocus,
InputProps = {},
autoComplete,
allowNegative,
labelProps
} = this.props;
if (type === "phone") {
InputProps.inputComponent = PhoneNumberInputMask;
}
let onChangeEvent = onChange;
//Stop them from entering negative numbers unless they explicitly allow them
if (type === "number" && !allowNegative) {
onChangeEvent = e => {
const numberString = e.target.value;
if (!isNaN(numberString) && Number(numberString) >= 0) {
onChange(e);
}
};
}
return (
<FormControl
className={classes.formControl}
error
aria-describedby={`%${name}-error-text`}
>
<FormatInputLabel {...labelProps}>{label}</FormatInputLabel>
<TextField
error={!!error}
id={name}
type={type}
value={value}
onChange={onChangeEvent}
margin="normal"
onBlur={onBlur}
onFocus={onFocus}
InputProps={{
...InputProps,
classes: {
input: classnames({
[classes.input]: true,
[classes.searchInput]: isSearch
})
}
}}
placeholder={placeholder}
multiline={multiline}
autoFocus={autoFocus}
disabled={disabled}
autoComplete={autoComplete}
onWheel={e => e.preventDefault()}
/>
<FormHelperText
className={classes.errorHelperText}
id={`${name}-error-text`}
>
{error}
</FormHelperText>
</FormControl>
);
}
}
InputGroup.defaultProps = {
value: "",
type: "text",
labelProps: {}
};
InputGroup.propTypes = {
error: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string.isRequired,
label: PropTypes.string,
placeholder: PropTypes.string,
type: PropTypes.string,
isSearch: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
multiline: PropTypes.bool,
autoFocus: PropTypes.bool,
InputProps: PropTypes.object,
disabled: PropTypes.bool,
autoComplete: PropTypes.string,
allowNegative: PropTypes.bool,
labelProps: PropTypes.object
};
export default withStyles(styles)(InputGroup);
I hope someone can advise what the issue is and how to overcome it.
Normally when we pass data from parent, we consider it to be Immutable,
(i.e) It should not be changed directly from the child components.
So, what one could do is use the prop value directly from the parent and use a method to mutate or change from the parent.
Below code is self explanatory.
Parent
class Parent {
// parent immutable state
public parentObject: object = {
location: "",
phoneNo: ""
};
constructor() {
this.state = {
parentObject: this.parentObject
}
}
public onParentObjectChangeCallBack(key: string, value: string | number) {
this.state.parentObject[key] = value;
// to rerender
this.setState();
}
public render () {
return <ChildComponent parentObject={this.state.parentObject}
onChange={this.onParentObjectChangeCallBack} />
}
}
Child
class ChildComponent {
#Prop
public parentObject: object;
#Prop
public onChange: Function;
public render() {
<div>
{
this.parentObject.keys.map((key) => {
return <div>
<span> {{key}}</span>
// calls parent method to change the immutable object
<input value={{this.parentObject[key]}} onChange=(val) =>
this.onChange(key, val) />
</div>
})
}
</div>
}
}
Upon submitting the form, I am url encoding the values to then send them in an API request. But when I encode the values, they show up on my form encoded:
Why is it changing the formik values? I'm trying to clone the object which I am encoding in order to not update the formik values but they still update.
Code:
export const uploadAddProductEpic: Epic<*, *, *> = (
action$: ActionsObservable<*>
) =>
action$.ofType(UPLOAD_ADD_PRODUCT).mergeMap((action) => {
const newAction = Object.assign({}, encodeAddProductAction(action))
return ajax
.post('http://192.168.1.12:4000/products', newAction.payload, {
'Content-': 'application/json'
})
.map((response) => uploadAddProductFulfilled(response))
.catch((error) => Observable.of(uploadAddProductRejected(error)))
})
export const handleSubmit = (values, props: AddViewProps): void => {
Toast.show('Uploading Product', {
duration: 3000,
position: 30,
shadow: true,
animation: true,
hideOnPress: true,
delay: 0
})
props.uploadAddProduct(values)
}
export const encodeAddProductAction = (action: VepoAction<Product>) => {
const action1 = Object.assign({}, action)
action1.payload.Shop = encodeURIComponent(
JSON.stringify(action1.payload.Shop)
)
action1.payload.Brand = encodeURIComponent(
JSON.stringify(action1.payload.Brand)
)
action1.payload.Name = encodeURIComponent(
JSON.stringify(action1.payload.Name)
)
action1.payload.Description = encodeURIComponent(
JSON.stringify(action1.payload.Description)
)
return action1
}
const mapStateToProps = (state: RecordOf<VepoState>) => ({
locationListDisplayed: state.formControls.root.locationListDisplayed
})
// eslint-disable-next-line flowtype/no-weak-types
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => ({
uploadAddProduct: (product: Product): void => {
dispatch(uploadAddProduct(product))
}
})
class AddGroceryItemView extends React.Component {
render() {
const {
values,
handleSubmit,
setFieldValue,
errors,
touched,
setFieldTouched,
isValid,
isSubmitting
} = this.props
return (
<Container>
<VepoHeader title={'Add Vegan Grocery Product'} />
<Container style={container}>
<ScrollView
keyboardShouldPersistTaps="always"
style={viewStyle(this.props.locationListDisplayed).scrollView}>
<LocationAutocomplete
label={'Grocery Store'}
placeholder={'Enter Grocery Store'}
setFieldTouched={setFieldTouched}
setFieldValue={setFieldValue}
name="GroceryStore"
required
error={errors.GroceryStore}
touched={touched.GroceryStore}
/>
<View style={viewStyle().detailsContainer}>
<Input
label={'Product Name'}
onTouch={setFieldTouched}
value={values.Name}
placeholder="Enter Name"
name="Name"
required
error={touched.Name && errors.Name}
deleteText={setFieldValue}
onChange={setFieldValue}
/>
<Input
label={'Product Brand'}
value={values.Brand}
onTouch={setFieldTouched}
error={touched.Brand && errors.Brand}
placeholder="Enter Brand"
name="Brand"
required
onChange={setFieldValue}
deleteText={setFieldValue}
/>
<View>
<Input
label={'Product Description'}
value={values.Description}
placeholder="Enter Description"
multiline={true}
required
onTouch={setFieldTouched}
error={touched.Description && errors.Description}
numberOfLines={4}
name="Description"
deleteText={setFieldValue}
onChange={setFieldValue}
/>
<Input
isValid={true}
isPrice={true}
label={'Product Price'}
value={values.Price}
onTouch={setFieldTouched}
error={touched.Price && errors.Price}
placeholder="Enter Price"
name="Price"
deleteText={setFieldValue}
onChange={setFieldValue}
/>
<CategoriesMultiselect.View
error={errors.Categories}
setFieldValue={setFieldValue}
setFieldTouched={setFieldTouched}
touched={touched.Categories}
name="Categories"
required
label="Product Categories"
categoryCodes={[CategoryEnums.CategoryCodes.Grocery]}
/>
<ImagePicker
label="Product Image"
setFieldValue={setFieldValue}
name="Image"
/>
</View>
</View>
</ScrollView>
</Container>
<Button.View
title="submit"
onPress={handleSubmit}
label={'GO!'}
disabled={!isValid || isSubmitting}
loading={isSubmitting}
/>
</Container>
)
}
}
const container = {
flex: 1,
...Spacing.horiz_pad_md_2,
backgroundColor: Colors.grey_lite,
flexDirection: 'column'
}
const formikEnhancer = withFormik({
validationSchema: Yup.object().shape({
Name: Yup.string().required(),
Brand: Yup.string().required(),
GroceryStore: Yup.object()
.shape({
city: Yup.string(),
latitude: Yup.number(),
longitude: Yup.number(),
name: Yup.string(),
place_id: Yup.string(),
street: Yup.string(),
street_number: Yup.string(),
suburb: Yup.string()
})
.required(),
Image: Yup.object().shape({
uri: Yup.string(),
name: Yup.string(),
type: Yup.string()
}),
Categories: Yup.array()
.min(1, 'Please select at least 1 Category')
.required(),
Description: Yup.string()
.min(9)
.required(),
Price: Yup.string().matches(
/^\d+(?:\.\d{2})$/,
'Price must contain 2 decimal places (cents) e.g. 4.00'
)
}),
isInitialValid: false,
mapPropsToValues: () => ({
Name: '',
Brand: '',
Description: '',
Price: '',
Categories: [],
GroceryStore: {},
Image: {}
}),
handleSubmit: (values, { props }) => {
handleSubmit(values, props)
},
displayName: 'AddGroceryItemView'
})(AddGroceryItemView)
I'm new to react js and I need to get the state of the component to be accessed by another class, I encountered this problem because I'm using atomic design because writing everything in one component is turning to be a problem, here is my code:
Headcomponent:
class Headcomponent extends React.Component{
constructor (props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
}
this.setState({formErrors: fieldValidationErrors,
emailValid: emailValid,
passwordValid: passwordValid
}, this.validateForm);
}
validateForm() {
this.setState({formValid: this.state.emailValid &&
this.state.passwordValid});
}
render(){
return (
<Form fields={this.props.form} buttonText="Submit" />
);
}
}
Headcomponent.propTypes = {
form: PropTypes.array,
};
Headcomponent.defaultProps = {
form: [
{
label: 'label1',
placeholder: 'Input 1',
},
{
label: 'label2',
placeholder: 'Placeholder for Input 2',
},
],
};
export default Headcomponent;
form.js
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (<LabeledInput label={field.label} placeholder={field.placeholder} key={i}/>))
}
<Button text={props.buttonText} />
</form>
);
Form.propTypes = {
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonText: PropTypes.string.isRequired,
};
export default Form;
LabeledInput.js (where I need to pass the state of my password)
const LabeledInput = props => (
<div className={`form-group `} >
<Label text={props.label} />
<Input value={props.value} placeholder={props.placeholder} type="text" onChange={props.onChange} />
<div class="valid-feedback">{props.errorMessage}</div>
<small class="form-text text-muted">{props.exampleText}</small>
</div>
);
LabeledInput.propTypes = {
label: PropTypes.string.isRequired,
placeholder: PropTypes.string,
onChange: PropTypes.func.required,
value: PropTypes.string.isRequired,
exampleText: PropTypes.string,
errorMessage: PropTypes.string,
};
export default LabeledInput;
How can I access state of headComponent in LabeledInput?
The most reasonable way is passing it down (The values of Headcomponent's state you need) as props:
Headcomponent.js
class Headcomponent extends React.Component {
constructor (props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
}
render() {
return (
<Form
fields={this.props.form}
formValid={this.state.formValid} // from Headcomponent's state
buttonText="Submit"
/>
);
}
}
Form.js
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (
<LabeledInput
label={field.label}
formValid={props.formValid} // from Headcomponent's state
placeholder={field.placeholder}
key={i}
/>
))
}
<Button text={props.buttonText} />
</form>
);
LabeledInput.js
const LabeledInput = props => (
<div className={`form-group `} >
{ props.formValid && 'This form is valid' } // from Headcomponent's state
<Label text={props.label} />
<Input value={props.value} placeholder={props.placeholder} type="text" onChange={props.onChange} />
<div class="valid-feedback">{props.errorMessage}</div>
<small class="form-text text-muted">{props.exampleText}</small>
</div>
);
So if any time the Headcomponent's state is updated it will be propagated to the LabeledInput component
The simplest way to access the state of headComponent in LabeledInput in to keep passing it down.
If you want to access the value this.state.password from headComponent inside LabeledInput then you pass this this.state.password as a prop to the form component in the render method
render(){
return (
<Form
fields={this.props.form}
buttonText="Submit"
password={this.state.password} />
);
}
This then gives you access to this.state.password as a prop inside the Form component. You then repeat the process and pass it to the LabeledInput component inside form
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (
<LabeledInput
label={field.label}
placeholder={field.placeholder}
key={i}
password={this.props.password}
/>))
}
<Button text={props.buttonText} />
</form>
);
This then gives you access to the value inside the LabeledInput component by calling this.props.password.
You can use props to achieve this.
Root Component:
<div>
<child myState="hello"></child>
</div>
Child Component:
<div>
<child2 myOtherProperty={this.props.myState}></child2>
</div>
Child1 Component:
<div>{this.props.myOtherProperty}</div>
You can also pass functions as property to other components and let them call back the the root component if needed like so:
<div>
<child onChange={this.myOnChangeMethodDefinedInRootComponent.bind(this)}></child>
</div>
this way you can "tell" the root components if something changed inside the children without using Redux
hope this helps
Here is a quick prototype of what you are looking at,
passing state from head component as props to all the way down to label compoent. Changes in label component will modify the state of head component and force to re-render all components.
// Head Component
class HeadCompoent {
constructor() {
this.state = {
password: '',
userName: '',
}
}
handleFieldChange = (key, val) => {
this.setState({
[key]: val,
});
};
render() {
<Form
fields={[{
item: {
value: this.state.password,
type: 'password',
key: 'password'
},
}, {
item: {
value: this.state.userName,
type: 'text',
key: 'userName'
}
}]}
handleFieldChange={this.handleFieldChange}
/>
}
}
// Form Component
const Form = (fields) => (
<div>
{
fields.map(p => <Label {...p} />)
}
</div>);
// Label Component
const Label = ({ type, value, key, handleFieldChange }) => {
const handleChange = (key) => (e) => {
handleFieldChange(key, e.target.value);
};
return (
<input type={type} value={value} key={key} onChange={handleChange(key)} />
);
};
export default headComponent then import it inside LabelledInout
This is the Headcomponent
export default class Headcomponent extends React.Component{
...
}
LabelledInput Component
import Headcomponet from './headComponent.js'
const LabelledInput = (props) => {
return(<Headcomponent />);
}