handleChange in the parent component and child? - reactjs

What I am trying to do is:
on the parent component I have onChange=this.handleChange that it works fine for textInputs and it allows me to go to the next steps of my form but it doesn't setState of dropDowns whatever I do my dropdowns are empty and if I setState dropdowns on the child I am unable to trigger the handleChange on the parent thing that I need
class CompanyInfo extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.company,};
this.handleChange = this.props.onChange.bind(this);
}
render() {
return(
<SelectInput value={this.state.value}
onChange={this.handleChange}
floatingLabelText={messages.company[lang]}
floatingLabelFixed={true}
>
{this.props.company.map((element) => {
return <MenuItem value={element.Value} primaryText={element.Value} />})}
</SelectInput>
parent:
handleChange = (event) => {
console.log("test");
const { stepIndex } = this.state;
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
var type = null;
switch(stepIndex) {
case 0: type='UPDATE_CINFO'; break;
case 1: type='UPDATE_PINFO'; break;
default: break;
}
this.props.dispatch(updateCurrentForm( type, { [name]: value } ));
this.setState( this.state , () => this.validateFields() )
}

Just use the prop of handleChange directly in the child component. And you also should control the state only at one place and use the state values as props in child components.
render() {
const {handleChange, company} = this.props
return(
<SelectInput value={company}
onChange={handleChange}
floatingLabelText={messages.company[lang]}
floatingLabelFixed={true}
>
...
}

Related

How to refactor class component to functional(have to rerender, got undefined )

In IssueEdit component the main state of my app is stored(which I got from an API
call).
I pass that data to a specialized input component where issue.effort have to be
converted to string and stored as string. It works well in case of Class Component. I tried to refactor the class component to functional component and this is when the problem started. The problem is that after first render formattedValue is undefined so nothing is displayed. The input field is empty. How to make functional component behave equally as class component?
export const IssueEdit = () => {
const [issue, setIssue] = useState({effort: 5})
...
return (
...
<td>
<NumInput
name="effort"
value={issue.effort}
/>
</td>
...
*** Class Component ***
function format(num) {
return num != null ? num.toString() : '';
}
export default class NumInput extends React.Component {
constructor(props) {
super(props);
this.state = { value: format(props.value) };
this.onChange = this.onChange.bind(this);
}
onChange(e) {
if (e.target.value.match(/^\d*$/)) {
this.setState({ value: e.target.value });
}
}
render() {
const { value } = this.state;
return (
<input
type="text"
value={value}
onChange={this.onChange}
/>
);
}
}
*** Functional Component ***
export const NumInput = ({ value }) => {
const format = (num) => {
num != null ? num.toString() : ''
}
const [formattedValue, setFormattedValue] = useState(format(value))
const onChange = (e) => {
if (target.value.match(/^\d*$/)) setFormattedValue(target.value)
}
return (
<input
type="text"
value={formattedValue}
onChange={onChange}
/>
)
}
Is this just a typo ? onChange={onChangeValue} should be onChange={onChange}.
You named your function onChange in the functional component but are then invoking it with onChangeValue which doesnt exist.
Another problem as the author commented I'll edit so everyone can see:
const format = (num) => {
num != null ? num.toString() : ''
}
// what u meant
const format = (num) => {
return num != null ? num.toString() : ''
}

Why is my props updating before I update the state?

I am trying to change an input inside a GrandChild class and a Bootstrap Table inside Parent class*. An user would change the input inside **GrandChild class then save it, so the changes are seen in the Bootstrap Table in Parent class; however, I am seeing this weird behavior where my props are changing before I call the .onChange (which is my save). I believe this is causing my inputs to not save or setting the state properly.
Data being passed down hierarchy: GrandParent => Parent => Child => GrandChild
It is occurring at the Child class's handleSave() function:
export class Child extends React.Component {
constructor(props){
this.state = {
data:this.props.data
}
}
handleChange = (name, value) => {
this.setState((prevState) => {
let newState = {...prevState};
newState.data.dataList[0][name] = value; // data
return newState;
});
};
handleSave(){
let dataList = this.state.data.dataList.slice();
console.log("dataList state-dataList:", dataList);
console.log("dataList before onChange 2:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
this.props.onChange("dataList", dataList);
console.log("dataList onChange 3:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
}
render() {
return (
<div>
<GrandChild data={this.state.data} onChange={this.handleChange} />
</div>
)
}
Child class's this.props.onChange gets sent back to the Parent class:
export class Parent extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
columns = [
{dataField: '..', text: '...' },
{dataField: '..', text: '...' },
{dataField: '..', text: '...' },
{dataField: '..', text: '...'}];
handleChange = (name, value) => {
this.props.onChange(name, value);
};
render() {
return (
<div>
<BootstrapTable
hover
condensed={true}
bootstrap4={true}
keyField={'id'}
data={this.props.data.dataList}
columns={this.columns}
/>
<Child data={this.props.data} onChange={this.handleChange} />
</div>
);
}
}
Then Parent class's this.props.onChange* gets sent to GrandParent Class:
export class GrandParent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {...this.props.location.state.data}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (name, value) => {
this.setState((prevState) => {
let newState = {};
let data = Object.assign({}, prevState.data);
data[name] = value;
newState.data = data;
return newState;
});
};
render() {
return (
<div>
<Form>
<Parent data={this.state.data} onChange={this.handleChange} />
</Form>
</div>
)
}
This is the GrandChild's class:
export class GrandChild extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ?
target.checked :
target.value;
const name = target.name;
this.props.onChange(name, value);
};
render() {
return (
<div>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>Label Name</Form.Label>
<Form.Control name="labelName" value={this.props.data.[0]labelName || ""} //ignore the index for now
onChange={this.handleInputChange}/>
</Form.Group>
</Form.Row>
</div>
)
}
}
I expected that console.logs() of the dataLists to be different; however, they give the same exact object before it even runs the this.props.onChange("dataList", dataList);
Potentially, the third dataList console.log might be same as the state dataList because of setState being asynchronous.
It looks like the main issue is that you're mutating state/props in Child:
handleChange = (name, value) => {
this.setState((prevState) => {
// {...prevState} makes a shallow copy of `prevState`
let newState = {...prevState};
// Next you're modifying data deep in `newState`,
// so it's mutating data in the `dataList` array,
// which updates the source of truth for both props and state.
newState.data.dataList[0][name] = value;
return newState;
});
};
One way to do this (avoiding mutation) is like this:
handleChange = (name, value) => {
this.setState(prevState => ({
data: {
...prevState.data,
dataList: [
{
...prevState.data.dataList[0],
[name]: value
},
...prevState.data.dataList.slice(1)
]
}
}));
};
If that's more verbose than you'd like, you could use a library like immutable-js.
Another issue that could cause you bugs is copying props into state. This article gives some explanation of why that's bad: https://overreacted.io/writing-resilient-components/#dont-stop-the-data-flow-in-rendering
Basically: If you set props in state and then update state and pass props down to a child, the data you're passing down will be stale. It doesn't look like you're doing that here, but it would be easy to miss. An easy way to avoid this is to name any props you plan on setting in state initialProp If your prop is named initialData, it will be clear that from that point in the tree you should rely on the value in state rather than props.
Also, handleChange in Grandparent can be written more simply:
handleChange = (name, value) => {
this.setState(prevState => ({
data: {
...prevState.data,
[name]: value
}
}))
};

how to get the checked value of a radio button using react

I have below radio button Component
import React from 'react';
import PropTypes from 'prop-types';
export default class RadioButton extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => {
this.props.handleChange(event);
}
render() {
return (
<div>
<input type="radio" name="measureValue" value={this.props.value} onChange={this.handleChange} checked={true}/>
<label>{this.props.value}</label>
</div>
);
}
}
Im using this component as
handleRadioSelect = (event) =>{
this.setState({
selectedRadioValue : event.target.value
})
}
<RadioButton value="Fact" handleChange = { this.handleRadioSelect }/>
Now,I got the error as handleChnage is not a function.
to get value of checked, use the event.target.checked instead of event.target.value, so that:
handleRadioSelect = (event) =>{
this.setState({
radioChecked : event.target.checked
})
}
And your error appear because you need to use the arrow function to this.handleCheck (so you can pass in the event props) so that:
onChange={e => this.handleCheck(e)}
In this case, you do not need to bind it anymore and just use normal function for the handleCheck so that:
handleChange(event) {
this.props.handleChange(event);
}
This is how I normally approach it, hope that helps!
handleChange = (event) => {
let target = event.target;
let name = target.name;
let value = target.type === 'checkbox' ? target.checked : target.value;
this.setState({
[name] : value
})
}
Hope this helps

Class not applied to the input element reactjs

currently, want to style an input element when the input element is not empty and the user has entered text. Below is the code snippet. Class is applied when !input_active but when condition is set to (!input_active && !inputEmpty) class does not apply to the input element.
constructor(props) {
super(props);
this.input_text_ref = React.createRef();
this.state = {
input_active: true,
};
}
focus = () => {
console.log("focus");
this.setState({
input_active: true
});
};
blur = () => {
console.log("blur");
this.setState({
input_active: false
});
};
handle_text_input_change = (event) => {
this.props.onChange(event);
this.setState({
text_input_value: event.target.value
});
console.log("withinhandle", this.state.text_input_value);
};
render() {
const {
value,
disabled,
...rest
} = this.props;
const {
input_active
} = this.state;
console.log(input_active);
let input_element_classes = [''];
let inputEmpty = value.length == 0;
console.log("inputempty", inputEmpty);
const inputCompiled = value.length > 0;
if (input_active && !inputEmpty) {
input_element_classes.push('blur');
}
return (
<input {...rest}
className={input_element_classes.join(' ')}
type="text"
ref={this.input_text_ref}
onChange={this.handle_text_input_change}
disabled={disabled}
onBlur={this.blur}
//onKeyDown={this.shouldBlur}
onFocus={this.focus}
/>
);
}
Could someone help me with this? Also, based on input element validation (empty, disabled, the user entering text so on) how can I change the style of the input element. From the code, it's understood that I am using an array of classes (input_element_classes) based on validation I pop the class and push some other class. Is there any other easy way to do this. thanks.
It looks a bit overload. If I got you right, you can write it this way:
export class TextInput extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isActive: props.value.length > 0
};
}
static defaultProps = {
value: "",
disabled: false
};
onFocus = () => {
this.setState({ isActive: true });
};
onBlur = ({ target }) => {
this.setState({ isActive: target.value.length > 0 });
};
render() {
const { disabled, ...rest } = this.props;
const { isActive } = this.state;
return (
<input
{...rest}
className={`text-input ${isActive ? "active" : ""}`}
type="text"
disabled={disabled}
onBlur={this.onBlur}
onFocus={this.onFocus}
/>
);
}
}
You can find the example in the sandbox https://codesandbox.io/s/zl9mmvmp33

ReactJs: set initial state using props while using dynamic Taglist

I am sending an array named tags as props and i want to assign it in initial states so that those array elements should be displayed. using this code they are displayed properly but i am unable to edit them. when i click cross it gets deleted but after sometime it displays again. Seems like setState in handelClose method is not working.
import React from 'react';
import ReactDOM from 'react-dom';
import { Tag, Input, Tooltip, Button } from 'antd';
class EditableTagGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: [],
inputVisible: false,
inputValue: '',
};
}
handleClose = (removedTag) => {
// debugger;
const tags = this.state.tags.filter(tag => tag !== removedTag);
console.log(tags);
// debugger;
this.setState({ tags: tags });
}
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
}
handleInputChange = (e) => {
this.setState({ inputValue: e.target.value });
}
handleInputConfirm = () => {
const state = this.state;
const inputValue = state.inputValue;
let tags = state.tags;
if (inputValue && tags.indexOf(inputValue) === -1) {
tags = [...tags, inputValue];
}
console.log(tags);
this.setState({
tags,
inputVisible: false,
inputValue: '',
});
this.props.onSelect(tags);
}
// componentDidUpdate(prevProps, prevState) {
// this.setState({tags: this.props.tags})
// }
componentDidUpdate(prevProps, prevState) {
// debugger;
if (this.state.tags !== this.props.tags) {
this.setState({tags: this.props.tags})
}
}
saveInputRef = input => this.input = input
render() {
const {tags , inputVisible, inputValue } = this.state;
return (
<div>
{tags.map((tag, index) => {
const isLongTag = tag.length > 20;
const tagElem = (
<Tag key={tag} closable={index !== -1} afterClose={() => this.handleClose(tag)}>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</Tag>
);
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
})}
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 78 }}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && <Button size="small" type="dashed" onClick={this.showInput}>+ New Tag</Button>}
</div>
);
}
}
export default EditableTagGroup
The problem is that, you are saving and using the props in the local state and then modifying those, however everytime the component updates, you are setting the state back to the props.
// this is where you are setting the state back to
// props and hence your edit disappears
componentDidUpdate(prevProps, prevState) {
// debugger;
if (this.state.tags !== this.props.tags) {
this.setState({tags: this.props.tags})
}
}
What you need to do is to not maintain, the props in state but rather directly modifying it, by passing a handler from the parent to update the props.
See this answer on how to pass and update data in parent
How to pass data from child component to its parent in ReactJS?

Resources