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>
}
}
Related
I am working on an older project. As part of a form the user puts in a text - this text might be longer then the input field is wide. Therefore this field should be stretching over multiple lines to show all its content. It is no problem to make the field bigger, but the content is still only displayed in one line and then cut off.
<div className="small-12 medium-12 large-12">{this.renderCloze()}</div>
<FormikInput
label="Zusatzinformation"
name="checklist.additionalInfo"
readOnly={!!checklist.completed}
rows="2"
type="text"
size="large"
/>
Thats the part where the input field is created. This is the FormikInput:
import { ErrorMessage, Field } from 'formik';
import * as PropTypes from 'prop-types';
import React from 'react';
import FormInput from './FormInput';
export default class FormikInput extends React.Component {
static propTypes = {
component: PropTypes.oneOfType([
PropTypes.element,
PropTypes.elementType,
PropTypes.string,
]),
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
size: PropTypes.string,
};
handleOnChange = (event, onChange) => {
if (!event || !event.target) {
console.warn('FormikInput: empty event', event);
}
onChange && onChange(event);
};
render() {
const { name, component, ...childProps } = this.props;
return (
<Field name={name}>
{({ field }) => (
<FormInput
{...field}
component={component}
errorMessage={<ErrorMessage name={this.props.name} component="div" />}
{...childProps}
onChange={(event) => this.handleOnChange(event, field.onChange)}
/>
)}
</Field>
);
}
}
FormInput.js:
import * as PropTypes from 'prop-types';
import React from 'react';
export default class FormInput extends React.Component {
static propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
component: PropTypes.oneOfType([
PropTypes.element,
PropTypes.elementType,
PropTypes.node,
PropTypes.string,
]),
errorMessage: PropTypes.oneOfType([PropTypes.element, PropTypes.node, PropTypes.string]),
size: PropTypes.string,
value: PropTypes.any,
};
className = () => {
if (this.props.size === 'large') {
return 'small-12 medium-12 large-12';
}
if (this.props.size === 'medium') {
return 'small-12 medium-6 large-6';
}
if (this.props.size === 'third') {
return 'small-12 medium-6 large-4';
}
return this.props.size ? this.props.size : '';
};
id = () => {
if (this.props.id) {
return this.props.id;
}
if (this.props.name) {
return 'FormField__' + this.props.name;
}
return 'FormField__' + Math.random().toString(36);
};
render() {
let { label, errorMessage, component, size, ...childProps } = this.props;
childProps.id = this.id();
if (!component) {
component = 'input';
childProps.type = 'text';
}
const input = React.isValidElement(component)
? React.cloneElement(component, childProps)
: React.createElement(component, childProps);
return (
<div className={this.className()}>
<div className="group">
{input}
<label htmlFor={this.id()}>{this.props.label}</label>
</div>
{typeof this.props.errorMessage === 'string' ? (
<div>{this.props.errorMessage}</div>
) : (
this.props.errorMessage
)}
</div>
);
}
}
I'm trying to create an endEditing function for reactJs,but i have problem
when using an event hooks it calls the event a focusout but it calls the functions declared in all inputs and not only in the input that was selected.
I tried to do this that activates the function of all inputs if I leave only one.
src/components.js
export default function Input({
inputRef,
inputType,
placeholder,
functionOnEndingChange,
functionUpdatedValueRef,
}) {
useEventListener('focusout', functionOnEndingChange);
return (
<InputText
type={inputType}
ref={inputRef}
placeholder={placeholder}
onChange={text => functionUpdatedValueRef(text.target.value)}
/>
);
}
Input.propTypes = {
functionUpdatedValueRef: PropTypes.func,
functionOnEndingChange: PropTypes.func,
inputType: PropTypes.string,
inputRef: PropTypes.func,
placeholder: PropTypes.string,
};
Input.defaultProps = {
functionUpdatedValueRef: () => {},
functionOnEndingChange: () => null,
inputType: 'text',
inputRef: () => {},
placeholder: 'placeholder input:',
};
src/utils/hooksEvent.js
export default function useEventListener(eventName, handler, element = window) {
// Create a ref that stores handler
const savedHandler = useRef();
// Update ref.current value if handler changes.
// This allows our effect below to always get latest handler ...
// ... without us needing to pass it in effect deps array ...
// ... and potentially cause effect to re-run every render.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(
() => {
// Make sure element supports addEventListener
// On
const isSupported = element && element.addEventListener;
if (!isSupported) return;
// Create event listener that calls handler function stored in ref
const eventListener = event => savedHandler.current(event);
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
},
[eventName, element] // Re-run if eventName or element changes
);
}
src/page/testeInput.js
<Column flex={1}>
<AreaInput>
<LabelText name="EMAIL" />
<Input
inputRef={inputEmailRef}
inputType="email"
placeholder="example#example.com"
functionUpdatedValueRef={text => setEmailState(text)}
functionOnEndingChange={() =>
verifyEmail('email', emailState)
}
/>
</AreaInput>
<AreaInput>
<LabelText name="EMAIL2" />
<Input
inputRef={inputEmailRef2}
inputType="email2"
placeholder="example#example.com"
functionUpdatedValueRef={text => setEmailState(text)}
functionOnEndingChange={() =>
verifyEmailTwo('email2', emailState)
}
/>
</AreaInput>
</Column>
by clicking on one of the inputs and leaving the focus it activates both functions
The answer is simple to the problem using onBlur solves the problem
https://reactjs.org/docs/events.html#focus-events
then the component can look like this
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import {
AreaInputIcon,
AreaInput,
Input,
InputFormMask,
AreaIcon,
IconUser,
IconOpenEyes,
IconClosedEyes,
} from './styles';
import { useEventListener } from '../../utils';
export default function InputIcon({
iconName,
button,
functionOnClick,
error,
disabled,
inputRef,
type,
functionUpdatedValueRef,
functionOnEndingChange,
placeholder,
inputMask,
}) {
// useEventListener('focusout', functionOnEndingChange);
function choseIcons(nameParam) {
switch (nameParam) {
case 'user':
return <IconUser />;
case 'passwordOpen':
return <IconOpenEyes />;
case 'passwordClosed':
return <IconClosedEyes />;
default:
return <IconUser />;
}
}
return (
<AreaInputIcon>
<AreaInput error={error}>
{type !== 'mask' ? (
<Input
ref={inputRef}
placeholder={placeholder}
onChange={text => functionUpdatedValueRef(text.target.value)}
onBlur={functionOnEndingChange}
/>
) : (
<InputFormMask
ref={inputRef}
mask={inputMask}
placeholder={placeholder}
onChange={text => functionUpdatedValueRef(text.target.value)}
onBlur={functionOnEndingChange}
/>
)}
</AreaInput>
<AreaIcon
button={button}
onClick={functionOnClick}
error={error}
disabled={disabled}
>
{choseIcons(iconName)}
</AreaIcon>
</AreaInputIcon>
);
}
InputIcon.propTypes = {
iconName: PropTypes.string,
button: PropTypes.bool,
functionOnClick: PropTypes.func,
functionUpdatedValueRef: PropTypes.func,
error: PropTypes.bool,
type: PropTypes.string,
disabled: PropTypes.bool,
inputRef: PropTypes.func,
functionOnEndingChange: PropTypes.func,
inputMask: PropTypes.string,
placeholder: PropTypes.string,
};
InputIcon.defaultProps = {
iconName: 'user',
button: false,
functionOnClick: () => {},
functionUpdatedValueRef: () => {},
error: false,
type: 'common',
disabled: true,
inputRef: () => {},
functionOnEndingChange: () => {},
inputMask: '99/99/99',
placeholder: 'palceholder input:',
};
I'm exploring React and am somewhat confused over lifecycle methods and parent-child communication. Specifically, I'm trying to create a component which wraps a select element and adds an input box when the "Other" option is selected. I have implemented this using getDerivedStateFromProps() but according to the documentation this lifecycle method should rarely be used. Hence my question: is there another pattern I should be aware of and use in this case?
This is my code, the value and options are passed down as props, as is the handleChange() method of the parent component. So when changes are made in the select or input elements, the parent component state is updated first and a new value is passed down through props.value.
export default class SelectOther extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
static getDerivedStateFromProps(props) {
let optionIndex = -1;
for (let i = 0; i < props.options.length; i++) {
if (props.options[i].value === props.value) {
optionIndex = i;
break;
}
}
if (optionIndex > -1) {
return {
selected: props.options[optionIndex].value,
text: "",
showInput: false
};
} else {
return {
selected: "",
text: props.value,
showInput: true
};
}
}
handleChange(e) {
this.props.handleChange({
"target": {
"name": this.props.name,
"value": e.target.value
}
});
}
render() {
return (
<div>
<label>{ this.props.label }</label>
<select name={ this.props.name } value={ this.state.selected } onChange={ this.handleChange }>
{
this.props.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)
}
<option value="">Other</option>
</select>
{
this.state.showInput &&
<div>
<label>{ this.props.label } (specify other)</label>
<input type="text" className="form-control" value={ this.state.text } onChange={ this.handleChange }></input>
</div>
}
</div>
)
}
}
You can simplify by not having SelectOther have any state, here is an example of how you can pass a function that dispatches an action to change values. Because SelectOther is a pure component it won't needlessly re render:
//make this a pure component so it won't re render
const SelectOther = React.memo(function SelectOther({
label,
name,
value,
options,
handleChange,
}) {
console.log('in render',name, value);
const showInput = !options
.map(o => o.value)
.includes(value);
return (
<div>
<label>{label}</label>
<select
name={name}
value={showInput ? '' : value}
onChange={handleChange}
>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
<option value="">Other</option>
</select>
{showInput && (
<div>
<label>{label} (specify other)</label>
<input
type="text"
name={name}
className="form-control"
value={value}
onChange={handleChange}
></input>
</div>
)}
</div>
);
});
const App = () => {
//create options once during App life cycle
const options = React.useMemo(
() => [
{ value: 'one', label: 'one label' },
{ value: 'two', label: 'two label' },
],
[]
);
//create a state to hold input values and provide
// a reducer to create new state based on actions
const [state, dispatch] = React.useReducer(
(state, { type, payload }) => {
//if type of action is change then change the
// payload.name field to payload.value
if (type === 'CHANGE') {
const { name, value } = payload;
return { ...state, [name]: value };
}
return state;
},
//initial state for the inputs
{
my_name: '',
other_input: options[0].value,
}
);
//use React.useCallback to create a callback
// function that doesn't change. This would be
// harder if you used useState instead of useReducer
const handleChange = React.useCallback(
({ target: { name, value } }) => {
dispatch({
type: 'CHANGE',
payload: {
name,
value,
},
});
},
[]
);
return (
<div>
<SelectOther
label="label"
name="my_name"
value={state.my_name}
options={options}
handleChange={handleChange}
/>
<SelectOther
label="other"
name="other_input"
value={state.other_input}
options={options}
handleChange={handleChange}
/>
</div>
);
};
//render app
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I could have uses useState in App but then I have to use useEventCallback or do a useState for every input value. The following documentation comes up with the useEventCallback pattern and then immediately after states that we don’t recommend this pattern so that's why I came up with the useReducer solution instead.
I have a container component where I have few functions, like this two for example:
newPeriodeCallback() {
const {
behandlingFormPrefix, perioder, periodeTyper, utsettelseArsaker,
} = this.props;
const {
uttakNyPeriodeFom, uttakNyPeriodeTom, uttakNyPeriodeType, uttakNyPeriodeAndel, uttakNyPeriodeArsak,
} = this.props.nyPeriode;
const getPeriodeData = (periode, periodeArray) => periodeArray
.filter(({ kode }) => kode === periode);
const periodeObjekt = getPeriodeData(uttakNyPeriodeType, periodeTyper)[0];
const arsakObjekt = getPeriodeData(uttakNyPeriodeArsak, utsettelseArsaker)[0];
const utsettelseÅrsak = arsakObjekt !== undefined ? {
kode: arsakObjekt.kode,
kodeverk: arsakObjekt.kodeverk,
navn: arsakObjekt.navn,
} : {};
const nyPeriode = [{
arbeidstidsprosent: uttakNyPeriodeAndel,
bekreftet: false,
fom: uttakNyPeriodeFom,
saksebehandlersBegrunnelse: null,
tom: uttakNyPeriodeTom,
utsettelseÅrsak,
uttakPeriodeType: {
kode: periodeObjekt.kode,
kodeverk: periodeObjekt.kodeverk,
navn: periodeObjekt.navn,
},
}];
this.props.reduxFormChange(`${behandlingFormPrefix}.UttakInfoPanel`, 'perioder', perioder.concat(nyPeriode)
.sort((a, b) => a.fom > b.fom));
this.setState({ isNyPeriodeFormOpen: !this.state.isNyPeriodeFormOpen });
}
removePeriodCallback(index) {
const { behandlingFormPrefix, perioder } = this.props;
this.props.reduxFormChange(
`${behandlingFormPrefix}.UttakInfoPanel`, 'perioder',
perioder.filter((e, i) => i === index)
.sort((a, b) => a.fom > b.fom),
);
}
I am sending these two functions as callbacks to different components:
<FieldArray
name="perioder"
component={UttakPeriode}
removePeriodCallback={this.removePeriodCallback}
inntektsmelding={inntektsmelding}
/>
<UttakNyPeriode newPeriodeCallback={this.newPeriodeCallback} />
The problem I have is when I am clicking on a button in the component where I am sending the newPeriodeCallback:
<Hovedknapp
className={styles.oppdaterMargin}
htmlType="button"
mini
onClick={newPeriodeCallback}
>
In the container component on inspecting the console I see that the removePeriodCallback is also being triggered which then removes the new period that is being added in the function newPeriodeCallback. Why is this happening, and how can I fix this?
Update
I have tried by following the suggestion in the comment, to use the arrow function in onClick, like this:
<Image
className={styles.removeIcon}
src={removePeriod}
onClick={() => removePeriodCallback(index)}
alt="Slett periode"
/>
And that has stopped the function from triggering from other places, but it is not triggering onClick either. What is wrong with this code?
This is the complete child component:
export const UttakPeriodeType = ({
bekreftet,
tilDato,
fraDato,
uttakPeriodeType,
removePeriodCallback,
index,
}) => (
<div className={classNames('periodeType', { active: !bekreftet })}>
<div className={styles.headerWrapper}>
<Element>{uttakPeriodeType.navn}</Element>
<div>
<Image src={editPeriod} alt="Rediger periode" />
<Image
className={styles.removeIcon}
src={removePeriod}
onClick={() => removePeriodCallback(index)}
alt="Slett periode"
/>
</div>
</div>
<div>
And this is the image component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from 'react-intl';
import Tooltip from 'sharedComponents/Tooltip';
export class Image extends Component {
constructor() {
super();
this.state = {
isHovering: false,
};
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
onFocus() {
this.setState({ isHovering: true });
}
onBlur() {
this.setState({ isHovering: false });
}
onKeyDown(e) {
if (e.key === 'Enter' || e.key === ' ') {
this.props.onKeyDown(e);
e.preventDefault();
}
}
render() {
const image = (<img // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
className={this.props.className}
src={this.props.src !== null ? this.props.src : this.props.imageSrcFunction(this.state.isHovering)}
alt={this.props.altCode ? this.props.intl.formatMessage({ id: this.props.altCode }) : this.props.alt}
title={this.props.titleCode ? this.props.intl.formatMessage({ id: this.props.titleCode }) : this.props.title}
tabIndex={this.props.tabIndex}
onMouseOver={this.onFocus}
onMouseOut={this.onBlur}
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onMouseDown={this.props.onMouseDown}
/>);
if (this.props.tooltip === null) {
return image;
}
return (
<Tooltip header={this.props.tooltip.header} body={this.props.tooltip.body}>
{image}
</Tooltip>
);
}
}
Image.propTypes = {
className: PropTypes.string,
src: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape(),
]),
imageSrcFunction: PropTypes.func,
onMouseDown: PropTypes.func,
onKeyDown: PropTypes.func,
alt: PropTypes.string,
altCode: PropTypes.string,
title: PropTypes.string,
titleCode: PropTypes.string,
tabIndex: PropTypes.string,
tooltip: PropTypes.shape({
header: PropTypes.string.isRequired,
body: PropTypes.string,
}),
intl: intlShape.isRequired,
};
Image.defaultProps = {
className: '',
src: null,
imageSrcFunction: null,
onMouseDown: null,
onKeyDown: null,
tabIndex: null,
tooltip: null,
alt: null,
altCode: null,
title: null,
titleCode: null,
};
export default injectIntl(Image);
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 />);
}