I want a container with table that has one column "enabled" which can be toggled. I want to save the state of toggle in object I used to display the row (In the example below, I want to store it in enable attribute of student object). I have table and toggle button displaying properly. But the toggle button is not clickable/togglable and doesnt store the state. live code here.
Here is my code
class student {
constructor(id, name, age,email,enable) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.enable = enable;
}
}
const Switch = ({ isOn, handleToggle}) => {
return (
<>
<input
checked={isOn}
onChange={handleToggle}
className="react-switch-checkbox"
id={`react-switch-new`}
type="checkbox"
/>
<label
className="react-switch-label"
htmlFor={`react-switch-new`}
>
<span className={`react-switch-button`} />
</label>
</>
);
};
class Table extends React.Component {
constructor(props) {
super(props)
this.state = {
students: [ new student(1, 'foo', 12, 'foo#gmail.com', true),
new student(2, 'bar', 22, 'bar#gmail.com', false),
new student(3, 'foobar', 44, 'foo#gmail.com', true),
new student(4, 'foofoo',57, 'foofoo#gmail.com', false)
]
}
}
renderTableHeader() {
let header = Object.keys(this.state.students[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, enable } = student //destructuring
//const [value, setValue] = useState(false);
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><Switch
isOn={enable}
handleToggle={() => student.enable=!enable}
/></td>
</tr>
)
})
}
render() {
return (
<div>
<h1 id='title'>React Dynamic Table</h1>
<table id='students'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render(<Table />, document.getElementById('root'));
Here is the screenshot of how page looks
The issue is, you cannot directly change the state like,
handleToggle={() => student.enable=!enable}
This will never change your state. To change your state you must use setState.
You should have a dedicated function to toggle your state,
handleToggle = (id) => {
this.setState({
students: this.state.students.map(std => {
if(parseInt(std.id) === parseInt(id)){ //parse id to integer for safer side
return {...std, enable: !std.enable}
}else{
return std;
}
})
})
}
You should provide this function to your Switch. Also provide id for updation.
<Switch
isOn={enable}
id={id} //provide id for updation
handleToggle={this.handleToggle} //provide function reference
/>
You need to make some changes to your Switch,
const Switch = ({ isOn, handleToggle, id}) => { //take prop id
return (
<>
<input
checked={isOn}
onChange={() => handleToggle(id)} //call handleToggle using id
className="react-switch-checkbox"
id={`react-switch-new${id}`} // Make it unique by adding id
type="checkbox"
/>
<label
className="react-switch-label"
htmlFor={`react-switch-new${id}`} // Make it unique by adding id
>
<span className={`react-switch-button`} />
</label>
</>
);
};
Demo
Related
I am displaying a list. Each item in the list is having a textbox. Textbox is showing DisplayOrder. Please find the sandbox: https://codesandbox.io/s/solitary-butterfly-4tg2w0
In Post API call, how to pass changed textbox values with corresponding description-id as a collection. StoredProcedure is taking description-id and display-sequence as parameters to save changed data in the database.
Please help on form submit how to do this? Thanks
import "./styles.css";
import React from "react";
import XMLParser from "react-xml-parser";
const data = `<?xml version="1.0"?>
<Category>
<description description-id="11" display-sequence="2">testing</description>
<description description-id="15" display-sequence="5">Guide</description>
<description description-id="20" display-sequence="7">test</description>
<description description-id="25" display-sequence="10">Guide</description>
<description description-id="30" display-sequence="12">test</description>
</Category>
</xml>`;
const REQUEST_URL = "";
const axios = {
get: () =>
new Promise((resolve) => {
setTimeout(resolve, 1000, { data });
})
};
class Sort_Descr extends React.Component {
constructor(props) {
super(props);
this.state = {
proddescriptions: [],
proddescription_id: "",
display_sequence: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {}
componentDidMount() {
this.getlistofdescriptions();
}
getlistofdescriptions() {
axios
.get(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => {
const jsonDataFromXml = new XMLParser().parseFromString(data);
const descriptions = jsonDataFromXml.getElementsByTagName(
"description"
);
console.log(descriptions);
this.setState({
proddescriptions: jsonDataFromXml.getElementsByTagName("description")
});
});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<ul style={{ listStyle: "none" }}>
{this.state.proddescriptions.map((item, index) => {
return (
<li key={item.attributes["description-id"]}>
{item.attributes["description-id"]}
<input
type="text"
size="5"
maxlength="3"
value={item.attributes["display-sequence"]}
onChange={this.handleChange}
/>
{item.value}
</li>
);
})}
</ul>
</div>
<input type="submit" name="submit" value="Submit" id="btnsubmit" />
</form>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h4>Sort list by updating the number in the textbox</h4>
<Sort_Descr />
</div>
);
}
Instead of storing and updating JSON/XML data in state I'd suggest mapping it to a simpler object that is easier to identify and update. React state should be the minimal amount of data necessary to represent your information, store only what you need.
const proddescriptions = descriptions.map(({ attributes, value }) => ({
id: attributes["description-id"],
sequence: attributes["display-sequence"],
value
}));
this.setState({ proddescriptions });
When mapping the state.proddescriptions to the inputs, use the id as the inputs name attribute value for identification when updating.
{this.state.proddescriptions.map((item) => (
<li key={item.id}>
<label>
{item.id}
<input
type="text"
name={item.id} // <-- name attribute
size="5"
maxLength="3"
value={item.sequence}
onChange={this.handleChange}
/>
</label>
{item.value}
</li>
))}
Implement the handleChange to shallow copy the previous state and update the matching array element by id.
handleChange = (event) => {
const { name, value } = event.target;
this.setState((prevState) => ({
proddescriptions: prevState.proddescriptions.map((el) =>
el.id === name
? {
...el,
sequence: value
}
: el
)
}));
};
In the handleSubmit callback use the onSubmit event object to prevent the default form action and access the current state.proddescriptions and map it back to any format your APIs are expecting.
handleSubmit = (event) => {
event.preventDefault();
console.log(this.state.proddescriptions);
// manipulate `state.proddescriptions` into a request payload
};
I want to be able to type into my input fields, and then have a button show up beside it upon typing that says submit edit. right now, I have a button that always is there, but I want it to only show up upon typing. this is all in react btw. so far, I have tried jquery, but react doesn't like it.
here's the whole page, to avoid any confusion of what I am doing and where my stuff is located.
import React, { Component } from "react";
import axios from "axios";
import "../styles/TourPage.css";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true,
};
}
componentDidMount() {
axios
.get("/getResults")
.then((res) => {
this.setState({
myData: res.data
});
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
deleteById = (id) => {
console.log(id)
axios
.post(`/deleteDoc`, {id: id} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
editById = (id, siteLocation, Services, cnum) => {
console.log(id, siteLocation, Services, cnum)
axios
.post(`/editDoc`, JSON.stringify({id: id, location: siteLocation, Services: Services, cnum: cnum}),{
headers: {
"Content-Type": "Application/json"
}
} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
render() {
// You can handle the loader part here with isLoading flag. In this case No data found will be shown initially and then the actual data
let { myData, isLoading } = this.state;
return (
<table id="customers">
<tr>
<th>siteLocation</th>
<th>Services</th>
<th>cnum</th>
</tr>
{myData.length > 0
? myData.map(({ location, Services, cnum, _id }, index) => (
<tr key={index}>
<td><input type="text" placeholder={location} name="location" id="location" /> </td>
<td><input type="text" placeholder={Services} name="Services" id="Services" /> </td>
<td><input type="text" placeholder={cnum} name="cnumhide" id="cnumhide" /> </td>
<td><input type="hidden" placeholder={cnum} name="cnum" id="cnum" /> </td>
<button
onClick={(e) => {
e.preventDefault();
this.deleteById(_id);
}}
disabled={isLoading}
>
Delete
</button>
<button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>
</tr>
))
: "No Data Found"}
</table>
);
}
}
const script = document. createElement("script"); $('input').keyup(function(){
if($.trim(this.value).length > 0)
$('#location').show()
else
$('#location').hide()
});
export default TourPage;
thanks 4 the help in advance.
You can use onfocus() in the text element. If you want to hide the button, use onfocusout() or in case if you want to track only after input has changed, use onchange() event
...
//class function
onTyping =()=>{
this.setState({
showSubmit:true
})
}
...
//render part
render(){
...
//input which you want to track typing
<input type="text" onfocus={()=>this.onTyping()} placeholder={location} name="location" id="location" />
...
//element submit button
{this.state.showSubmit && <button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>}
...
Here is an example that helps you,
const {
useState
} = React;
const Test = () => {
const [show, setShow] = useState(false);
const handleChange = (event) => {
if (event.target.value.length > 0)
setShow(true);
else
setShow(false)
}
return ( <div>
<input type = "text"
onChange = {
(event) => handleChange(event)
}/>
{show && < button > Submit changes now! </button>}
</div>
)
}
ReactDOM.render( < Test / > ,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There is a way to avoid jquery and continue using your react class component to achieve this.
Map over state.myData to render each item with an input and a button.
Use the array index with the input's onChange event callback to add the inputValue into the correct array item's object within state.
Use the array index with the button's onClick event callback to get the item from state.myData before sending it to the server.
If there is an inputValue for the item, you can conditionally render the button.
import React, { Component } from "react";
import axios from "axios";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
myData: res.data.results
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
newData[index].inputValue = target.value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(
`Clicked on 'submit edit' for ${item.name} with value ${item.inputValue}`
);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<div>
{myData.map(({ name, status, species, inputValue }, index) => {
return (
<div key={index}>
<p>{`${name} - ${species} - ${status}`}</p>
<input
type="text"
onChange={(e) => this.handleChangeInput(e, index)}
value={inputValue || ""}
/>
{inputValue && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</div>
);
})}
</div>
);
}
}
export default TourPage;
If you wanted to have an input per field within each row, you could make some small changes and save your edits to the item's state within a nested object.
Then you could check if there was anything inside that row's edits object to conditionally show the submit button per row.
import React, { Component } from "react";
import axios from "axios";
import isEmpty from "lodash.isempty";
import pick from "lodash.pick";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
// here we create an empty 'edits' object for each row
myData: res.data.results.map((d) => ({
...pick(d, ["name", "status", "species"]),
edits: {}
}))
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
const { value, name } = target;
newData[index].edits[name] = value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(`Clicked on 'submit edit' for ${item.name} with edits:`);
console.log(item.edits);
console.log("Updated item: ");
const { edits, ...orig } = item;
const newItem = { ...orig, ...edits };
console.log(newItem);
// Once saved to api, we can update myData with newItem
// and reset edits
const newData = [...this.state.myData];
newData[index] = { ...newItem, edits: {} };
this.setState({
myData: newData
});
};
showButton = (index) => {
const { edits } = this.state.myData[index];
return !isEmpty(edits);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<table>
<tbody>
{myData.map((row, index) => {
const { edits, ...restRow } = row;
return (
<tr key={index}>
{Object.keys(restRow).map((col) => {
return (
<td>
<label>
{col}:
<input
name={col}
value={edits[col] || restRow[col]}
onChange={(e) => this.handleChangeInput(e, index)}
/>
</label>
</td>
);
})}
<td>
{this.showButton(index) && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
export default TourPage;
I have a list of students and I want to make an expandable list view. Basically each student will have a plus & minus button and every time it's clicked a list of all student's grades will expand. However, so far when I click on one of the buttons it opens all the students' grade lists at the same time.
I can't seem to figure out how I can fix this so that when I click on a specific student it will open only that student's grades. Can anyone help me on this? I think I'm close, but I can't get there!
Here's my code:
import React from 'react';
import Average from './Average';
import './App.css';
class App extends React.Component{
state = {
students: [],
error: null,
search: null,
open: false,
}
componentDidMount(){
fetch('https://api.hatchways.io/assessment/students')
.then(res => res.json())
.then(
(data) => {
this.setState({
students: data.students,
})
},
(error) => {
this.setState({ error });
})
}
handleChange = (e) => {
this.setState({ search: e.target.value });
}
togglePanel = e => {
this.setState({ open: !this.state.open })
}
render(){
let { search, students, open } = this.state;
const display = students.filter((student) => {
if(search == null)
return student;
else if(student.firstName.toLowerCase().includes(search.toLowerCase()) ||
student.lastName.toLowerCase().includes(search.toLowerCase())){
return student;
}
}).map((student, idx) => {
return (
<div className = "student-container" key = {student.id}>
<img className = "student-pic" src = {student.pic} alt = "pictures"/>
<div className = "student-mini">
<h1>{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</h1>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<Average grades = {student.grades}/>
<button onClick = {(e) => this.togglePanel(e)}>{open ? '-' : '+'}</button>
<div>
<p>{open ? student.grades.map((grade, id) => {
return (
<p key = {id}>Test {id + 1}: {grade}%</p>
)
}): null}
</p>
</div>
</div>
</div>
)
})
return (
<div className = "gray-container">
<div className = "white-container">
<form>
<input onChange = {this.handleChange} type = "text" placeholder = "Search by
name">
</input>
</form>
<div>
{display}
</div>
</div>
</div>
)
}
}
export default App;
Each of your expandable panels uses the same single open state to determine whether it should be expanded or not, that is why they all open at once. The easiest solution here is for each panel to have its own state.
Ideally, you want to extract the JSX from your map's return statement to separate component presenting a single student's panel, let's say StudentPanel, pass the student object as prop and move the open state and the toggle function to that component as well.
UPDATE: You'd have something like this:
class StudentPanel extends React.Component {
state = {
open: false
}
togglePanel = e => {
this.setState(prevState => ({ open: !prevState.open }))
}
render() {
const { student } = this.props;
return (
<div className = "student-container" key = {student.id}>
// (...) rest of JSX
</div>
);
}
}
In render of your App you'd have:
(...).map(student => <StudentPanel student={student} />)
and the open property and togglePanel can be removed from App component completely.
Say I have a table:
<div>
<tr>
<td>
<p id='1' className="foo"> Boom </p>
</td>
<td>
<p id='2' className="foo"> Bang </p>
</td>
<td>
<p id='3' className="foobar"> Pew Pew </p>
</td>
</tr>
</div>
I want the data inside it to be editable in-place. Thus I want to substitute <p> element with an <input> and then substitute it with <p> again, but with new value. I've been doing it with jQuery and now made it with what seems to me as plain JS but with React. Code looks like this:
import React, { Component } from "react";
import "./App.css";
class App extends Component {
handleClick(e) {
if (e.target.className === 'foo'){
let element = document.getElementById(e.target.id)
let element_value = element.innerText
let parent_element = element.parentNode
let new_element = document.createElement('input')
parent_element.removeChild(element)
parent_element.appendChild(new_element)
new_element.setAttribute('class', 'input')
new_element.setAttribute('id', e.target.id)
new_element.setAttribute('value', element_value)
} else if (e.target.className === 'input') {
let element = document.getElementById(e.target.id)
let element_value = element.value
let parent_element = element.parentNode
let new_element = document.createElement('p')
parent_element.removeChild(element)
parent_element.appendChild(new_element)
new_element.setAttribute('class', 'foo')
new_element.setAttribute('id', e.target.id)
new_element.innerText = element_value
}
};
componentDidMount() {
document.addEventListener('dblclick', this.handleClick)
}
componentWillUnmount() {
document.removeEventListener('dblclick', this.handleClick)
}
render() {
return (
<div>
<tr>
<td>
<p id='1' className="foo"> Boom </p>
</td>
<td>
<p id='2' className="foo"> Bang </p>
</td>
<td>
<p id='3' className="foobar"> Pew Pew </p>
</td>
</tr>
</div>
)
}
}
export default App
However this doesn't seem to me as a good practice. Could you give me hints on how to improve/change my approach? Thank you.
The best approach is probably to create a controlled component that handles all the logic for the editable cell, and store the values in the parent. I made a sandbox that you can check out here, but I'll add the code here as well.
That way the cell component provides all the view stuff needed, and the parent controls the logic and data for all the cells.
So, the editable cell handles the functionality of switching between views:
const EditableCell = ({ id, onEdit, className, value }) => {
const [isEditing, setIsEditing] = useState(false);
const onClick = useCallback(() => {
setIsEditing(true);
}, []);
const onFinishedEditing = useCallback(() => {
setIsEditing(false);
}, []);
const onKeyDown = useCallback(
(e) => {
if (e.key === "Enter") {
onFinishedEditing();
}
},
[onFinishedEditing]
);
return (
<td>
{isEditing ? (
<input
value={value}
onChange={(e) => onEdit(e.target.value, id)}
onBlur={onFinishedEditing}
onKeyDown={onKeyDown}
autoFocus
/>
) : (
<p {...{ id, className, onClick }}>{value}</p>
)}
</td>
);
};
And then the app stores the cells' data and renders an EditableCell for each one:
export default function App() {
// This stores the cells values and properties, you can
// add or remove cells here are needed
const [cellValues, setCellValues] = useState([
{ id: "1", class: "foo", value: "Boom" },
{ id: "2", class: "foo", value: "Bang" },
{ id: "3", class: "foobar", value: "Pew Pew" }
]);
const onEdit = (value, id) => {
setCellValues(
cellValues.map((cellVal) =>
cellVal.id === id ? { ...cellVal, value } : cellVal
)
);
};
return (
<div>
Click a cell to edit
<tr>
{cellValues.map((cellVal) => (
<EditableCell
id={cellVal.id}
value={cellVal.value}
className={cellVal.class}
onEdit={onEdit}
/>
))}
</tr>
</div>
);
}
This might not perfectly match with the functionality you're wanting, but should give you a starting point
I've been promise to myself that i will made good deed at least one per day.I know that you write in class way but i stick to hooks so much that... sorry man :P
it call onChange when during editing you will press enter.
import React, { Component, useEffect, useMemo, useRef, useState } from "react";
import "./styles.css";
const Td = ({ children, editable = false, onChange, className, id }) => {
const cell = useRef();
const [edit, setEdit] = useState(false);
const [value, setValue] = useState(() => {
while (typeof children !== "string") {
children = children.props.children;
}
return children;
});
const [oldValue, setOldValue] = useState(value);
useEffect(() => {
if (!cell.current) return;
const onEditMode = () => editable && setEdit(true);
const target = cell.current;
target.addEventListener("click", onEditMode);
return () => {
target.removeEventListener("click", onEditMode);
};
}, [cell, setEdit, editable]);
const paragraph = useMemo(() => (
<p id="1" className="foo">
{value}
</p>
),[value]);
const input = useMemo(() => {
const update = (value) => {
setEdit(false);
if (onChange && typeof onChange === "function") {
onChange({
id,
newValue: value,
oldValue: oldValue
});
setOldValue(value);
}
}
return (
<input
value={value}
onChange={ e => setValue(e.target.value)}
onKeyDown={ e => e.key === "Enter" && update(value)}/>
)
},[value, setEdit, onChange, id, oldValue, setOldValue]);
return (
<td ref={cell} className={className}>
{edit ? input : paragraph}
</td>
);
};
class App extends Component {
componentDidMount() {
}
componentWillUnmount() {
}
tableCellValueChange({ id, newValue, oldValue }) {
console.log(
`table cell id: ${id} value changed from ${oldValue} to ${newValue}`
);
}
render() {
return (
<div>
<table>
<thead></thead>
<tbody>
<tr>
<Td
onChange={this.tableCellValueChange}
id="special"
editable>
<p>Bang </p>
</Td>
<Td onChange={this.tableCellValueChange} editable>
<p>Bang</p>
</Td>
<Td editable={false} className="forbar">
Pew Pew
</Td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default App;
here you have sandbox
live example
I render a React list and there is an Edit button in each list item. I wanted to toggle to switch from the data to the input form and back. Similar to this application in this article: https://medium.com/the-andela-way/handling-user-input-in-react-crud-1396e51a70bf. You can check out the demo at: https://codesandbox.io/s/fragrant-tree-0t13x.
This is where my React Component display the list:
import React, { Component } from "react";
import PriceBox from "../SinglePricebox/index";
// import SecurityForm from "../SecurityForm/index";
import AddPriceForm from "../AddPriceForm/index";
// import { uuid } from "uuidv4";
export default class PriceForm extends Component {
constructor(props) {
super(props);
this.state = {
priceArr: this.props.pricelist,
// newPriceArr: this.props.updatePrice,
showPricePopup: false,
addPricePopup: false,
isToggleOn: true,
date: props.date || "",
number: props.number || ""
};
}
updateInput = ({ target: { name, value } }) =>
this.setState({ [name]: value });
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
togglePricePopup = () => {
this.setState(prevState => ({
showPricePopup: !prevState.showPricePopup
}));
};
addPricePopup = () => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup
}));
};
/* adds a new price to the list */
addPrice = newPrice => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup,
// spreads out the previous list and adds the new price with a unique id
priceArr: [...prevState.priceArr, { ...newPrice }]
}));
};
// handlePriceSubmission = () => {
// const { updatePrice } = this.props;
// this.addPricePopup();
// updatePrice(priceArr);
// };
toggleItemEditing = (index) => {
this.setState(prevState => ({
priceArr: prevState.priceArr.map(priceItem => {
// isToggleOn: !state.isToggleOn;
})
}));
};
// toggleItemEditing = index => {
// this.setState({
// items: this.state.items.map((item, itemIndex) => {
// if (itemIndex === index) {
// return {
// ...item,
// isEditing: !item.isEditing
// }
// }
// return item;
// })
// });
// };
render() {
// const { updatePrice } = this.props;
return (
<div className="popup">
<div className="popup-inner">
<div className="price-form">
<h2>Prices</h2>
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
))}
</div>
<div className="buttons-box flex-content-between">
<button
type="button"
onClick={this.addPricePopup}
className="btn add-button">Add +</button>
{this.state.addPricePopup && (
<AddPriceForm
addPrice={this.addPrice}
cancelPopup={this.addPricePopup}
/>
)}
<div className="add-btns">
<button
type="button"
onClick={() => this.props.closeUpdatePopup()}
className="btn cancel-button"
>
Close
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
The list inside the component above is:
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
))}
</div>
And this is the single list item:
import React, { Component } from "react";
export default class SinglePricebox extends Component {
state = {
showPopup: false, //don't show popup
todaydate: this.props.date
};
/* toggle and close popup edit form window */
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
toggleEditPriceSubmission = getPriceIndex => {
const { toggleItemEditing, date } = this.props;
// toggle the pop up (close)
// this.showPopup();
toggleItemEditing({ ...getPriceIndex, date });
console.log("date?", date);
};
/* handles edit current security form submissions */
// handleEditSecuritySubmission = editSecurity => {
// const { editCurrentSecurity, id } = this.props;
// // toggle the pop up (close)
// this.togglePopup();
// // sends the editSecurity fields (name, isin, country) + id back to
// // App's "this.editCurrentSecurity"
// editCurrentSecurity({ ...editSecurity, id });
// };
render() {
return (
<div className="pricebox">
<article className="pricetable">
{this.toggleEditPriceSubmission
? "editing" : "not editing"}
<table>
<tbody>
<tr>
<td className="date-width">{this.props.date}</td>
<td className="price-width">{this.props.number}</td>
<td className="editing-btn">
<button
type="button"
className="edit-btn"
onClick={this.toggleEditPriceSubmission}
>
{this.toggleEditPriceSubmission ? "Save" : "Edit"}
</button>
</td>
<td>
<button
type="button"
className="delete-btn">
X
</button>
</td>
</tr>
</tbody>
</table>
</article>
</div>
);
}
}
I have been struggling all afternoon to toggle the edit button in each list item. I was attempting to get the key of each list item which is the this.prop.date.
You can see my code in detail at CodeSandBox: https://codesandbox.io/s/github/kikidesignnet/caissa
I would create a component which will handle the list item as a form and update it as if it was SecurityForm.
{this.state.priceArr.map((props) => {
if(props) {
return <PriceListForm methodToUpdate {...props} />
}else {
retun (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
);
}
})}
and make PriceListForm look like PriceBox but use inputs to capture new data. This way you will have two different components with less complicated logic instead of having a huge component with complex validations to check if you will display an input or not.
create a funtion named Edit to update the State
Edit = (id) => {
this.setState({edit: !this.state.edit, id})
}
and instead of that
<td className="country-width">{this.props.country}</td>
do something like
<td className="country-width">{this.state.edit ? <input type="text" value={this.props.country} onChange={() => UPDATE_THE_CONTENT}/> : this.props.isin}</td>
and call the Edit function onclick of Edit Button and pass ID of that td as a param to update it.
NOTE: by default THE VALUE OF EDIT STATE is false/null.
you can use onChange to update that box or some other techniques like create a button along with it and use that to update it.
hope this might help you