Parent: (The TextField component is a styled input element from office-ui-fabric-react: Link)
<Searchbar>
<TextField label="Account ID" />
<TextField label="Account Name" />
</Searchbar>
Searchbar component:
export default class Searchbar extends React.Component<ISearchbarProps, ISearchbarState> {
private _searchfields: React.ReactNode[];
public constructor(props: ISearchbarProps) {
super(props);
this.state = {
searchValues: {},
};
this._handleChange = this._handleChange.bind(this);
this._getChild = this._getChild.bind(this);
this._getChild();
}
private _handleChange(event: any, field: string) {
const searchValues = this.state.searchValues;
searchValues[field] = event.target.value;
this.setState({ searchValues });
console.log(this.state.searchValues);
}
private _getChild(): void {
let fielCounter = 0;
this._searchfields = React.Children.map(this.props.children, (child: any) => {
if (child.type.displayName === 'StyledTextFieldBase') {
const searchValues = this.state.searchValues;
const fieldname = child.props.label.replace(/\s/g, "") + fielCounter++;
searchValues[fieldname] = "";
this.setState({ searchValues });
return React.cloneElement(child, {
value: this.state.searchValues[fieldname],
onChange: (e) => this._handleChange(e, fieldname)
});
}
});
}
public render(): React.ReactElement<ISearchbarProps> {
return (
<div>{this._searchfields}</div>
);
}
}
The Problem:
When I type something into the input element, the state console output in the _handleChange shows the updated state, but the input value stays the same as I wouldn't type anything.
Also each letter gets remove from the state. So if I type "test", the console would output "t", "e", "s", "t"
What am I doing wrong?
So I dont know how you managed to get it to build at all. Setting the state in the constructor is not working and throwing an error. But I guess your main problem is, that you build your searchfields only in constructor. It will never called again (unless the searchbar component is destroyed and rebuilded) and so your children are fixed.
public constructor(props: ISearchbarProps) {
super(props);
this.state = {
searchValues: {},
};
this._handleChange = this._handleChange.bind(this);
this._getChild = this._getChild.bind(this);
this._getChild();
}
private _handleChange(event: any, field: string) {
const searchValues = this.state.searchValues;
searchValues[field] = event.target.value;
this.setState({ searchValues });
console.log(this.state.searchValues);
}
private _getChild(): React.ReactNode[] | undefined | null {
return React.Children.map(this.props.children, (child: any) => {
if (child.type === 'input') {
const fieldname = child.props.name;
const value = this.state.searchValues[fieldname];
return React.cloneElement(child, {
value: value,
onChange: (e: any) => this._handleChange(e, fieldname)
}) as React.ReactNode;
}
});
}
public render(): React.ReactElement<ISearchbarProps> {
return (
<div>{this._getChild()}</div>
);
}
}
I changed the code a bit to work with normal inputs but it should work with Textfields too. Now the getChild method is called everytime you type into the input. I am not sure if this is good in regards to performance. Maybe you can save the searchvalue state in an upper component (where you render the textfields) and declare them directly in the code. But I dont know your usecase here and why you are doing it like that in the first place.
<Searchbar searchValues={this.state.searchValues}>
<input name="test1" value={this.state.searchValues["test1"]} onChange={...}/>
<input name="test2"value={this.state.searchValues["test2"]} onChange={...}/>
</Searchbar>