I am trying to update the state of a class using useState(), but when I call the set function the render method of the class doesn't get called.
Also when I call the set function useEffect is called twice.
const InputSlider = (state) => {
const [value, setValue] = useState({
sliderValue: 0
});
const handleSliderChange = (event, newValue) => {
setValue(prevState => ({
...prevState,
sliderValue: newValue
}))
}
useEffect(() => {
console.log("state updated ", value)
});
return(
<Slider
onChange={handleSliderChange}
defaultValue={0}
//value ={value}
getAriaValueText={valuetext}
aria-labelledby="discrete-slider"
valueLabelDisplay="auto"
min={state.state.min}
max={state.state.max}
step={state.state.step}
/>
);
}
class App extends Component {
constructor(){
super();
this.state = {
sliderValue: 0
}
}
render(){
return(
<div className ="slider">
<InputSlider state = { this.state } />
</div>
)
}
}
Related
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
}
}))
};
This is the parent component:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
news: ""
}
}
componentDidMount() {
this.updateNews();
}
updateNews = () => {
...
}
render() {
<CustomButton type="primary" />
}
This is the CustomButton:
const CustomButton = (props) => {
const {
type
} = props;
const updateItem = () => {
... // The firing of the setState should be here
}
return (
<Button
type={type}
onClick={() => {
updateItem();
}}
>{value}
</Button>
);
How can I fire from inside const updateItem = () => { in CustomButton, so that Parent runs updateNews or componentDidMount?
Use the componentDidUpdate in Parent component like this.
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
news: "",
fetchToggle:true
}
}
componentDidMount() {
this.updateNews();
}
componentDidUpdate(prevprops,prevstate){
if(prevstate.fetchToggle!==this.state.fetchToggle){
this.updateNews();
}
}
updateNews = () => {
...
}
fetchToggle=()=>{
this.setState({
fetchToggle:!this.state.fetchToggle;
})
}
render() {
<CustomButton type="primary" fetchToggle={this.fetchToggle} />
}
In the child component clicking on button call this toggle function.
const CustomButton = (props) => {
const {
type
} = props;
const updateItem = () => {
... // The firing of the setState should be here
}
return (
<Button
type={type}
onClick={() => {
props.fetchToggle()
}}
>{value}
</Button>
);
Remember that a toggling value in state is a cleaner and elegant way to update or fetch latest data on every click.
You should pass a callback function to the CustomButton, something like that:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
news: ""
}
}
componentDidMount() {
this.updateNews();
}
updateNews = () => {
...
}
render() {
<CustomButton type="primary" stateUpdatingCallback={(val) => {this.setState({myVal: val})}}/>
}
const CustomButton = (props) => {
const {
type
} = props;
const updateItem = () => {
... // The firing of the setState should be here
}
return (
<Button
type={type}
onClick={() => {
this.props.stateUpdatingCallback("somevalue")
}}
>{value}
</Button>
);
I am trying to make an edit page. I am update the state for any changes made. I want to compare the initial state with the last state on the last save. but I can not control the first state.
export default class extends Component {
constructor(props){
super(props);
this.changeDetails = this.changeDetails.bind(this);
this.state = {
driver: this.props.driver
}
}
changeDetails = value => {
this.setState({
driver:value
})
}
onRegister = () => {
//I want to make a comparison here.
}
render() {
const {driver} = this.state
return (
<div>
<EditView driver={driver} changeDetails={this.changeDetails}/>
</div>
);
}
}
EditView.js
export default class extends Component {
render() {
const { driver} = this.props;
const changeDetails = event => {
driver['fname] = event.target.value;
this.props.changeDetails(driver);
};
return (
<div>
<Input
value={driver.fname}
onChange={event => changeDetails(event)}
/>
</div>
);
}
}
Do not mutate driver itself directly. Use something like this:
const changeDetails = event =>
this.props.changeDetails( { ...driver, fname: event.target.value } );
I'm new to reactjs and am creating a volume bar.
I update my state with my value and I'd like to enter it in handleclick () {}; so I take updated state and insert it into my function setVolume ();
Is there another way for me to enter my setstate into handleclick (); ?
import React from 'react'
import Slider from 'react-rangeslider'
import 'react-rangeslider/lib/index.css'
import './css/Volume.css'
class VolumeBar extends React.Component
constructor(props, context) {
super(props, context)
this.state = {
volume: 0
}
}
handleOnChange = (value) => {
this.setState({
volume: value
})
}
handleClick(volume) {
window.DZ.player.setVolume()
}
render() {
let { volume } = this.state
console.log(volume);
return (
<div className="VolumeBar">
<Slider value={volume} orientation="horizontal" onChange= {this.handleClick} />
</div>
);
}
}
export default VolumeBar
Have you tried like that?
<Slider value={volume} orientation="horizontal" onChange=this.handleOnChange} />
handleOnChange = (e) => {
this.setState({
volume: e.target.value
})
To set the current volume?
handleOnChange = (value) => {
this.setState({
volume: value
})
window.DZ.player.setVolume(value)
}
thank you i solved it like this.
So in the example below i have validateResult which is set to the displayMessage. Depending on the user input it'd return a value, this is outside of the class component and i dont know how to test a function outside of the class with jest.
So i tried using mount from enzyme to mount the component then with instance to access the function but this gave me an error saying that this is not a function and im not sure how to test this.
test.js
const wrapper = mount (
<tempComponent />,
);
const instance = wrapper.instance();
it('expect result to be good', () => {
expect(instance.validateResult(true)).toBe("good");
});
tempComponent.js
const validateResult = (data) => {
if(data)
return "good";
else
return "bad";
};
class tempComponent extends Component {
constructor(props) {
super(props);
this.state = { inputdata: '' };
this.onSuccess = this.onSuccess.bind(this);
}
render() {
const { inputdata } = this.state;
const { onSubmit } = this.props;
const displayMessage = validateResult(inputdata);
return (
<div id="submit-form" className="row justify-content-center">
<div className="col-md-4">
<FormContainer onSubmit={() => onSubmit({ inputdata }, this.onSuccess)} >
<Input type="text" label="" onTextChange={(value) => this.setState({ ...this.state, inputdata: value })} text={inputdata} />
<SubmitButton value={'Submit'} disabled={displayMessage}/>
</FormContainer>
</div>
</div>
);
}
}