How to independently delete dynamically-added input fields in ReactJS - reactjs

I'm trying to independently delete dynamic inputs in a form in React. I have a user attribute group, and then user attribute children. I need to be able to dynamically add new user attribute groups and children, but then delete those fields without deleting ALL of the child attributes.
Right now, when I delete a child attribute, it deletes one from EACH user attribute group.
I have a working fiddle here that shows my code: https://codesandbox.io/embed/23kr654w80
import React, { Component } from "react";
import { Button, Input, Row, Col, Form, FormGroup, Label } from "reactstrap";
class OfferCriteria extends Component {
constructor(props) {
super(props);
this.state = {
single: "",
attributeSingle: [{ single: "" }],
child: "",
attributeChild: [{ child: " " }]
};
}
handleNameChange = event => {
this.setState({
name: event.target.value
});
};
handleAddSingleAttribute = () => {
this.setState({
attributeSingle: this.state.attributeSingle.concat([{ name: "" }])
});
};
handleRemoveSingleAttribute = idx => () => {
this.setState({
attributeSingle: this.state.attributeSingle.filter(
(s, sidx) => idx !== sidx
)
});
};
handleAddChildAttribute = () => {
this.setState({
attributeChild: this.state.attributeChild.concat([{ child: "" }])
});
};
handleRemoveChildAttribute = idz => () => {
this.setState({
attributeChild: this.state.attributeChild.filter(sidz => idz !== sidz)
});
};
render() {
return (
<div>
<Row>
<Col lg="10">
<hr />
</Col>
<Col lg="2" className="float-right">
<Button color="success" onClick={this.handleAddSingleAttribute}>
Add Attribute Group
</Button>
</Col>
</Row>
{this.state.attributeSingle.map(() => (
<div>
<br />
<Row>
<Col lg="2">
<Label>User Attributes</Label>
</Col>
<Col lg="3" className="float-left">
<FormGroup check inline>
<Input
className="form-check-input"
type="radio"
id="includeUserAttributes"
name="inline-radios"
value="includeUserAttributes"
/>
<Label
className="form-check-label"
check
htmlFor="inline-radio1"
>
Include
</Label>
</FormGroup>
<FormGroup check inline>
<Input
className="form-check-input"
type="radio"
id="excludeUserAttributes"
name="inline-radios"
value="excludeUserAttributes"
/>
<Label
className="form-check-label"
check
htmlFor="inline-radio2"
>
Exclude
</Label>
</FormGroup>
</Col>
<Col lg="4">
<Input
type="text"
name="text-input"
placeholder="This is parent attribute"
/>
</Col>
</Row>
<br />
<Row>
<Col lg="3">
{this.state.attributeChild.map(() => (
<div className="shareholder">
<Input
type="text"
name="text-input"
placeholder="This is child attribute"
/>
</div>
))}
</Col>
<Col lg="3" className="float-right">
{this.state.attributeChild.map(() => (
<div className="shareholder">
<Button
color="primary"
onClick={this.handleAddChildAttribute}
>
Add Attribute Child
</Button>
<br />
</div>
))}
</Col>
<Col lg="3" className="float-right">
{this.state.attributeChild.map(idz => (
<div className="shareholder">
<Button
color="danger"
onClick={this.handleRemoveChildAttribute(idz)}
>
Remove Attribute Child
</Button>
<br />
</div>
))}
</Col>
</Row>
<hr />
</div>
))}
</div>
);
}
}
export default OfferCriteria;
I need these child attributes to delete ONLY in their parent attribute group, instead of deleting all of them from all the attribute groups.

There are a couple of things going wrong with your code, but I'll focus on your initial question.
The problem is that you use the same array of child for all your groups. In order to be correct, you should include the attributeChild state into the attributeSingle objects :
{
attributeSingle: [
{
single: "",
attributeChild: [
{
child: " "
}
]
}
]
}
That way, children remain independent between groups.

Related

Child component renders even though its being memoized

Child component renders even though props are not altered of it.
Following is the parent component
import Child from "./Child";
function Parent({
selected,
show,
setShow,
}) {
const [isRunNow, setIsRunNow] = useState(true);
const [isNotifyMe, setIsNotifyMe] = useState(false);
const handleNotify = useCallback(() => {
setIsNotifyMe(!isNotifyMe);
}, [isNotifyMe]);
const handleSchedule = useCallback(() => {
setIsRunNow(!isRunNow);
}, [isRunNow]);
const WindowForm = () => {
return (
<div>
<Row>
<Col span={12}>
<label className={styles.labelWeight}> Name : </label>
<input
type="text"
value={selected.name}
readOnly={true}
className={styles.input}
/>
<br></br>
<br></br>
<label className={styles.labelWeight}>Description : </label>
<input
type="text"
value={selected.description}
readOnly={true}
className={styles.input}
></input>
<br></br>
<br></br>
<input
type="checkbox"
name="runImm"
id="runImm"
checked={isRunNow}
onChange={handleSchedule}
></input>
<label> Schedule as soon as possible</label>
</Col>
<Col span={12}>
<input
type="checkbox"
name="notifyProcess"
id="notifyProcess"
checked={isNotifyMe}
onChange={handleNotify}
></input>
<label> Notify me when this process ends</label>
<br></br>
<br></br>
<label>Submission Notes : </label>
<textarea
name="notes"
id="notes"
rows="4"
cols="50"
disabled={!isNotifyMe}
></textarea>
</Col>
</Row>
<Row>
<Col span={24}>
<Child isRunNow={isRunNow} />
</Col>
</Row>
</div>
);
};
return (
<Modal
visible={show}
width={800}
centered
maskClosable={false}
onCancel={() => setShow(false)}
title="JKM"
>
<WindowForm />
</Modal>
);
}
Child component is as follows:
import Uploader from "./Uploader";
import Downloader from "./Downloader";
const { TabPane } = Tabs;
const areEqual = (prevProps, nextProps) => {
console.log("passed here"); // THIS IS NEVER LOGGED!!
return true;
};
function Child({ isRunNow }) {
console.log(
`Rendering Childe component...isRunNow value : ${isRunNow}`
);
return (
<div>
<Tabs defaultActiveKey="ka">
<TabPane tab="ka" key="ka">
Panel
</TabPane>
<TabPane tab="sa" key="sa" disabled={isRunNow}>
<Downloader />
</TabPane>
<TabPane tab="da" key="da">
<Uploader />
</TabPane>
</Tabs>
</div>
);
}
export default React.memo(Child, areEqual);
When I check or uncheck check box Notify me, the child component Child re-renders every time. It seems props are not equal and hence its re-rending. I could not figure out where its going wrong.
Please suggest where i m doing wrong.
I suggest to you to separate the WindowForm to a component it's seems that this is the problem.
when I use it as a component the memo start to work
I think is due to this is a function that return component so it's render it no matter what.
the solution:
move WindowForm out side of the component and create a new one with it and then call it and it will work fine
const WindowForm = () => {
return (
<div>
<Row>
<Col span={12}>
<label className={styles.labelWeight}> Name : </label>
<input
type="text"
value={selected.name}
readOnly={true}
className={styles.input}
/>
<br></br>
<br></br>
<label className={styles.labelWeight}>Description : </label>
<input
type="text"
value={selected.description}
readOnly={true}
className={styles.input}
></input>
<br></br>
<br></br>
<input
type="checkbox"
name="runImm"
id="runImm"
checked={isRunNow}
onChange={handleSchedule}
></input>
<label> Schedule as soon as possible</label>
</Col>
<Col span={12}>
<input
type="checkbox"
name="notifyProcess"
id="notifyProcess"
checked={isNotifyMe}
onChange={handleNotify}
></input>
<label> Notify me when this process ends</label>
<br></br>
<br></br>
<label>Submission Notes : </label>
<textarea
name="notes"
id="notes"
rows="4"
cols="50"
disabled={!isNotifyMe}
></textarea>
</Col>
</Row>
<Row>
<Col span={24}>
<Child isRunNow={isRunNow} />
</Col>
</Row>
</div>
);
};
this component and his state to a new component and everything will start to work

When i change another form element autocomplete value automatically cleared in Formik React js

The form has two elements.
When I select autocomplete value from emal element and then try to input a text value to titl field, the autocomplete value is automatically changed to empty.
What should I do to fix this issue?
I have tried to change formik initialValues using states but it's not working.
sorry for the language issue. Thanks in advance!
class TicketNew extends React.Component{
state = {
clearForm:false,
spinner:false,
closeForm:false,
emailsugges:[],
}
loadAlldata() {
this.setState({
spinner:false,
})
axios.post(baseUrl+'/api/load_company_list')
.then(res => {
const comanyList = res.data;
const emls = comanyList.emls.map(function(item, i){
return {
value:item.tci, title:item.tcc
}
})
this.setState({
emailsugges:emls
})
})
this.setState({
spinner:false,
})
};
componentDidMount(){
this.loadAlldata();
};
render(){
return(
<React.Fragment>
<Formik
initialValues={{ emal: "", titl: "" }}
validationSchema={formSchema}
>
{
({ errors,
touched,
handleSubmit,
isSubmitting,
handleBlur,
values,
resetForm
}) => (
<div>
<Form onSubmit={handleSubmit}>
<Card>
<CardHeader></CardHeader>
<CardBody>
<Row>
<Col md="5" sm="12">
<FormGroup row className="position-relative">
<Col md="4">
<span>Title</span>
</Col>
<Col md="8">
<Field
type="text"
name="titl"
id="titl"
className={`
form-control ${errors.titl && touched.titl && "is-invalid"}
`}
onBlur={handleBlur('titl')}
/>
{errors.titl &&
touched.titl ? (
<div className="invalid-tooltip mt-25">
{errors.titl}
</div>
) : null}
</Col>
</FormGroup>
</Col>
<Col md="2" sm="12"></Col>
<Col md="5" sm="12">
<FormGroup row className="position-relative"
style={{display:rqst!="1"?'none':''}}
>
<Col md="4">
<span>Email Address</span>
</Col>
<Col md="8">
<Field name="emal"
component={ ({field, form}) =>
<AutoComplete
type="email"
name="emal"
id="emal"
suggestions={this.state.emailsugges}
value={
this.state.emailsugges ?
this.state.emailsugges.find(option =>
option.value === field.value)
: ''}
className={`
form-control ${errors.emal && touched.emal && "is-invalid"}
`}
filterKey="title"
suggestionLimit={4}
/>}
/>
{errors.emal &&
touched.emal ? (
<div className="invalid-tooltip mt-25">
{errors.emal}
</div>
) : null}
</Col>
</FormGroup>
</Col>
</Row>
</CardBody>
</Card>
</Form>
</div>
)}
</Formik>
</React.Fragment>
)
}
};
export default TicketNew;

React useState hook does not shows correct state of array

I have two functional components and from parent component I am creating set of controls dynamically. Based on each item created I want to delete them but every time last one is getting deleted. For example three rows created when I delete second or first on,last one was getting deleted.
Education.jsx
function Education(props) {
const blankEdu = { id: 0, name: "", percentage: "", year: "" };
const [eduState, setEduState] = useState([{ ...blankEdu }]);
const addEducation = () => {
setEduState([...eduState, { ...blankEdu }]);
};
function handleRemove(index) {
console.log(index);
if (eduState.length != 1) {
const updatedEdu = [...eduState];
updatedEdu.splice(index, 1);
setEduState([...updatedEdu]);
}
}
const handleEducationChange = (index, e, c) => {
const updatedEdu = [...eduState];
updatedEdu[index][c] = e.target.value;
updatedEdu[index]["id"] = index;
setEduState(updatedEdu);
};
return (
<div>
<div className="shadow p-3 mb-5 bg-white rounded">
Final Step: Education
</div>
{eduState.map((val, idx) => (
<div
key={idx}
>
<EducationInput
key={`edu-${idx}`}
idx={idx}
handleEducationChange={handleEducationChange}
/>
{eduState.length > 1 ? (
<Button variant="danger" onClick={() => handleRemove(idx)}>
Remove Course
</Button>
) : null}
</div>
))}
<Button variant="outline-info" onClick={addEducation}>
Add New Degree
</Button>
</div>
);
}
export default Education;
EducationInput.jsx
const EducationInput = ({ idx, handleEducationChange }) => {
return (
<div key={`edu-${idx}`} id={`edu-${idx}`}>
<span className="border border-success">
<Form>
<Form.Group as={Row}>
<Form.Label column sm={3}>
{`Course #${idx + 1}`}:
</Form.Label>
<Col sm={5}>
<input
type="text"
onChange={e => handleEducationChange(idx, e, "name")}
/>
</Col>
</Form.Group>
<Form.Group as={Row}>
<Form.Label column sm={3}>
Passing Year:
</Form.Label>
<Col sm={5}>
<input
type="text"
onChange={e => handleEducationChange(idx, e, "year")}
/>
</Col>
</Form.Group>
</Form>
</span>
</div>
);
};
export default EducationInput;
I checked and verified value of updatedEdu by printing on console. It is giving correct output on console but setEduState function does not updating properly on UI, don't know why.
You are depending the index of the item, but indexes are changing when you add or remove elements, so they are not reliable.
You need to generate an automatic unique id when creating a new education.
For example uuid package is popular for this task.
I refactored your code a little bit to make it work:
Education:
import React, { useState } from "react";
import EducationInput from "./EducationInput";
import Button from "react-bootstrap/Button";
import uuidv4 from "uuid/v4";
function Education(props) {
const blankEdu = { id: "", name: "", percentage: "", year: "" };
const [eduState, setEduState] = useState([{ ...blankEdu }]);
const addEducation = () => {
setEduState([...eduState, { ...blankEdu, id: uuidv4() }]);
};
function handleRemove(id) {
console.log(id);
if (eduState.length > 1) {
const updatedEdus = eduState.filter(edu => edu.id !== id);
setEduState(updatedEdus);
}
}
const handleEducationChange = (id, field, value) => {
console.log(field, value);
let updatedEducations = eduState.map(edu => {
if (edu.id === id) return edu;
edu[field] = value;
return edu;
});
setEduState(updatedEducations);
};
return (
<div>
<div className="shadow p-3 mb-5 bg-white rounded">
Final Step: Education
</div>
{eduState.map(val => (
<div key={val.id}>
<EducationInput
key={`edu-${val.id}`}
idx={val.id}
handleEducationChange={handleEducationChange}
/>
{eduState.length > 1 ? (
<Button variant="danger" onClick={() => handleRemove(val.id)}>
Remove Course
</Button>
) : null}
</div>
))}
<Button variant="outline-info" onClick={addEducation}>
Add New Degree
</Button>
<br />
<br />
Educations in json:{JSON.stringify(eduState)}
</div>
);
}
export default Education;
EducationInput
import React from "react";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
const EducationInput = ({ idx, handleEducationChange }) => {
return (
<div key={`edu-${idx}`} id={`edu-${idx}`}>
<span className="border border-success">
<Form>
<Form.Group as={Row}>
<Form.Label column sm={3}>
{`Course #${idx + 1}`}:
</Form.Label>
<Col sm={5}>
<input
type="text"
name="name"
onChange={e =>
handleEducationChange(idx, e.target.name, e.target.value)
}
/>
</Col>
</Form.Group>
<Form.Group as={Row}>
<Form.Label column sm={3}>
Passing Year:
</Form.Label>
<Col sm={5}>
<input
type="text"
name="year"
onChange={e =>
handleEducationChange(idx, e.target.name, e.target.value)
}
/>
</Col>
</Form.Group>
</Form>
</span>
</div>
);
};
export default EducationInput;
Codesandbox

react is editing all array objects instead of just one array object

Currently a user can enter first, lastname, and employee id. When it submits it renders an iteration of employees with the first, last, and employee id.
The problem is when you click edit, it edits the data but it will edit all of the fields for every object in the employees iteration.
How can I fix the code so that it will edit just that particular object within that iteration.
Home component
........
class Login extends Component {
constructor(props) {
super(props);
this.state = {
activeTab: '3',
firstName: '',
lastName: '',
employeeID: '',
employees: [],
Edit: false,
updatedFirst: '',
updatedLast:'',
updatedEmployeeID: ''
};
}
toggle = (tab) => {
if(this.state.activeTab !== tab){
this.setState({
activeTab: tab
})
}
}
onSubmit = (e) => {
e.preventDefault();
const {firstName, lastName, employeeID} = this.state
const ourForm ={
firstName: firstName,
lastName: lastName,
employeeID: employeeID,
// we need an id to so that it can be edited properly
}
this.setState({
employees: [...this.state.employees,ourForm]
}, () => {
console.log(this.state.employees)
})
}
onChange = (e) => {
e.preventDefault()
// e.preventDefault();
this.setState({
[e.target.name]: e.target.value,
});
}
updatedChange = (e) => {
e.preventDefault()
// e.preventDefault();
this.setState({
[e.target.name]: e.target.value,
});
}
onEdit = (e) => {
e.preventDefault();
this.setState({
Edit: !this.state.Edit
})
}
onReset = (e) => {
e.preventDefault();
this.setState({
firstName: '',
lastName: '',
employeeID: ''
})
}
render(){
return (
......
<MyForm
onSubmit={this.onSubmit}
onChange={this.onChange}
onReset={this.onReset}
firstName={this.state.firstName}
lastName={this.state.lastName}
employeeID={this.state.employeeID}
/>
</Col>
</Row>
</TabPane>
</TabContent>
</Container>
<List
employees={this.state.employees}
Edit={this.state.Edit}
onEdit ={this.onEdit}
onChange={this.onChange}
updatedEmployeeID={this.state.updatedEmployeeID}
updatedFirst={this.state.updatedFirst}
updatedLast={this.state.updatedLast}
/>
</div>
);
}
}
export default Login;
Form.js
import React, {Component} from 'react';
import { Col, Form, FormGroup, Label, Input, Button } from 'reactstrap';
const MyForm = (props) => {
return(
<Form style={{ margin: '30px 0px'}}>
<FormGroup row>
<Label for="firstName" sm={2} size="sm">First Name:</Label>
<Col sm={10}>
<Input type="text" onChange={props.onChange} value={props.firstName} name="firstName" id="exampleEmail" placeholder="Enter First Name"/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="lastName" sm={2} size="sm">Last Name:</Label>
<Col sm={10}>
<Input type="text" onChange={props.onChange} value={props.lastName} name="lastName" id="exampleEmail2" placeholder="Enter Last Name" />
</Col>
</FormGroup>
<FormGroup row>
<Label for="Employee ID" sm={2} size="sm">Employee ID:</Label>
<Col sm={5}>
<Input type="text" onChange={props.onChange} value={props.employeeID} name="employeeID" id="exampleEmail2" placeholder="Enter Employee ID" />
</Col>
</FormGroup>
<FormGroup row>
<Col sm={12}>
<div className="float-right">
<Button onClick={props.onSubmit} size="lg" style={{ margin: '0px 5px'}} color="secondary">Add</Button>
<Button onClick={props.onReset} size="lg" style={{ margin: '0px 5px'}}color="warning">Reset</Button>
</div>
</Col>
</FormGroup>
<hr></hr>
<FormGroup row>
<Col sm={4}>
<Input type="text" name="search" id="exampleEmail2" placeholder="Search" />
</Col>
<Col sm={8}>
<Label for="sort" sm={2} size="sm">Sort:</Label>
<Button onClick={props.onSubmit} size="lg" style={{ margin: '0px 5px'}} color="secondary">First Name</Button>
<Button onClick={props.onSubmit} size="lg" style={{ margin: '0px 5px'}} color="secondary">Last Name</Button>
<Button onClick={props.onSubmit} size="lg" style={{ margin: '0px 5px'}} color="secondary">ID</Button>
</Col>
</FormGroup>
</Form>
)
}
export default MyForm;
List component
import React, {Component, Fragment} from 'react';
import { Col, Form, FormGroup, Label, Input, Button } from 'reactstrap';
const List = (props) => {
return(
<Fragment>
{props.employees.map( (item, i) => (
<div style={{ margin: '40px 0px'}} key={i}>
<hr style={{ border:'1px dashed #000'}}></hr>
<div className="float-right">
<Button onClick={props.onEdit} size="lg" style={{ margin: '0px 5px'}} color="secondary">{props.Edit ? 'Save': 'Edit'}</Button>
<Button size="lg" style={{ margin: '0px 5px'}}color="secondary">Delete</Button>
</div>
<FormGroup row>
<Col sm={5}>
{props.Edit ? (
<Input type="text" onChange={props.onChange} value={ props.updatedFirst ? props.updatedFirst : item.firstName } name="updatedFirst" placeholder="Enter First Name"/>
):(
<div>
{props.updatedFirst ? props.updatedFirst : item.firstName }
</div>
)}
</Col>
</FormGroup>
<FormGroup row>
<Col sm={5}>
{props.Edit ? (
<Input type="text" onChange={props.onChange} value={ props.updatedEmployeeID ? props.updatedEmployeeID : item.employeeID} name="updatedEmployeeID" placeholder="Enter EmployeeID"/>
):(
<div>
{props.updatedEmployeeID ? props.updatedEmployeeID : item.employeeID}
</div>
)}
</Col>
</FormGroup>
<FormGroup row>
<Col sm={5}>
{props.Edit ? (
<Input type="text" onChange={props.onChange} value={ props.updatedLast ? props.updatedLast: item.lastName} name="updatedLast" placeholder="Enter Last Name"/>
):(
<div>
{props.updatedLast ? props.updatedLast : item.lastName}
</div>
)}
</Col>
</FormGroup>
</div>
))}
</Fragment>
)
}
export default List;
The following example shows how to pass a handler and set the state accordingly.
For good measure I separated the logic and the presentation, the presentational components are pure components using React.memo.
//A container should only contain the logic
class EmployeesContainer extends React.Component {
state = {
employees: [{ name: '' }, { name: '' }, { name: '' }],
};
//define what needs to happen if you click edit on an
// employee
onEdit = index => {
//edit will be called with the index of the employee
// the Employees component owns the list of employees
// so it will have to make changes to it
this.setState({
employees: this.state.employees.map((employee, i) =>
i === index ? { ...employee, edit: true } : employee
),
});
};
//Same idea as onEdit, index needs to be passed to indicate
// what employee needs to be changed
onChange = (index, e) => {
this.setState({
employees: this.state.employees.map((employee, i) =>
i === index
? { ...employee, name: e.target.value }
: employee
),
});
};
render() {
return (
<Employees
employees={this.state.employees}
onEdit={this.onEdit}
onChange={this.onChange}
/>
);
}
}
//The Employees presentational component, contains the jsx
// you can make it a pure component by using React.memo
const Employees = React.memo(
({ employees, onEdit, onChange }) => (
<div>
{employees.map((employee, index) => (
<EmployeeContainer
key={index}
index={index}
employee={employee}
onEdit={onEdit}
onChange={onChange}
/>
))}
</div>
)
);
//Make this a container as well because it does more
// than only produce jsx
class EmployeeContainer extends React.Component {
state = {};
//create onChange and onEdit only when index changes
// this will prevent unnecessary renders
static getDerivedStateFromProps(props, state) {
const { index, onChange, onEdit } = props;
if (state.index !== index) {
return {
index,
onChange: e => onChange(index, e),
onEdit: () => onEdit(index),
};
}
return null;
}
render() {
const { employee } = this.props;
const { onChange, onEdit } = this.state;
return (
<Employee
employee={employee}
onChange={onChange}
onEdit={onEdit}
/>
);
}
}
//presentational component, is also pure component
const Employee = React.memo(
({ employee, onChange, onEdit }) => (
<div>
{employee.edit ? (
<input
type="text"
value={employee.name}
onChange={onChange}
/>
) : (
<button onClick={onEdit}>edit</button>
)}
</div>
)
);
I don’t think onSubmit is updating employees correctly. You shouldn’t use this.state inside setState.
this.state inside setState ReactJS
Try this..
this.setState(prevState => ({
employees: [...prevState.employees, ourForm]
}, () => {
console.log(this.state.employees)
}))

How do I reset modal props on close

I have a component, DeleteRoute, which is just a wrapper around a Modal. It allows me to delete a "Route" entity.
I am using React as well as Redux to maintain data and state.
I am struggling with the correct architecture to make the modal work. I didn't like having all of the state in the parent container because it made the parent cluttered. So I put the redux actions inside the modal. This way I pass in a route from the parent, but call "deleteRoute" from the redux store inside the DeleteRoute component, and I can display success and error messages.
This all works great, except it I close and re-open the modal, the previous success/error message is still displayed. This is because the closing/opening is done with a toggle from the parent, but the parent can't reset the child props.
I thought I could just pass the success and error properties in the parent, and anytime the parent re-renders it would reset those, but it isn't (despite it re-rendering when isOpen changes).
class DeleteRoute extends React.Component {
constructor(props) {
super(props);
this.deleteRoute = this.deleteRoute.bind(this);
}
deleteRoute() {
this.props.deleteRoute(this.props.route);
}
render() {
var route = this.props.route || {};
return (
<div>
<Modal
isOpen={this.props.isOpen}
toggle={this.props.toggle}
>
<ModalHeader toggle={this.props.toggle}>Delete Route</ModalHeader>
<ModalBody>
{this.props.isLoading && <Spinner color="primary" />}
<Alert color="danger" isOpen={this.props.error}>{this.props.error}</Alert>
<Alert color="success" isOpen={this.props.success}>Deleted successfully</Alert>
<Form>
<Row form>
<Col>
<FormGroup>
<Label for="CssPlatform">Css Platform</Label>
<Input disabled name="CssPlatform" type="text" value={route.CSSPlatform} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="ProdManager">Prod Manager</Label>
<Input disabled name="ProdManager" type="text" value={route.ProdManager} />
</FormGroup>
</Col>
</Row>
<Row form>
<Col>
<FormGroup>
<Label for="CssProduct">Css Product</Label>
<Input disabled name="CssProduct" type="text" value={route.CSSProduct} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="ProgSupervisor">Prog Supervisor</Label>
<Input disabled name="ProgSupervisor" type="text" value={route.ProgSupervisor} />
</FormGroup>
</Col>
</Row>
<Row form>
<Col>
<FormGroup>
<Label for="CssSubProduct">Css SubProduct</Label>
<Input disabled name="CssSubProduct" type="text" value={route.CSSSubProduct} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="RallyProject">Rally Project</Label>
<Input disabled name="RallyProject" type="text" value={route.RallyProject} />
</FormGroup>
</Col>
</Row>
<Row form>
<Col>
<FormGroup check inline>
<Label check>
<Input disabled name="CssProductActive" type="checkbox" checked={route.CSSProductActive} />
Css Product Active
</Label>
</FormGroup>
</Col>
<Col>
<FormGroup check inline>
<Label check>
<Input disabled name="CssSubProductActive" type="checkbox" checked={route.CSSSubProductActive} />
Css SubProduct Active
</Label>
</FormGroup>
</Col>
</Row>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.deleteRoute}>Delete Route</Button>{' '}
<Button color="secondary" onClick={this.props.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
export default connect(
state => state.deleteRouteReducer,
dispatch => bindActionCreators(actionCreators, dispatch))(DeleteRoute);
render() {
return (
<div>
<h2>Routes</h2>
<p>Routes define how items from CSS get imported into Rally. Use routes to connect a Rally project to a set of criteria in CSS.</p>
<div>
<AddRoute isOpen={this.props.showAddRoute} toggle={this.toggleAddRoute} />
<DeleteRoute error={this.props.deleteRouteError} success={this.props.deleteRouteSuccess} isOpen={this.props.showDeleteRoute} route={this.props.selectedRoute} toggle={this.toggleDeleteRoute} />
<DataTable
actions={[
{ Click: this.toggleAddRoute, Color: 'green', Icon: 'MdAdd', ToolTip: "Add new route" },
{ Click: this.toggleEditRoute, Color: 'orange', Icon: 'MdEdit', ToolTip: "Edit route", RowAction: true },
{ Click: this.toggleDeleteRoute, Color: 'red', Icon: 'MdRemove', ToolTip: "Delete route", RowAction: true },
]}
columns={[
{ Title: "Platform", Field: "CSSPlatform" },
{ Title: "Product", Field: "CSSProduct" },
{ Title: "SubProduct", Field: "CSSSubProduct" },
{ Title: "ProdManager", Field: "ProdManager" },
{ Title: "ProgSupervisor", Field: "ProgSupervisor" },
{ Title: "Product Active?", Field: "CSSProductActive" },
{ Title: "SubProduct Active?", Field: "CSSSubProductActive" },
{ Title: "Rally Project", Field: "RallyProject" },
{ Title: "Rally Url", Field: "RallyUrl" }
]}
data={this.props.routes}
edit={this.editRoute}
isLoading={this.props.isLoading} />
</div>
</div>
);
I ended up adding a redux action to reset the success and error props, and then calling that when the modal is closed. I think this is in line with correct redux architecture, but I'm open to better solutions if anyone has them.
class DeleteRoute extends React.Component {
constructor(props) {
super(props);
this.deleteRoute = this.deleteRoute.bind(this);
this.toggle = this.toggle.bind(this);
}
deleteRoute() {
this.props.deleteRoute(this.props.route);
}
toggle() {
if (this.props.isOpen) {
// reset the error and success messages on close
this.props.initialize();
}
this.props.toggle();
}
render() {
var route = this.props.route || {};
return (
<div>
<Modal
isOpen={this.props.isOpen}
toggle={this.toggle}
>
<ModalHeader toggle={this.toggle}>Delete Route</ModalHeader>
<ModalBody>
{this.props.isLoading && <Spinner color="primary" />}
<Alert color="danger" isOpen={this.props.error}>{this.props.error}</Alert>
<Alert color="success" isOpen={this.props.success}>Deleted successfully</Alert>
<Form>
<Row form>
<Col>
<FormGroup>
<Label for="CssPlatform">Css Platform</Label>
<Input disabled name="CssPlatform" type="text" value={route.CSSPlatform} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="ProdManager">Prod Manager</Label>
<Input disabled name="ProdManager" type="text" value={route.ProdManager} />
</FormGroup>
</Col>
</Row>
<Row form>
<Col>
<FormGroup>
<Label for="CssProduct">Css Product</Label>
<Input disabled name="CssProduct" type="text" value={route.CSSProduct} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="ProgSupervisor">Prog Supervisor</Label>
<Input disabled name="ProgSupervisor" type="text" value={route.ProgSupervisor} />
</FormGroup>
</Col>
</Row>
<Row form>
<Col>
<FormGroup>
<Label for="CssSubProduct">Css SubProduct</Label>
<Input disabled name="CssSubProduct" type="text" value={route.CSSSubProduct} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="RallyProject">Rally Project</Label>
<Input disabled name="RallyProject" type="text" value={route.RallyProject} />
</FormGroup>
</Col>
</Row>
<Row form>
<Col>
<FormGroup check inline>
<Label check>
<Input disabled name="CssProductActive" type="checkbox" checked={route.CSSProductActive} />
Css Product Active
</Label>
</FormGroup>
</Col>
<Col>
<FormGroup check inline>
<Label check>
<Input disabled name="CssSubProductActive" type="checkbox" checked={route.CSSSubProductActive} />
Css SubProduct Active
</Label>
</FormGroup>
</Col>
</Row>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.deleteRoute}>Delete Route</Button>{' '}
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
export default connect(
state => state.deleteRouteReducer,
dispatch => bindActionCreators(actionCreators, dispatch))(DeleteRoute);
and the redux bits
import axios from 'axios';
// actions
const deleteRouteType = "DELETE_ROUTE";
const deleteRouteFailureType = "DELETE_ROUTE_FAILURE";
const deleteRouteSuccessType = "DELETE_ROUTE_SUCCESS";
const initializeType = "DELETE_ROUTE_INITIALIZE";
const initialState = { error: null, success: null };
// action creators
export const actionCreators = {
initialize: () => (dispatch) => {
dispatch({ type: initializeType });
},
deleteRoute: (route) => async (dispatch) => {
dispatch({ type: deleteRouteType });
axios
.delete(`api/route`, route)
.then(res => {
if (res.data.error) {
dispatch({ type: deleteRouteFailureType, payload: res.data.errorMessage });
}
else {
dispatch({ type: deleteRouteSuccessType, payload: res.data.data });
}
})
.catch(err => {
dispatch({ type: deleteRouteFailureType, payload: err.message });
});
}
};
// reducers
export const reducer = (state, action) => {
state = state || initialState;
switch (action.type) {
case initializeType:
return {
...state,
error: null,
isLoading: false,
success: false
};
case deleteRouteType:
return {
...state,
error: null,
isLoading: true,
success: false
};
case deleteRouteFailureType:
return {
...state,
error: action.payload,
isLoading: false,
success: false
};
case deleteRouteSuccessType:
return {
...state,
error: null,
isLoading: false,
success: true
};
default:
return state;
}
};

Resources