setState is delayed when using onChange to trigger function (react) - reactjs

I'm new to coding and have just recently started using React.
I am selecting items from a drop-down menu using e.target.value. I am then setting the state of a variable with the value of the selected item.
However, the state value seems to be delayed i.e
I select item A and in the console logs I have selected item A but the state remains empty. Only once I select a new item e.g. item B, does the state update to reflect I selected item A.(screen shot below)
screen shot of console logs
I'm not entirely sure what I'm missing
Please see the code below:
class GetApi extends React.Component {
constructor(props) {
super(props);
// Set initial state
this.state = {
error: null,
isLoaded:false,
projects: [],
title: ' ',
description:'',
url:'',
id: '',
// selectedOptionId: []
};
// Binding this keyword
this.handleChange = this.handleChange.bind(this);
}
// handle onChange event of the dropdown
handleChange = (e) => {
console.log(`target value is ${e.target.value}`)
//update value of item id to be deleted
const toDelete = e.target.value
this.setState({ id: toDelete })
console.log(`This.state.id = ${this.state.id}`)
if (this.state.id === null){
console.log('Error - Selected Option is Empty')
} else {
console.log(`Selected ID is: ${this.state.id}`)
}
}
render(){
return (
<Container>
{/* Delete Item */}
<div className="delete">
<select
id='deleteSelect'
name='projects'
label= 'Projects'
multiple={false}
// assign onChange function
onChange={this.handleChange}
>
{/* set list options */}
{projects.map((item)=>(
<option key={item.id}
// let the value of the of dropdown be the id
value = {item.id}
>{item.id}:{item.title}
</option>
))}
</select>
<Button
onClick={(e) => this.delete(e)}>Delete</Button>
</div>
</Container>
)
}
}
export default GetApi;

Yes, this is how setState works, it triggers a new render with the updated value but it doesn't update the current one.
Also I strongly encourage you to look at function components and hooks, classes have been kind of obsolete in React for many years.

Related

(React) How to match the modal content with the clicked element?

I'm trying to match the modal that shows with the clicked element, right now i'm rendering all the modals with the click, I been trying to make them match with the index but no luck , please help.
here are my constructors
constructor(props) {
super(props);
this.state = {
portfolioData: [],
newModal:[],
modalPost: false,
isShown: false
};
}
showModal = (i) =>{
this.setState({ isShown: true, modalPost: true })
}
closeModal = () => {
this.setState({isShown:false, modalPost: false})
}
and here I get the data and render two list component and the modal
componentDidMount() {
axios.get(`data.json`)
.then(res => {
const portfolioData = [res.data.portfolio.projects.film];
this.setState({ portfolioData });
})
};
the components
const portfolioList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<PortfolioItem
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
showModal={this.showModal}
youtube={val.trailer}
/>
))
const modalList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<Modal
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
closeModal={this.closeModal}
youtube={val.trailer}
/>
))
and the return
<section id="portfolio">
{ portfolioList }
{ this.state.modalPost !== false ? modalList : null }
</section>
Your code will make as many modal as the data held by this.state.portfolioData, which is unnecessary, inefficient and may result into wasted rendering. If you take a step back and think from this way, You are going to render only one modal but render it with the data of the selected item
Lets see an example,
We can start by having an additional state value selectedValue which will hold the clicked item's value
this.state = {
portfolioData: [],
newModal:[],
modalPost: false,
isShown: false,
selectedValue: {} //<---
};
Then, when the user clicks the item we can set that particular items value in the state; specifically in selectedValue
const portfolioList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<PortfolioItem
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
showData={() => this.showData(val)} //<---
youtube={val.trailer}
/>
))
//new function for one specific task <---
const showData = (value) => {
this.setState({selectedValue: value}, this.showModal)
}
Finally, instead of mapping over the data you can render only one modal which takes and show the data from the this.state.selectedValue
<Modal
id={this.state.selectedValue.title.en.toString().toLowerCase().split(" ").join("-")}
title={this.state.selectedValue.title.en}
imgsrc={this.state.selectedValue.imgsrc}
status={this.state.selectedValue.status}
profile={this.state.selectedValue.profile}
director={this.state.selectedValue.director}
production={this.state.selectedValue.production}
closeModal={this.closeModal}
youtube={this.state.selectedValue.trailer}
/>
This is merely an idea you can follow. You should organize/optimize your code later per your codebase afterwards. If you are unfamiliar with setStates second parameter it takes a callback as a second parameter which it executes after updating state. Reference: https://reactjs.org/docs/react-component.html#setstate
Hope this helps you, Cheers!
Edit: I just noticed you are not using that isShown anywhere. That is the value that the modal should be opened based on. The modal should have a prop which makes it show/hide by passing true/false, check the documentation. And you should pass this.state.isShown to that specific prop to make everything come together and work!
The problem is you are creating multiple instances of modals when you are looping over your portfolioData. I dont think you need the multiple instances since you will open only one at a time.
When you are setting the state of modal to isShown. You are actually setting state on all the instances of the generated modals. Hence you end up opening multiple modals. You should ideally have just one modal and pass data to your modal.
You can do this:
First, Move the modal out of the loop so that you have only one instance of it.
Pass data to:
<a>onClick={() => this.toggleModal(provider.data)} key={index}>Portfolio Item</a>
Lastly, in toggleModal function first set the data then open modal.
This way all your PortfolioItem links will end up calling the same modal instance. But with different data. Once you set the data you can rerender your component with the already existing isShown state.
Here is a small example:
This way all your PortfolioItem links will end up calling the same modal instance. But with different data. Once you set the data you can rerender your component with the already existing isShown state.
Here is a small example:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
isShown: false,
data: ''
}
this.list = [{data: 'data1'}, {data: 'data2'}];
}
onAnchorClick({data},event){
this.setState({data, isShown: true});
}
render(){
return(
<div>
{this.list.map((obj, idx) => <div key={idx}>
<a onClick={this.onAnchorClick.bind(this, obj)}>Portfolio Item</a>
</div>)}
<div style={{display: !this.state.isShown ? 'none' : 'block'}}>
<h3>Data: {this.state.data}</h3>
</div>
</div>
)
}
}
window.onload = () => {
ReactDOM.render(<App />, document.getElementById("app"));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Trouble updating radio button state value in parent component from child element

I'm currently working a a multipage checklist app to make a common checklist procedure more efficient.
my parent component called MainForm has all of the states for my app. In my first child element, I had to fill some text inputs. The states are updating and saving as planned. My second page (or other child element) was the portion where my checklist would begin. The issue is my app is rending, but the radiobutton value isn't being sent to my state. I'm also having an issue where I can select the 'yes' radio button and then the 'no' radio button, but I can't go from 'no' to 'yes'. radioGroup21 is the radio group that's giving me problem. All other states are working.
I'm getting an error in my console that says:
"Checkbox contains an input of type radio with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props.
I've tried removing the value tag and the defaultValue line in my Radio elements, but no luck. I've tried creating constructor(props) in my parent element but I still kept having issues."
So far I've tried removing the defaultValue in my radio button and after I tried removing the value line. Unfortunately I this did not help.
I also read about controlled and uncontrolled inputs. I've tried changing my parent components state to put them in a constructor(props) bracket. But no luck.
I also tried to not use the handleChange function and use the setState function with values of {radioButton21 === 'yes'} but that didn't work.
//Parent Component
Class MainForm extends Component {
state = {
step: 1,
projectNumber: '',
projectName: '',
numberOfSystems: '',
buildSheet: '',
controlPhilosophy: '',
projectLayoutDrawing: '',
projSoftwareValidation: '',
CppDrawing: '',
radioGroup21: '',
}
nextStep = () => {
const { step } = this.state
this.setState({
step : step + 1
})
}
prevStep = () => {
const { step } = this.state
this.setState({
step : step - 1
})
}
handleChange = input => event => {
this.setState({ [input] : event.target.value })
}
render(){
const {step} = this.state;
const { projectNumber, projectName, numberOfSystems, buildSheet , controlPhilosophy, projectLayoutDrawing, projSoftwareValidation, CppDrawing, radioGroup21 } = this.state;
const values = { projectNumber, projectName, numberOfSystems, buildSheet, controlPhilosophy, projectLayoutDrawing, projSoftwareValidation, CppDrawing, radioGroup21 };
switch(step) {
case 1:
return <ProjectInfo
nextStep={this.nextStep}
handleChange = {this.handleChange}
values={values}
/>
case 2:
return <PrelimInspection
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange = {this.handleChange}
values={values}
/>
export default MainForm;
-----------------------------------
//Child Component
import React, { Component } from 'react';
import { Form, Button, Radio } from 'semantic-ui-react';
import { throws } from 'assert';
class PrelimInspection extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const { values } = this.props
return(
<Form color='blue' >
<h1 className="ui centered">System Installation</h1>
<Form.Field inline>
<Form.Field>System Properly Supported</Form.Field>
<Radio
label = {'Yes'}
name = {'radio21'}
value = {'Yes'}
onChange={this.props.handleChange('radioGroup21')}
defaultValue={values.radioGroup21}
/>
<Radio
label = {'No'}
name = {'radio21'}
value = {'No'}
onChange={this.props.handleChange('radioGroup21')}
defaultValue={values.radioGroup21}
/>
</Form.Field>
<Button onClick={this.back}>Back</Button>
<Button onClick={this.saveAndContinue}>Save And Continue </Button>
</Form>
)
}
}
export default PrelimInspection
The app is rendering and the layout is correct. Unfortunately the state values aren't being sent to the parent state.
I checked the documentation https://react.semantic-ui.com/addons/radio/#types-radio-group and I have found few things you missed:
1.) Radio component asked the checked props (but you did not supply it).
2.) Which then requires you to pass the value, in your case it should come from the parent component:
<PrelimInspection
valueFromParent={this.state["radioGroup21"]}
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
so in your Child Component' render, take the value:
render() {
const { values, valueFromParent } = this.props;
...
3.) Radio's onChange value is passed as the second param (obj.value).
<Radio
label={'Yes'}
name={'radio21'}
value={"Yes"}
checked={valueFromParent === 'Yes'}
onChange={this.props.handleChange("radioGroup21")}
...
/>
So you can take the selected value like this:
// MainForm
handleChange = input => (event, obj) => { // obj is the second param
console.log("sendin here", input, obj.value);
this.setState({ [input]: obj.value });
};

React Parent component checkbox state updates with one step delay

I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});

how to 'inject' confirm event into react-select?

I am using this project https://github.com/JedWatson/react-select for select component. I use it to render a multi select component. Below is a sample code:
import Async from 'react-select/lib/Async';
<Async
className="user-select"
classNamePrefix="user-select"
defaultValue={this.state.defaultValue}
defaultOptions
isClearable={false}
loadOptions={this.loadOptions}
isMulti
/>
below is a screenshot. It renders two items Purple and Red.
The item will be removed when I click the close button after each item. How can I add a prompt model to ask user confirm before deleting?
on clicking the item send that particular id to the event handler function and set that value and id in the state and also do setState for modal to true to show the model when item is clicked
You also make sure to set the showModal to false when user clicks yes or no in the modal so that it will work next time when you want to delete other item.
constructor(props){
super(props);
this.state = {
itemId: 0,
showModal: false,
itemValue: ""
}
}
handleItem = event => {
this.setState({
itemId: event.target.id,
showModal: true,
itemValue: event.target.value
});
}
resetModalFlag = () => {
this.setState({
showModal: false
})
}
render(){
const { showModal, itemId, itemValue } = this.state;
return(
<div>
<Select onChange={this.handleItem} />
{showModal && <Modal id={itemId} itemValue={itemValue} resetModalFlag={this.resetModalFlag} />}
</div>
)
}
In Modal component access itemId and itemValue using this.props and you can show text like are you sure you want delete this.props.itemValue With yes or no button. When either one of these buttons clicked you need to call resetModalFlag in yes and no button event handler functions like
handleYesButton= () =>{
this.props.resetModalFlag();
}
handleNoButton= () =>{
this.props.resetModalFlag();
}

react-select does not clear value when redux-form is reset

I have a stateless React function to render a react-select Select to a form. Everything else works nicely with redux-form except when redux-form is reset. I need to reset the form manually after successful post.
onChange and onBlur change the redux-form value correctly when Select has a value change. When I reset the redux-form, the redux-form value is cleared but the Select will have the old value.
function SelectInput(props) {
const { input, options, label } = props;
const { onChange, onBlur } = input;
const handleChange = ({ value }) => {
onChange(value);
};
const handleBlur = ({ value }) => {
onBlur(value);
};
return (
<FormField {...props}>
<Select placeholder={label} options={options} onChange={handleChange} onBlur={handleBlur} />
</FormField>
);
}
I converted the SelectInput to React.PureComponent, and added the value as a state inside the component and looked for when the Component received new props:
constructor(props) {
super(props);
this.state = {value: ''}
}
componentWillReceiveProps(nextProps){
this.setState({value: nextprops.input.value})
}
<Select value={this.state.value} placeholder={label} options={options} onChange={handleChange} onBlur={handleBlur} />
With this Select was not able to show the value at all.
The problem is that how I can update the Select to show empty value when redux-form that this field is part of is reset? Redux-form resets the value corretly inside the redux state and if I try to submit the form, validation notices that that Select has empty value. The Select will however display the old value so that user thinks that there is a value selected.
Reset is done by dispatching reset in the actual redux-form component. Redux devtools show that fields are reset and the redux state is cleared from all the value, Select component just won't update the DISPLAYED value to empty.
const afterSubmit = (result, dispatch) =>
dispatch(reset('datainputform'));
export default reduxForm({
form: 'datainputform',
onSubmitSuccess: afterSubmit,
})(DataInputForm);
Versions I use:
react-select#v2.0.0-beta.6
redux-form#7.3.0
You can also set a key at the form level itself. The key will take a unique value that you can store in the component state. This unique value will be updated every time reset is hit.
state = {
unique_key: ''
}
// this is the onClick handler for reset button on the form
onResetForm = () => {
reset_val = Date.now();
this.props.reset();
this.setState({unique_key: reset_val});
}
<Form actions={action_func}, key={this.state.unique_key}/>
Now whenever reset is clicked, the handler will update the unique_key. This will result in re-rendering the Form with the default values. The handler also calls the form reset function to clear the redux.
Got it working. Problem was handling the Select null value. Changed stateless function to PureComponent, added the value to state.
constructor(props) {
super(props);
this.state = { value: '' };
}
Redux-form changes the react-select value by sending new props. So added
componentWillReceiveProps(nextProps) {
if (nextProps.input.value === '') {
this.setState({ value: '' });
}
}
Added setState to handleChange:
handleChange = (data) => {
const value = data === null ? '' : data;
this.setState({ value });
this.props.input.onChange(data.value);
};
And then added the value prop.
<Select value={this.state.value}...

Resources