Unable to set state in a react component - reactjs

I have a react component that looks like this. I call a method cleanUpInvoices
to format a date in my array object(invoices). This works very well without any
problem. I tried to setState to dateCleanUpResult.
All I get is "dateCleanUpResult is not defined". I have tried so many things and nothing works.
I am not able to set state.
What is wrong with this code?
Here is the entire code
class Tester extends PureComponent {
constructor(){
super();
this.state = {
invoices:[],
startDate:'',
endDate:'',
queryResult:[],
dateCleanUpResult:[]
};
this.searchForInvoicesByDates = this.searchForInvoicesByDates.bind(this);
this.handleChange = this.handleChange.bind(this);
this.cleanUpInvoices = this.cleanUpInvoices.bind(this);
}
handleChange({ target }) {
this.setState({
[target.name]: target.value
});
}
componentDidMount() {
const getCustomerId = this.props.customer.customerId;
AXIOS_AUTHED.get(`${API}/customers/${getCustomerId}/invoices?sort=settledDate,desc`)
.then(res => {
const invoices= res.data.content;
this.setState({ invoices });
})
}
cleanUpInvoices(){
const invoice = this.state.invoices;
invoice.forEach(function(invoicer) {
const newDate = invoicer.settledDate.substring(0, invoicer.settledDate.indexOf('T'));
invoicer.settledDate = moment(newDate, 'YYYY-MM-DD').format('MM-DD-YYYY');
});
return this.setState({
dateCleanUpResult: invoice
}, () => this.state.dateCleanUpResult);
}
searchForInvoicesByDates(startDate, endDate){
var myResult = this.cleanUpInvoices();
console.log(myResult);
//now perform your date search based on the result from above
let cleanedStartDate = moment(startDate).format('MM-DD-YYYY');
let cleanedEndDate = moment(endDate).format('MM-DD-YYYY');
let filteredResult = [];
for(let i = 0; i < this.state.dateCleanUpResult.length; i++){
if(this.state.dateCleanUpResult[i].settledDate >= cleanedStartDate && this.state.dateCleanUpResult[i].settledDate <= cleanedEndDate) {
filteredResult.push(this.state.dateCleanUpResult[i]);
}
}
console.log(filteredResult);
const listItems = filteredResult.map((number) =>
<li key={number.orderNumber}>{number.orderNumber} - {moment(number.settledDate).format('MMM-DD-YYYY')} </li>
);
this.setState({queryResult:listItems});
return (
<ul>{listItems}</ul>
);
}
render() {
return (
<PageBase
navigation={['Customer Solution', 'Tester App']}
>
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<Paper>
<Typography className="customer-solution-subheader" component="h3" variant="subheading">
Tester App
</Typography>
<form>
<div className="customer-form-details">
<span>DATE RANGE COVERED*</span><br/>
<span className="ctrCalendar">
<label htmlFor="start">Start date:</label>
<input type="date" id="start" name="startDate" value={this.state.startDate} onChange={this.handleChange} required></input>
</span>
<span className="ctrCalendar">
<label htmlFor="start">End date:</label>
<input type="date" id="end" name="endDate" value={this.state.endDate} onChange={this.handleChange} required></input>
</span>
<span>
<Button variant="contained" className="next-button" id="btnSearchDates" onClick={() =>this.searchForInvoicesByDates(this.state.startDate, this.state.endDate)}>Search</Button><br/><br/>
</span>
<p>Search Result (Invoices/Dates)</p>
<div role="content" className="invContentParent">
<div name="teach" id="invContentChild">
</div>
</div>
</div>
</form>
</Paper>
</div>
</div>
</PageBase>
);
}
}
export default Tester;

It's almost right, but what's happining is that when you return the for the array it could be just the empty array you are initializing at the state of the component.
We fix that be passing a callback function to setState method, which is a function that returns the state you want, the updated one right?
That callback function will be invoked after insuring that the new state has been set, and, we also return the setState function, because it's the one that is returning the new state.
return this.setState({
dateCleanUpResult: invoice
}, () => this.state.dateCleanUpResult);
That ARTICLE is a good explanation for that matter.

I created a jsfiddle showing that you can update the state in your case.
http://jsfiddle.net/efp82rjg/3/
I think you are getting an issue because you are assuming cleanUpInvoices() will return the updated value of state. But it won't because setState is asynchronous and even though the value will be updated but it won't show the updated value to you. If you want to access the updated value after setState() then use the call back that is available to you after setState function. Please read the documentation here: https://reactjs.org/docs/react-component.html#setstate
class Hello extends React.Component {
constructor() {
super();
this.state = {
invoices: [
{ settledDate: 'no' },
{ settledDate: 'no' },
],
dateCleanUpResult: [],
};
this.cleanUpInvoices = this.cleanUpInvoices.bind(this);
}
cleanUpInvoices() {
const invoice = this.state.invoices;
invoice.forEach((invoicer) => {
invoicer.settledDate = 'testing';
});
this.setState({
dateCleanUpResult: invoice,
});
return this.state.dateCleanUpResult;
}
render() {
return (
<div>
<button onClick={this.cleanUpInvoices}>test</button>
{this.state.dateCleanUpResult.map(item => (
<div>{item.settledDate}</div>
))}
</div>
);
}

I'm assuming you receive the value of "undefined" where you are logging the returned value cleanUpInvoices in your searchForInvoicesByDateMethod. Although this is the result of the async nature of setState, implementing a setState callback will not remedy this issue (without more, it will merely let you access the updated state in the scope of that handler). If you want to stick to your current implementation, I would return a promise and async/await the returned promise value. This will delay execution of code in your searchForInvoicesByDates method when cleanUpInvoices is called.
cleanUpInvoices(){
// Avoid directly mutating state and generating circular reference by creating new array with map
const invoice = this.state.invoices.map(invoice => Object.assign({}, invoice));
//.....
//.....
return new Promise((res) => {
return this.setState({
dateCleanUpResult: invoice
}, () => res(this.state.dateCleanUpResult)));
}
}
////////
async searchForInvoicesByDates(startDate, endDate){
var myResult = await this.cleanUpInvoices();
//....
}
Trying not to deviate from the question posed, here is another quick thought that might simplify the implementation. Looks like you're initially making an API call on componentDidMount to retrieve the full universe of invoices, then generating a subset by date based on user input. Is it necessary to maintain the generated subset in state? Each time invoices are searched for by date, it seems you want to use the full universe returned from the API as your filter starting point. If that's the case, you can just immediately return the result from cleanUpInvoices -- no need for a state update. This would be different, of course, if you needed the invoice subset for further reference or manipulation.

Related

Iam trying to update the state "Experience" but I didn't get updated value. please correct if my code is wrong

am trying to calculate the year of experience so I Initialized state "Experience = 0" and I initialized the current year to the "currentDt" state
constructor(props){
super(props);
this.state = {
CurrentDt : new Date().getFullYear(),
DOJ : 0,
Experience : 0
}
}
Getting user input for "DOJ" state and updating using setstate()
GetJoiningDt = (event) =>{
const DojValue = event.target.value;
this.setState({
DOJ :DojValue
})
}
Below code for subtracting currentYear and DOJ year then trying to update "Experience" state by subtracted value`
calculateExperience = ()=>{
alert("Inside_calculateExperience");
let a = this.state.CurrentDt;
let b = this.state.DOJ;
var ExpValue = a - b; //2021-2015
alert(ExpValue); // 6
this.setState({
Experience : ExpValue //0
})
}
when I alert the value of "ExpValue" it shows value 6 but Inside setstate() its updating 0. and converting states to JSON.
GetAsJSONData = () =>{
console.log("JsonData");
this.calculateExperience();
let JsonData = {
Experience : this.state.Experience
}
let showdata = JSON.stringify(JsonData);
document.querySelector('label').textContent = showdata;
document.write(showdata);
}
render(){
return(
<form>
<input type="text" value={this.state.DOJ} onChange=
{this.GetJoiningDt} placeholder="Year of DOJ" name="fullname" />
<br/>
<button type="submit" onClick={this.GetAsJSONData}>Convert
Json</button><br></br>
<label></label>
</form>
)
}
You need to consider Async and Await.
I made a sandbox version for you.
Please check the link below
https://codesandbox.io/s/mystifying-water-9w238?file=/src/App.js
You need to prevent the page from reloading after the form is submitted as the react state is lost after page reload.
<form id='form'>
<input type='text'>
<button type='submit'>submit</button>
</form>
<script>
document.getElementById('form').onsubmit = e => {
e.preventDefault();
}
</script>
If you don't want to do that, then use localstorage object to store your state and then retain it after the form is submitted.

Filter state that is set from onClick as data- in react

Two main issues.
(1) onClick needs to update two items in my state
(2) I need to filter state #2 to count the number of times a string appears and render it if it equals the length of state#1
More Detail:
I am mapping through my objects and rendering a button for each. I need the onClick to setState of two different attributes so i pass value={item.item} to update state selectedItems , and data-matches={item.matches} to update state of matchesList.
-- note that item.item is a string and item.matches is an array of strings.
When I call onClick to update the state, the value works fine, but the data-matches creates a weird object which i cant iterate over and this is why thats a problem...
I need to map through the data-matches state and count each instance of a string, if the count of that particular string is equal to the length of selectedItems state, then I need to render that.
If this is confusing to you, it's because I am completely lost and new to this. Maybe its worth mentioning that my props are coming from Redux..
Example of some objects for reference
{
'item': 'apple',
'matches': ['beef', 'bacon', 'cheese', 'carrot'],
},
{
'item': 'carrot',
'matches': ['apple', 'bacon', 'goat'],
},
export class Items extends Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [],
firstStep: true,
matchesList: []
};
this.selectItems = this.selectItems.bind(this);
this.filterMatches = this.filterMatches.bind(this);
}
selectItems(event) {
this.setState({
selectedItems: [...this.state.selectedItems, event.target.value],
firstStep: false,
matchesList: [...this.state.matchesList, event.target.dataset.matches]
});
}
Below, in the col 'All", I am mapping through my state selectedItems which is an array, so I can grab the actual object from props which is the same name. I then map over the matches array of that object, and grab the actual object that has the same name as the matches...
in the col 'Cross Referenced", I am mapping over the state updated from the onClick. This is where I need to filter.. I'll show you my attempt at that after this code block.
render() {
return (
<Fragment>
<div className="col">
<div className="row">
<div className="col-5">
<h1>All</h1>
{this.state.selectedItems.map(selected =>
this.props.items.map(item =>
item.item == selected
? item.matches.map(match =>
this.props.items.map(item =>
item.item == match ? (
<div>
<p>{item.item}</p>
<button
key={item.item}
value={item.item}
data-matches={item.matches}
onClick={this.selectItems}
>
Select
</button>
</div>
) : null
)
)
: null
)
)}
</div>
<div className="col-5">
<h1>Cross Referenced</h1>
{this.state.matchesList.map(matches => (
<p>{matches}</p>
))}
</div>
</div>
</div>
)}
</div>
</Fragment>
);
}
}
My attempt at filtering, even though the matchesList is not right.
filterMatches(matchesList, selectedItems) {
let arr1 = matchesList;
let arr2 = selectedItems;
let obj = arr1.reduce((op, inp) => {
let key = inp;
op[key] = op[key] || 0;
op[key]++;
return op;
}, {});
let final = Object.keys(obj).filter(key => {
return obj[key] === arr2.length;
});
return final;
}
in the render
<div className="col-5">
<h1>cross referenced</h1>{this.state.matchesList.filter(this.filterMatches(this.state.matchesList, this.state.selectedItems)).map(matches => (
<p>{matches}</p>
))}
</div>
</div>
I tried out the data-matches thing, and it looks to me like event.target.dataset.matches was coming out to be a string (Not an array of strings, one big CSV string). Try doing it the following way:
class YourComponent extends React.Component {
state = {...};
selectItems = (item, matches) => {
this.setState({
selectedItems: [...this.state.selectedItems, item],
firstStep: false,
matchesList: [...this.state.matchesList, ...matches]
});
}
render() {
return (
{...}
{this.props.items.map(item => (
<button
key={item.item}
value={item.item}
onClick={() => this.selectItems(item.item, item.matches)}
>
Select
</button>
))}
{...}
);
}
}
Actually I think I see what your issue is, the filter is working fine and it's already giving you your array of ["bacon"]. Try getting rid of the outer filter, I don't see a point to it being there.
const YourComponent = props => (
{...}
{this.filterMatches(this.state.matchesList, this.state.selectedItems).map(matches => (
<p>{matches}</p>
))}
{...}
);

React Cannot read property 'getAttribute' of undefined

I get the following error when trying to compile my app TypeError: Cannot read property 'getAttribute' of undefined.
I'm having trouble tracking down why in this line: if (event.target.getAttribute('name') && !sectionsClicked.includes(event.target.getAttribute('name'))) { is error
Here is the main react component
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
sectionsClicked: [],
};
this.handleProgress = this.handleProgress.bind(this);
}
handleProgress = (event) => {
const { progressValue } = this.state;
console.log(progressValue);
const { sectionsClicked } = this.state;
if (event.target.getAttribute('name') && !sectionsClicked.includes(event.target.getAttribute('name'))) {
this.setState({
progressValue: Math.floor((sectionsClicked.length / 77) * 100),
sectionsClicked: sectionsClicked.push(event.target.getAttribute('name')),
});
console.log(sectionsClicked);
}
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div className="text-center mt-5">
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={(event) => this.handleProgress(event)}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
As per the docs:
https://github.com/final-form/react-final-form#onchange-formstate-formstate--void
The expected param passed to onChange is formState not event. Try logging the parameter passed to the function it might have the changes you need.
The <FormSpy> component that originally calls onChange in <Wizard>, which you report upwards to <App>, passes a parameter that is not an event. It's of type FormState.
On that type, the target property is undefined, thus the error you're seeing.
If you see the properties of that object in the documentation, you'll probably get what you want by checking the dirty fields of the form, and finding the associated DOM elements.
In all likelihood, though, you can find a way of achieving the desired behavior without resorting to DOM attributes at all. The very same dirtyFields property has the names you might be looking for.
Also, take a look at your methods and binding. You're actually using three separate approaches to fix the context of your handleProgress function.
For more information, look at this answer:
Can you explain the differences between all those ways of passing function to a component?

Updating a selected object when user changes inputs in a form

When a user clicks a square it becomes currentHtmlObject. I want people to be able to update it's properties in the right sidebar.
I have no idea how to take a single input field and update an object's property that I'm holding in a react-redux state and update the main viewing area DrawingCanvas.
I got kinda close where the info I was entering into the form was activating my reducers and actions. But I couldn't figure out how to distinguish between left and top.
// React
import React from 'react'
export class RightSidebar extends React.Component {
constructor(props) {
super(props)
}
handleChange(evt) {
console.log(evt)
this.props.onUpdateCurrentHtmlObject(evt.target.value)
}
render() {
const { currentHtmlObject } = this.props
return (
<form>
{this.props.currentHtmlObject.id}
<div className="right-sidebar">
<div className="form-group">
<label>Position X</label>
<input
type="number"
name="left"
className="form-control"
value={this.props.currentHtmlObject.styles ? this.props.currentHtmlObject.styles.left : ''}
onChange={this.handleChange.bind(this)} />
</div>
<div className="form-group">
<label>Position Y</label>
<input
type="number"
className="form-control"
value={this.props.currentHtmlObject.styles ? this.props.currentHtmlObject.styles.top : ''}
onChange={this.handleChange.bind(this)} />
</div>
</div>
</form>
)
}
}
RightSidebar.defaultProps = {
currentHtmlObject: {
styles: {
left: null,
top: null
}
}
}
There is no need to distinguish between left and top, let's assume you have an action named update and all it does is to update a selected object's property. Here is what the action method may look like:
updateSelectedObj(id, payload){
return {
type: UPDATE_SELECTED_OBJ,
id: id,
payload: payload
}
}
Here is what your event handler might look like in class RightSidebar:
handleChange(evt) {
// since your top and left input fields have a corresponding name property, evt.target.name will return either `left` or `top`
store.dispatch(updateSelectedObj({styles:{evt.target.name:evt.target.value}})
}
Here is your reducer:
[UPDATE_SELECTED_OBJ]: (state, action) => {
// I assume you have a list of objects in the canvas but only one can
// be selected at a time. and I assume the name of the list is objList
let selectedObj = state.objList.filter(obj => obj.id == action.id)[0]
selectedObj = Object.assign({}, selectedObj, action.payload)
return { objList: state.objList.map(obj => obj.id === action.id? Object.assign({}, obj, selectedObj : obj) }
}
I might suggest simplifying the component itself. Sorry for being brief :). I can update w/ more context when I get some time.
This is a stripped down example, but basically thinking of each "number input" as only needing a value and onChange (emits value, not an event).
You would make use of react-redux's connect so that updateObject is a callback accepting the "patch data" to be merged into the currentObject's state.
/**
* #param currentObject
* #param updateObject An already bound action creator that accepts payload to "update object"
*/
function SideBar({currentObject, updateObject}) {
const {styles} = currentObject;
return (
<div>
<NumberInput
value={styles.left}
onChange={left => updateObject({left})}
/>
<NumberInput
value={styles.top}
onChange={top => updateObject({top})}
/>
</div>
)
}
The connect statement might look something like
const SideBarContainer = connect(
(state, {objectId}) => ({
currentObject: _.find(state.objects, {id}),
}),
(dispatch, {objectId}) => ({
updateObject: data => dispatch(
actions.updateObject(objectId, data)
)
})
)(SideBar);
And the actual usage, maybe something like
<SidebarContainer objectId={currentObjectId} />

How do I change class names for dynamic children?

I want to have the email address validate onBlur and change the class to invalid, so the guest knows. How do I do that with dynamic children? Also, this is my first time playing with React, so feel free suggest better conventions.
https://jsfiddle.net/reactjs/69z2wepo/
var EmailList = React.createClass({
getInitialState: function() {
return { emailInputs: [this.createInput()] };
},
validateEmail: function(event) {
var email = event.target.value;
var re = /^([\w-]+(?:\.[\w-]+)*)#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
return re.test(email);
},
createInput: function() {
return <li><input class="email" onBlur={this.validateEmail} type="text" placeholder="Email Address"/></li>
},
getEmailInputs: function() {
if (this.state.emailInputs.length < this.props.quantity) {
// Add inputs
for(var x = this.state.emailInputs.length; x < this.props.quantity; x++) {
this.state.emailInputs.push(this.createInput())
}
} else {
// Remove inputs
for(var x = this.state.emailInputs.length; x > this.props.quantity; x--) {
this.state.emailInputs.pop()
}
}
return this.state.emailInputs;
},
render: function() {
return <ol>
{this.getEmailInputs()}
</ol>
}
});
React.render(<EmailList quantity="5" />, document.getElementById('container'));
First of all "NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable."
https://facebook.github.io/react/docs/component-api.html
Use this.setState({myarray:[email]}); instead. Loop and add the complete array to the state with setState then render {this.state.myarray}
In fact I would put the quantity in the state and add the corresponding number of div to an array and render this array (without stocking this array in the state, just the quantity prop).
To answer your initial question I would just change the class using jquery(event.target)... because I'm lazy. But I suspect using the state to store the state ;) would be cleaner. (An array of the state of each div binded with the keys, btw react will probably ask for keys)
I hope it helps
The React convention is to use render() to generate content which is a reflection of your component's current state. As such, the bits which can change are usually modelled as state which is either passed in as props or stored in the component's own state.
In this case you need some state for each each email input, so at a minimum you'll need value and valid state for each one. Since there are a list of inputs, the state could be a list of objects with value and valid properties:
var DEFAULT_FIELD = {name: '', valid: null}
// ...
getInitialState() {
return {
fields: Array(this.props.quantity).fill(DEFAULT_FIELD)
}
},
With this state, you could render the fields something like this:
render() {
return <ol>
{this.state.fields.map((field, index) => <li>
<input className={'email' + field.valid === false ? ' invalid' : ''}
key={index}
value={field.value}
placeholder="Email Address" />
</li>)}
</ol>
}
This will be read-only because you haven't provided a way for input to update the state. You need to know which index the edited field corresponds to in the array in your state:
handleChange(index, event) {
// Update this.state.fields[index] with new value
},
// ...
render() {
return <ol>
{this.state.fields.map((field, index) => <li>
<input className={'email' + field.valid === false ? ' invalid' : ''}
key={index}
value={field.value}
placeholder="Email Address"
onChange={this.handleChange.bind(this, index) />
</li>)}
</ol>
}
You'll also need the index for onBlur validation:
handleBlur(index, event) {
// Update this.state.fields[index] with new validation status
},
// ...
render() {
return <ol>
{this.state.fields.map((field, index) => <li>
<input className={'email' + field.valid === false ? ' invalid' : ''}
key={index}
value={field.value}
placeholder="Email Address"
onChange={this.handleChange.bind(this, index)
onBlur={this.handleChange.bind(this, index) />
</li>)}
</ol>
}
I have a runnable full implementation of this which uses React's update() immutability helper to avoid mutating the state:
https://gist.github.com/insin/407de6bb93dfe077433f
If you download and extract this Gist and run npm install, running npm start will start a webpack dev server on http://localhost:3000 which supports hot reloading so you can play about with it - e.g. tweak the quantity prop being passed from App.js.

Resources