compare the first state to the last state reactjs - reactjs

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 } );

Related

React - selected value from list is undefined

I have a React component SelectObj which gets data from my api and passes it to a component DynamicSelect. The DynamicSelect creates a selection list. When the user selects the option, the selected value should be returned to SelectObj.
DynamicSelect.js:
export class DynamicSelect extends Component {
constructor(props) {
super(props);
}
handleChange = event => {
let selectedValue = event.value;
this.props.onSelectChange(selectedValue);
};
render() {
let arrayOfData = this.props.arrayOfData;
let options = arrayOfData.map(data => (
<option key={data.id} value={data.objective_name}>
{data.objective_name}
</option>
));
//console.log(options);
return (
<select
name="customSearch"
className="custom-search-select"
onChange={this.handleChange}
>
<option>Select item</option>
{options}
{console.log(options)}
</select>
);
}
}
export default DynamicSelect;
SelectObj.js
import { getObjectives } from "../../actions/assessments";
import { DynamicSelect } from "./DynamicSelect";
export class SelectObj extends Component {
static PropTypes = {
objectives: PropTypes.array.isRequired,
getObjectives: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.state = {
selectedValue: "Nothing selected"
};
}
handleSelectChange = selectedValue => {
this.setState({
selectedValue: selectedValue
});
};
componentDidMount() {
this.props.getObjectives();
}
render() {
let arrayOfData = this.props.objectives;
return (
<Fragment>
<DynamicSelect
arrayOfData={arrayOfData}
onSelectChange={this.handleSelectChange}
/>
{console.log(this.state.selectedValue)}
<div>Selected Value: {this.state.selectedValue}</div>
</Fragment>
);
}
}
const mapStateToProps = state => ({
objectives: state.objectives.objectives
});
export default connect(mapStateToProps, { getObjectives })(SelectObj);
The console.log in DynamicSelect prints out the options that I expect, whereas the console.log in SelectObj is undefined. How do I get this selected value from DynamicSelect?
I thought I maybe have to bind my handleChange with this.handleChange = this.handleChange.bind.(this) but that didn't fix this problem (maybe I still should be binding the handleChange?).
<DynamicSelect
arrayOfData={arrayOfData}
onSelectChange={(e) => this.handleSelectChange(e)}
/>
Try to pass your function like this and see whether the result is getting updated.

delete array item cause error perform a React state update on an unmounted component

I have google for this problem.
The solution is put is_Mounted in componentWillMount and unmount, but this will let deletion not happened.
I would appreciate it greatly if you could let me know how to deal with this problem which I deal with for about a week.
My pseudocode...
IRPage
class IRPage extends Component {
state = {
companyList: [],
contractListOfList: [],
};
addCompanyArr = (newCompany) => {
this.setState(
state => {
const list = state.companyList.push(newCompany);
return {
list,
};
}
)
};
addContractArr = (newContractList) => {
this.setState(
state => {
const list = state.contractListOfList.push(newContractList);
return {
list,
};
}
);
}
render() {
return (
// pass array method.....to IRContent
)
}
}
IRContent
class IRContent extends React.Component {
constructor(props) {
super(props);
}
addCompany = () => {
const companyNode = <IRCompany setContractArr={this.props.setContractArr} contractList={newContractList} companyList={this.props.companyList} setCompanyArr={this.props.setCompanyArr} addContractArr={this.props.addContractArr}/>;
this.props.addCompanyArr(companyNode);
var newContractList = [];
this.props.addContractArr(newContractList);
}
render() {
return(
<div>
{
this.props.companyList.map((element, index) => {
return <div key={"myCompanyKey_"+index+"_"+this.props.companyList.length} id={index}>{element}</div>;
})
}
<button color="primary" onClick = {this.addCompany}>
add company
</button>
</div>
);
}
}
IRCompany
class IRCompany extends React.Component {
constructor(props) {
super(props);
}
state = {
contractList: this.props.contractList,
};
deleteCompany = event => {
event.preventDefault();
// var targetID = ; I'm sure this is correct in original code
this.props.companyList.splice(targetID, 1);
this.props.contractListOfList.splice(targetID, 1);
this.props.setCompanyArr();
};
addContract = event => {
event.preventDefault();
var newContract = <IRContract contractList={this.state.contractList} setContractList={this.setContractList} setCompanyArr={this.props.setCompanyArr} contractListOfList={this.props.contractListOfList} setContractArr={this.props.setContractArr}/>;
this.props.contractList.push(newContract);
this.setContractList();
};
setContractList = () => {
this.setState(
state => {
const list = state.contractList;
return {
list,
};
}
)
}
render() {
return(
<div>
{
this.state.contractList.map((element, index) => {
return <tbody key={"myContractKey" + index + "_" +this.state.contractList.length} id={index}>{element}</tbody>;
})
}
</div>
);
}
}
IRContract
class IRContract extends React.Component {
constructor(props) {
super(props);
}
deleteMainContract = event => {
event.preventDefault();
this.props.contractList.splice(contractNum, 1);
this.props.setContractList();
};
render() {
return(
<React.Fragment>
<button name="deleteMainContract" type="button" onClick={this.deleteMainContract} style={{borderRadius: "6px", fontSize: "16px"}}>delete</button>
</React.Fragment>
);
}
}
If I click add company, add contract, and delete contract, it will work correctly.
However, if I click add company, add contract, add another company, and delete contract, it will fail.
Error message is "Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.", but I never write something like componentWillUnmount.

delete three level component(a component have an array of component, each have an array of compoent)

I would appreciate it greatly if you could let me know how to deal with this problem.
I hava a page component which has an array of company component, and each company has an array of contract.
If I delete any company, every company component will re-render, so I cant put array of contract under each company state, so I put it under page component state.
The question is If I delete one company, how can I correctly re-render all contracts under each component.
Thank you for reading my problem, and sorry for my poor English:(
Error Message is "TypeError: Cannot read property 'contractList' of undefined"
My page code is...
class IRPage extends Component {
// // initialize our state
state = {
companyList: [],
};
addCompanyArr = (newCompany) => {
this.setState(
state => {
const list = state.companyList.push(newCompany);
return {
list,
};
}
)
};
addContractArr = (index, newContract) => {
this.setState(
state => {
const list = state.companyList[index].contractList.push(newContract);
return {
list,
};
}
);
}
setCompanyArr = () => {
this.setState(
state => {
const list = state.companyList;
return {
list,
};
}
)
};
render() {
return (
<div className="container m-5">
<IRContent companyList={this.state.companyList} setCompanyArr={this.setCompanyArr} addCompanyArr={this.addCompanyArr} addContractArr={this.addContractArr}></IRContent>
</div>
)
}
}
export default IRPage;
My content code is ...
class IRContent extends React.Component {
constructor(props) {
super(props);
}
addCompany = () => {
const companyNode = <IRCompany companyList={this.props.companyList} setCompanyArr={this.props.setCompanyArr} index={this.props.companyList.length} addContractArr={this.props.addContractArr}/>;
const newCompany = {
node: companyNode,
contractList: [],
};
this.props.addCompanyArr(newCompany);
}
render() {
return(
<div className="container col-sm-12 justify-content-center">
<h1 align="center">data</h1>
<hr/>
{
this.props.companyList.map((element, index) => {
return <div key={"myCompanyKey_"+index+"_"+this.props.companyList.length} id={index}>{element.node}</div>;
})
}
<button color="primary" onClick = {this.addCompany}>
add new company
</button>
</div>
);
}
}
export default IRContent;
My company code is...
class IRCompany extends React.Component {
constructor(props) {
super(props);
}
deleteCompany = event => {
event.preventDefault();
var targetID = event.target.parentElement.parentElement.parentElement.parentElement.parentElement.getAttribute("id")
this.props.companyList.splice(targetID, 1);
this.props.setCompanyArr();
};
addContract = event => {
event.preventDefault();
var newContract = <IRContract/>;
var targetID = event.target.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.getAttribute("id");
this.props.addContractArr(targetID, newContract);
this.forceUpdate();
};
render() {
return(
<div>
{
this.props.companyList[this.props.index].contractList.map((element, index) => {
return <React.Fragment key={"myContractKey" + index + "_" +this.props.companyList[this.props.index].contractList.length}>{element}</React.Fragment>;
})
}
<button color="primary" onClick = {this.addContract}>主約</button>
</div>
);
}
}
export default IRCompany;
I can successively add company and contract, but there are some problem on deleing.
Thank you for reading my problem, and sorry for my poor English:(

How to show validation message on <TagsInput> react premade component on unique value

I have an input tag component from react-tagsinput as follows:
const onTagChange = (tags) => {
const noDuplicateTags = tags.filter((v, i) => tags.indexOf(v) === i);
const duplicateEntered = tags.length !== noDuplicateTags.length;
if (duplicateEntered) {
onTagChange(tags);
console.log('duplicate');
}
onTagChange(noDuplicateTags);
};
function TagContainer({
tags,
}) {
return (
<div>
<Header>Meta:</Header>
<TagsInput value={tags} onChange={onTagChange} />
</div>
);
}
TagContainer.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
};
TagContainer.defaultProps = {
tags: [],
};
export default TagContainer;
and the implementation on the onTagChange method which is passed as a prop to the <TagContainer> component in another component.
export class Modal extends React.Component {
...
...
onTagChange = (tags) => {
this.props.onTagChange(tags);
}
...
...
render() {
return(
<TagContainer
tags={tags}
onTagChange={this.onTagChange}
/>
);
}
}
Problem: onlyUnique prop in the <TagsInput> component is set to true to avoid duplicate entries. But I need to display an error message saying "duplicate values" as soon as user enters a duplicate value. How can this be done especially on the third party component.
I think you're going to have to handle dealing with duplicates in your component because you are getting no feedback from <TagInput /> component.
At a higher level, I would do something like this
class Example extends React.Component {
constructor() {
super();
this.state = {
showDuplicateError: false
};
}
handleTagChange(tags) {
const uniqueTags = removeDuplicates(tags);
const duplicateEntered = tags.length !== uniqueTags.length;
if (duplicateEntered) {
this.showDuplicateError();
}
// add unique tags regardless, as multiple tags could've been entered
const { onTagChange } = this.props;
onTagChange(uniqueTags);
}
showDuplicateError() {
this.setState({
showDuplicateError: true
});
}
render() {
const { showDuplicateError } = this.state;
const { tags } = this.props;
return (
<React.Fragment>
{ showDuplicateError && <div>Duplicate entered</div>}
<TagsInput value={ tags } onTagChange={ this.handleTagChange } />
</React.Fragment>
);
}
}

How can I pass the state from parent component to a child to use in an API call?

I have a React app that queries an API that I built in Spring Boot. The React side should query the database to find a number of Activity objects to populate one Itinerary object. I want to keep track of the Ids of the activities so the app doesn’t return the same one more than once. Here’s how I set it up:
The Itinerary component has an array called "activities" in its state that will hold the ids. This starts out as empty. The Itinerary component also has a function called UpdateActivities() that updates the State with a new id when an activity is added.
UpdateActivities is passed as a callback to the child component Activity as props. The activities array in the state is turned into a string (because I'll be using this in the API call) and also passed as props to the Activity component.
In the Activity component, the callback function is further passed as a prop to the ActivityDetails component and the string of ids is further passed to ActivityDetails as well.
The ActivityDetails component makes the actual API call to my Spring Boot application and uses the callback function to update the State on the parent component. I attempted to save the string of ids to a constant to use in my API call. I want the string to go at the end of the call, so my app will know to skip over any activities with those Ids (currently hard-coded to 1).
The API call doesn't work if I replace 1 with the constant string of ids, because it shows up as empty. Is this an async issue? Is there a way to make it so the React app doesn't make the API call on ActivityDetails until the State is updated on the parent component?
Itinerary Component
class Itinerary extends Component {
constructor(props) {
super(props);
this.state = {
template: null,
activities: []
};
}
updateActivities = (id) => {
const activityList = [...this.state.activities, id];
this.setState({
activities: activityList
})
}
componentDidMount = () => {
const {duration, travelerType, pace} = this.props.userAnswers;
const transport = this.props.userAnswers.internalTravel.sort().join(', ');
const TEMPLATES_URL = `http://localhost:8080/templates/duration/${duration}/travelers/${travelerType}/pace/${pace}/transport/${transport}`;
axios.get(TEMPLATES_URL)
.then(response => {
this.setState({
template: response.data[0].content
});
})
.catch(function (error) {
console.log(error.message);
});
}
render() {
let cities;
if (this.state.template === null) {
cities = <div><h3>Loading...</h3></div>;
} else {
let data = this.state.template.split(", ");
cities = data.map((city, index) => {
return (
<section>
<City
key={index}
day={index + 1}
city={city}
/>
<Activity
key={`${city}${index}`}
day={index + 1}
lastDay={data.length}
city={city}
userAnswers={this.props.userAnswers}
updateActivityState={this.updateActivities}
activityList={this.state.activities.join(', ')}
/>
</section>
)
});
}
return (
<div>
<div className="row">
<div className="col s9">
<h3>{cities}</h3>
</div>
</div>
</div>
);
}
}
export default Itinerary;
Activity Component
class Activity extends Component {
render() {
let city = this.props.city;
if(this.props.day === 1) {
return (
<ActivityDetails
userAnswers={this.props.userAnswers}
city={city}
timeOfDay="evening"
handleActivityList={this.props.updateActivityState}
activities={this.props.activityList}/>
);
} else if (this.props.day === this.props.lastDay) {
return (
<ActivityDetails
userAnswers={this.props.userAnswers}
city={city} timeOfDay="morning"
handleActivityList={this.props.updateActivityState}
activities={this.props.activityList} />
);
} else {
return(
<section>
<ActivityDetails
userAnswers={this.props.userAnswers}
city={city}
timeOfDay="morning"
handleActivityList={this.props.updateActivityState}
activities={this.props.activityList} />
<ActivityDetails
userAnswers={this.props.userAnswers}
city={city}
timeOfDay="afternoon"
handleActivityList={this.props.updateActivityState}
activities={this.props.activityList} />
<ActivityDetails
userAnswers={this.props.userAnswers}
city={city}
timeOfDay="evening"
handleActivityList={this.props.updateActivityState}
activities={this.props.activityList} />
</section>
);
}
}
}
export default Activity;
ActivityDetails Component
class ActivityDetails extends Component {
constructor(props) {
super(props);
this.state = {
activity: 'Loading...',
};
}
componentDidMount() {
const {travelParty, budget, pace, sites} = this.props.userAnswers;
const cityTravel = this.props.userAnswers.cityTravel.sort().join(', ');
const interests = this.props.userAnswers.interests.sort().join(', ');
const entertainment = this.props.userAnswers.entertainment.sort().join(', ');
const currentActivities = this.props.activities;
console.log(`currentActivities: ${currentActivities}`);
const city = this.props.city;
const timeOfDay = this.props.timeOfDay;
const ACTIVITY_URL = `http://localhost:8080/filter/${city}/${timeOfDay}/${travelParty}/${budget}/${pace}/${sites}/${cityTravel}/${interests}/${entertainment}/1`;
console.log(ACTIVITY_URL);
axios.get(ACTIVITY_URL)
.then(response => {
const newActivity = response.data.content;
const updatedActivityId = response.data.id;
this.props.handleActivityList(updatedActivityId);
this.setState({
activity: newActivity,
});
})
.catch(function(error) {
console.log(error.message);
});
}
render () {
return (
<div>{this.state.activity}</div>
);
}
}
export default ActivityDetails;
Revised with ComponentDidMount:
class ActivityDetails extends Component {
constructor(props) {
super(props);
this.state = {
activity: 'Loading...',
};
}
getActivity() {
const {travelParty, budget, pace, sites} = this.props.userAnswers;
const cityTravel = this.props.userAnswers.cityTravel.sort().join(', ');
const interests = this.props.userAnswers.interests.sort().join(', ');
const entertainment = this.props.userAnswers.entertainment.sort().join(', ');
const city = this.props.city;
const timeOfDay = this.props.timeOfDay;
const currentActivities = this.props.activities;
console.log(`currentActivities: ${currentActivities}`);
const ACTIVITY_URL = `http://localhost:8080/filter/${city}/${timeOfDay}/${travelParty}/${budget}/${pace}/${sites}/${cityTravel}/${interests}/${entertainment}/${currentActivities}`;
console.log(ACTIVITY_URL);
axios.get(ACTIVITY_URL)
.then(response => {
const newActivity = response.data.content;
const updatedActivityId = response.data.id;
this.props.handleActivityList(updatedActivityId);
this.setState({activity: newActivity});
})
.catch(function(error) {
console.log(error.message);
});
}
componentDidMount() {
this.getActivity();
}
componentDidUpdate(prevProps) {
if(prevProps.activities !== this.props.activities) {
this.getActivity();
}
}
render () {
return (
<div>{this.state.activity}</div>
);
}
}
export default ActivityDetails;
The API call doesn't work if I replace 1 with the constant string of ids, because it shows up as empty. Is this an async issue?
This makes sense, yes. It's likely doing your Itinerary's fetch at roughly the same time as your ActivityDetails' fetch, so the state.activities there is still an empty array (ergo empty string once you've mutated it and passed it down).
Is there a way to make it so the React app doesn't make the API call on ActivityDetails until the State is updated on the parent component?
Yes, you can use other lifecycle methods to facilitate this. In your case, you probably want componentDidUpdate.
You could do something like:
class ActivityDetails extends Component {
constructor(props) {
super(props);
this.state = {
activity: 'Loading...',
};
}
doMyGet(values) {
const ACTIVITY_URL = `http://localhost:8080/filter/${city}/${timeOfDay}/${travelParty}/${budget}/${pace}/${sites}/${cityTravel}/${interests}/${entertainment}/1`;
console.log(ACTIVITY_URL);
axios.get(ACTIVITY_URL)
.then(response => {
const newActivity = response.data.content;
const updatedActivityId = response.data.id;
this.props.handleActivityList(updatedActivityId);
this.setState({
activity: newActivity,
});
})
.catch(function(error) {
console.log(error.message);
});
}
componentDidMount() {
const {travelParty, budget, pace, sites} = this.props.userAnswers;
const cityTravel = this.props.userAnswers.cityTravel.sort().join(', ');
const interests = this.props.userAnswers.interests.sort().join(', ');
const entertainment = this.props.userAnswers.entertainment.sort().join(', ');
const currentActivities = this.props.activities;
console.log(`currentActivities: ${currentActivities}`);
const city = this.props.city;
const timeOfDay = this.props.timeOfDay;
this.doMyGet(currentActivities)
}
componentDidUpdate(prevProps) {
if (this.props.activities !== prevProps.activities) {
this.doMyGet(this.props.activities)
}
}
render () {
return (
<div>{this.state.activity}</div>
);
}
}
export default ActivityDetails;

Resources