How to change this code more simply using for loop? - javascript

Please look at my code first.
I get 1 to 3 files from DB, depending on how many files did a writer uploaded. The maximum is 3, and if there's none, I render 'There is no file'.
To flexibly render files, I used three conditional statements. However, It is too messy and I think there is probably better idea than this.
const innerPost = () => {
const [files, setFiles] = useState([]);
const [deletedFilePk, setDeletedFilePk] = useState([]);
const deleteFile1 = () => {
setDeletedFilePk([...deletedFilePk, filePkNum]);
console.log(deletedFilePk);
};
const deleteFile2 = () => {
setDeletedFilePk([...deletedFilePk, filePkNum + 1]);
console.log(deletedFilePk);
};
const deleteFile3 = () => {
setDeletedFilePk([...deletedFilePk, filePkNum + 2]);
console.log(deletedFilePk);
};
return (
<>
{files.map((file) => {
if (file.length == 0) {
return (
<div>
<h5>THERE IS NO FILE.</h5>
</div>
);
}
if (file.length == 1) {
return (
<div>
<div>
<a
href={`/api/store/download-noticeboard-file fileId=${filePkNum}`}
>
{file[0]}
</a>
<Button
size='small'
onClick={deleteFile1}
>
delete
</Button>
</div>
</div>
);
}
if (file.length == 2) {
return (
<div>
<div>
<a
href={`/api/store/download-noticeboard-file?fileId=${filePkNum}`}
>
{file[0]}
</a>
<Button
size='small'
onClick={deleteFile1}
>
delete
</Button>
</div>
<div>
<a
href={`/api/store/download-noticeboard-file?fileId=${filePkNum + 1}`}
>
{file[1]}
</a>
<Button
size='small'
onClick={deleteFile2}
>
delete
</Button>
</div>
</div>
);
}
if (file.length == 3) {
return (
<div>
<div>
<a
href={`/api/store/download-noticeboard-file?fileId=${filePkNum}`}
>
{file[0]}
</a>
<Button
size='small'
onClick={deleteFile1}
>
delete
</Button>
</div>
<div>
<a
href={`/api/store/download-noticeboard-file?fileId=${filePkNum + 1}`}
>
{file[1]}
</a>
<Button
size='small'
onClick={deleteFile2}
>
delete
</Button>
</div>
<div>
<a href={`/api/store/download-noticeboard-file?fileId=${filePkNum + 2}`}></a>
<Button
size='small'
onClick={deleteFile3}
>
delete
</Button>
</div>
</div>
);
}
})}
</>
);
};
export default innerPost;
I need some wisdom!

You can map through the array and display each item and use the index property for your functions:
const innerPost = () => {
const [files, setFiles] = useState([]);
const [deletedFilePk, setDeletedFilePk] = useState([]);
const deleteFile = (index) => {
setDeletedFilePk([...deletedFilePk, index]);
console.log(deletedFilePk);
}
if (file.length == 0) {
return (
<div>
<h5>THERE IS NO FILE.</h5>
</div>
);
} else {
files.map((file, index) => {
return (
<div>
<a
href={`/api/store/download-noticeboard-file fileId=${index}`}
>
{file}
</a>
<Button
size='small'
onClick={() => deleteFile(index)}
>
delete
</Button>
</div>
);
})
}
};
export default innerPost;

Related

How can I map through Props consisting of 3 useMemo functions in React?

I have 3 separate functions using useMemo that return a different type of alert for a user in a dashboard.
These alerts appear at the top of the user dashboard if they are truthy, but if there is more than 1 alert that should appear, they are placed within a slideshow that slides automatically.
I have created a Slideshow component and pass all 3 functions in through props. My question is how I can map through props in the Slideshow component to render out these alerts?
I have tried to do so with 'props.map' but receive an error about map missing from PropTypes so am unsure if I am doing this correctly by trying to render out the alerts with a .map?
This is my main file with the 3 useMemo alerts:
export const TransactionsPage = (props) => {
const { t } = props;
const { data: profile } = apiService.useGetSingleAccountQuery();
const { data: accountInfo } = apiService.useGetPlanQuery();
const renderMaxManualTransactionWarning = useMemo(() => {
if (accountInfo?.exceed_tx_capability)
return (
<>
<div className={"free-trial-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>{t("pages.transactions.warning.transactions_limit")}</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/account/subscription/change_plan")}
>
{t("pages.transactions.warning.action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
);
}, [t, accountInfo]);
// }, [t]);
const renderMissingTransferTransactionWarning = useMemo(() => {
if (unlinkedTransferTx?.items?.length > 0)
return (
<>
<div className={"import-warning"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.transfer_transactions_missing").replace(
"[num]",
unlinkedTransferTx.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/transfer/list")}
>
{t("pages.transactions.warning.transfer_transactions_missing_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
);
}, [t, unlinkedTransferTx]);
const renderSuggestedLinksWarning = useMemo(() => {
if (suggestedLinks?.items?.length > 0)
return (
<div className={"suggested-links-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.suggested_links").replace(
"[num]",
suggestedLinks.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/links/suggestions")}
>
{t("pages.transactions.warning.links_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
);
}, [t, suggestedLinks]);
return (
<LoggedInLayout className={"transactions-page"}>
<Slideshow
renderMaxManualTransactionWarning={renderMaxManualTransactionWarning}
renderMissingTransferTransactionWarning={renderMissingTransferTransactionWarning}
renderSuggestedLinksWarning={renderSuggestedLinksWarning}
/>
</LoggedInLayout>
);
};
TransactionsPage.propTypes = {
t: func,
};
export default withTranslation()(TransactionsPage);
And my SlideShow component:
/* eslint-disable no-unused-vars */
import React, { useEffect, useState, useRef, useMemo } from "react";
import "./index.scss";
import PropTypes from "prop-types";
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 10000;
export const Slideshow = (props) => {
const [index, setIndex] = useState(0);
const timeoutRef = useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() => setIndex((prevIndex) => (prevIndex === colors.length - 1 ? 0 : prevIndex + 1)),
delay
);
return () => {
resetTimeout();
};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{/* Map through the functions and render them here */}
</div>
<div className="slideshowDots">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshowDot${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
};
Slideshow.propTypes = {
};
export default Slideshow;
Put simply, I just want to map through the functions coming from props and render them in the Slideshow.
Any advice on this would be appreciated. Many thanks in advance!
Instead of pass the alerts as a props you can do it as a children
1:
export const TransactionsPage = (props) => {
const { t } = props;
const { data: profile } = apiService.useGetSingleAccountQuery();
const { data: accountInfo } = apiService.useGetPlanQuery();
const renderMaxManualTransactionWarning = useMemo(() =>
accountInfo?.exceed_tx_capability ?
<>
<div className={"free-trial-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>{t("pages.transactions.warning.transactions_limit")}</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/account/subscription/change_plan")}
>
{t("pages.transactions.warning.action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
: null
, [t, accountInfo]);
// }, [t]);
const renderMissingTransferTransactionWarning = useMemo(() =>
unlinkedTransferTx?.items?.length > 0 ?
<>
<div className={"import-warning"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.transfer_transactions_missing").replace(
"[num]",
unlinkedTransferTx.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/transfer/list")}
>
{t("pages.transactions.warning.transfer_transactions_missing_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
: null
, [t, unlinkedTransferTx]);
const renderSuggestedLinksWarning = useMemo(() =>
suggestedLinks?.items?.length > 0 ?
<div className={"suggested-links-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.suggested_links").replace(
"[num]",
suggestedLinks.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/links/suggestions")}
>
{t("pages.transactions.warning.links_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
:null
, [t, suggestedLinks]);
return (
<LoggedInLayout className={"transactions-page"}>
<Slideshow>
{renderMaxManualTransactionWarning}
{renderMissingTransferTransactionWarning}
{renderSuggestedLinksWarning}
</Slideshow>
</LoggedInLayout>
);
};
TransactionsPage.propTypes = {
t: func,
};
export default withTranslation()(TransactionsPage);
2:
import React, { useEffect, useState, useRef, useMemo } from "react";
import "./index.scss";
import PropTypes from "prop-types";
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 10000;
export const Slideshow = ({children}) => {
const [index, setIndex] = useState(0);
const timeoutRef = useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() => setIndex((prevIndex) => (prevIndex === colors.length - 1 ? 0 : prevIndex + 1)),
delay
);
return () => {
resetTimeout();
};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{children}
</div>
<div className="slideshowDots">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshowDot${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
};
Slideshow.propTypes = {
};
export default Slideshow;

How to connect two components in a same component created on click of add button

I am creating multiple counters on click and I want to connect the two counters, When I increase the value in the first counter it should automatically decrease in the second counter. Can you suggest any solution where I can communicate with multiple counters as I can generate multiple counters on click?
const Counter = ({ value, onIncrement, onDecrement, hideIncrement }) => {
return (
<div>
<span>{value}</span>
{value > 0 && (
<button
onClick={() => {
onDecrement();
}}
>
-
</button>
)}
{hideIncrement === false && value < 10 && (
<button
onClick={() => {
onIncrement();
}}
>
+
</button>
)}
</div>
);
};
const Counters = () => {
const [counters, setCounters] = React.useState([4, 0, 0, 5]);
const sum = counters.reduce((acc, item) => acc + item, 0);
return (
<div>
<p>Sum: {sum}</p>
<button
onClick={() => {
setCounters([...counters, 0]);
}}
>
Add counter
</button>
<br />
<div>
{counters.map((value, index) => (
<Counter
value={value}
hideIncrement={sum >= 20}
onIncrement={() => {
const countersCopy = [...counters];
countersCopy[index] += 1;
setCounters(countersCopy);
}}
onDecrement={() => {
const countersCopy = [...counters];
countersCopy[index] -= 1;
setCounters(countersCopy);
}}
/>
))}
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Counters />, rootElement);
<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>
You can pass state to Couter component:
try this:
const Counter = ({value, onIncrement, onDecrement, hideIncrement,
addStatus, counterIndex, index}) => {
useEffect(() => {
if (counterIndex === index - 1) onDecrement(addStatus);
}, [counterIndex, addStatus])
return (
<div>
<span>{value}</span>
{value > 0 && (
<button
onClick={() => {
onDecrement();
}}
>
-
</button>
)}
{hideIncrement === false && value < 10 && (
<button
onClick={() => {
onIncrement();
}}
>
+
</button>
)}
</div>
);
};
const Counters = () => {
const [counters, setCounters] = React.useState([4, 4, 4, 5]);
const [addStatus, setAddStatus] = useState(0);
const [counterIndex, setCounterIndex] = useState(0);
const sum = counters.reduce((acc, item) => acc + item, 0);
return (
<div>
<p>Sum: {sum}</p>
<button
onClick={() => {
setCounters([...counters, 0]);
}}
>
Add counter
</button>
<br/>
<div>
{counters.map((value, index) => (
<Counter
value={value}
index={index}
addStatus={addStatus}
counterIndex={counterIndex}
hideIncrement={sum >= 20}
onIncrement={() => {
const countersCopy = [...counters];
countersCopy[index] += 1;
setAddStatus(a => a + 1)
setCounterIndex(index)
setCounters(countersCopy);
}}
onDecrement={() => {
const countersCopy = [...counters];
countersCopy[index] -= 1;
setCounters(countersCopy);
}}
/>
))}
</div>
</div>
);
};

Assign active class to the clicked button

I'm working on a React.js pagination and I would like to set an active class whenever I press a specific number.
How can I add this in the below code:
function Pagination() {
const [page, setPage] = useState(1);
const goPreviousPage = () => {
if (page > 1) {
setPage(prevPage => prevPage - 1);
}
};
const goNextPage = () => {
if (page < 11) {
setPage(prevPage => prevPage + 1);
}
};
const goSpecificPage = page => {
setPage(page);
};
return (
<div className="pagination-wrapper">
<button onClick={goPreviousPage} disabled={page <= 1}>
Previous Page
</button>
<button className="" onClick={() => goSpecificPage(page)}>{page}</button>
<button onClick={() => goSpecificPage(page + 1)}>{page + 1}</button>
<button onClick={() => goSpecificPage(page + 2)}>{page + 2}</button>
<button onClick={goNextPage} disabled={page >= 10 }>
Next Page
</button>
</div>
);
}
Many thanks!
You need to keep track of currently active button within your state and assign className conditionally:
const { useState } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const PaginationButtons = () => {
const [activeButton, setActiveButton] = useState(1),
handleButtonClick = buttonIdx => {
setActiveButton(buttonIdx)
// do whatever you need upon button click
}
return (
<div>
<button onClick={() => handleButtonClick(0)}>Prev</button>
<button
onClick={() => handleButtonClick(1)}
className={activeButton == 1 ? 'active' : ''}
>
1
</button>
<button
onClick={() => handleButtonClick(2)}
className={activeButton == 2 ? 'active' : ''}
>
2
</button>
<button
onClick={() => handleButtonClick(3)}
className={activeButton == 3 ? 'active' : ''}
>
3
</button>
<button onClick={() => handleButtonClick(4)}>Next</button>
</div>
)
}
render (
<PaginationButtons />,
rootNode
)
.active {
background-color: orange;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

embedded function in .map to access mapped information

I am using a .map function to display details to user.
I would like a button so that when it is clicked it expands for the user displaying more in depth information.
As it is mapped I cannot set a function as I would normally (between constructor and render)as it would not understand the mapped information e.g r.AssignedWorkStation
essentially I am asking is it possible to put a function within here (example below) and then have this be able to access the mapped properties
const renderTodos = currentTodos.map(r => {
Test(){
if(me){
//function to do what I want
}
}
return (
<>
<div className="jumbotron">
<button className="btn btn-primary" style={{ float: "right" }}>
View Details
</button>
<br />
<li>
<b>Workstation : </b>
{r.AssignedWorkStation}
</li>
<li>
<b>Date: </b>
{r.Date}
</li>
<li>
<b>Status: </b>
{r.CompleteToken}
</li>
<br />
</div>
</>
);
});
Whole class code
var results = [];
class AdminWorkstations extends React.Component {
constructor() {
super();
this.state = {
questions: [],
viewDetails: false,
currentPage: 1,
todosPerPage: 4
};
this.getQuestionByUniqueDate = this.getQuestionByUniqueDate.bind(this);
// this.test = this.test.bind(this);
}
// sets the questions form sql into state for questions
handleClick = event => {
this.setState({
currentPage: Number(event.target.id)
});
};
// test() {
// alert(r.AssignedWorkStation);
// }
componentDidMount() {
fetch(`/admin-completed-workstations`)
.then(recordset => recordset.json())
.then(results => {
this.setState({ questions: results.recordset });
console.log(this.state.questions);
this.state.questions &&
this.getQuestionByUniqueDate(this.state.questions);
});
}
handlePageChange(pageNumber) {
this.setState({ activePage: pageNumber });
}
getQuestionByUniqueDate(questions) {
for (var i = 0; i < questions.length; i++) {
if (
!results.find(q => q.Date == questions[i].Date) ||
!results.find(
q => q.AssignedWorkStation == questions[i].AssignedWorkStation
)
) {
results.push(questions[i]);
this.setState({ amountOfWorkstations: results.length });
}
}
return results;
}
render() {
const { currentPage, todosPerPage } = this.state;
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo);
debugger;
const renderTodos = currentTodos.map(r => {
return (
<>
<div className="jumbotron">
<button className="btn btn-primary" style={{ float: "right" }}>
View Details
</button>
<br />
<li>
<b>Workstation : </b>
{r.AssignedWorkStation}
</li>
<li>
<b>Date: </b>
{r.Date}
</li>
<li>
<b>Status: </b>
{r.CompleteToken}
</li>
<br />
{/* <Questions results={r}></Questions> */}
</div>
</>
);
});
const pageNumbers = [];
for (
let i = 1;
i <= Math.ceil(this.state.amountOfWorkstations / todosPerPage);
i++
) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<button
className="btn btn-primary"
key={number}
id={number}
onClick={this.handleClick}
>
{number}
</button>
);
});
let selectedWorkStation = window.localStorage.getItem("Workstation");
console.log(this.state.questions);
if (this.state.questions.length) {
return (
<div>
<h2 style={{ textAlign: "center" }}>
Completed Workstation Assessments
</h2>
<ul>
<button disabled className="btn btn-secondary">
Workstation Assessments
</button>
<Link to="./admin-center">
<button className="btn btn-secondary">Edit Questions</button>
</Link>
<Link to="./admin-center-view-users">
<button className="btn btn-secondary">View Users</button>
</Link>
<DropdownButton
style={{ float: "right" }}
id="dropdown-basic-button"
title="Completed"
>
<Dropdown.Item>
{" "}
<Link to="admin-view-workstation-assessments-declined">
In Progress
</Link>
</Dropdown.Item>
</DropdownButton>{" "}
</ul>
<ul>
{renderTodos}{" "}
<div
style={{ userSelect: "none", cursor: "pointer" }}
id="page-numbers"
>
{renderPageNumbers}
</div>
</ul>
</div>
);
} else if (!this.state.questions.length) {
return (
<>
{" "}
<div>
<h3 style={{ textAlign: "center" }}></h3>
<ul>
<br />
<br />{" "}
<div>
<h6>
{" "}
<tr>
Desk Location Selected :{" "}
<u style={{ color: "grey" }}>{selectedWorkStation}</u>
</tr>
</h6>
</div>
<div className="jumbotron">
<li style={{ textAlign: "center" }}>
<b>no completed Workstation Self-Assessments</b>{" "}
</li>
</div>
</ul>
</div>
</>
);
}
}
}
You should save your todos inside your component state, not compute it inside render.
You shouldn't have a global variable called results either, store that inside your component state as well.
Here is a small example:
fetch(`/admin-completed-workstations`)
.then(recordset => recordset.json())
.then(results => {
this.setState({ questions: results.recordset });
console.log(this.state.questions);
// Here, inside getQuestionByUniqueDate you should store result using this.setState instead of having a global variable
// Then you can simply move the entire renderTodo function outside the render function of this component
this.state.questions &&
this.getQuestionByUniqueDate(this.state.questions);
});
LE: Here is a comprehensive article about fetching data in React.js apps:
https://www.robinwieruch.de/react-fetching-data (I recommend reading it)
LE2: You can assign both results and todos inside your componentDidMount
getQuestionByUniqueDate(questions) {
const currentResults = this.state.results ? [...this.state.results] : [];
for (var i = 0; i < questions.length; i++) {
if (
!currentResults.find(q => q.Date == questions[i].Date) ||
!currentResults.find(
q => q.AssignedWorkStation == questions[i].AssignedWorkStation
)
) {
currentResults.push(questions[i]);
}
}
return currentResults;
}
fetch(`/admin-completed-workstations`)
.then(recordset => recordset.json())
.then(res => {
const results = res.recordset &&
this.getQuestionByUniqueDate(res.recordset);
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo);
this.setState({
questions: res.recordset,
results,
currentTodos,
amountOfWorkstations: results.length
});
});

Multiple fields appearing under a parent in ReactJs

class CriteriaSetValue extends Component {
state = {
count: 0,
tableName: "",
fieldName:"",
tables: [],
fields: [],
addLine: [],
addField: []
};
onSubmit = e => {
e.preventDefault();
const addField = this.state.addField;
const size = addField.length + 1;
addField.push(size);
this.setState({
addField
});
addNewLine = event => {
event.preventDefault();
const addLine = this.state.addLine;
const size = addLine.length + 1;
const nextLine = this.state.count + 1;
addLine.push(size);
this.setState({
count: nextLine,
addLine
});
};
render() {
const {showForm, tableName, fieldName } = this.state;
const { props, state } = this;
return (
<React.Fragment>
<div className="form-wrapper">
<div className="row">
<div className="col-10 container">
<form onSubmit={this.submit} className="card">
<div className="card-header">
<h3 className="card-title">
Criteria Set
{/* <Locale value="std.formupload" /> */}
</h3>
</div>
<div className="card-body">
<div className="row">
<div className="col-md-7 col-lg-8">
<div className="add-line">
<Button
icon
labelPosition="left"
onClick={this.addNewLine}
>
Add Line
<Icon name="plus" />
</Button>
{this.state.addLine.map(index => {
return (
<div
className="field-line"
style={{ marginTop: "30px" }}
key={index}
id={index}
>
<h4 className="field-button">Line {index}</h4>
<Button
className="field-button"
icon
onClick={this.toggleForm}
>
<Icon name="box" />
</Button>
</div>
);
})
}
{
this.state.addField.map(index => {
return (
<React.Fragment>
<div
className="field-button"
style={{
marginTop: "20px",
paddingLeft: "20px"
}}
key={index}
id={index}
>
<h4
className="field-button"
onclick={this.addCriteriaValue}
>
<span>
table.field
</span>
</h4>
<Button
className="field-button"
icon
onClick={this.toggleDelete}
>
<Icon name="delete calendar" />
</Button>
</div>
<br></br>
</React.Fragment>
);
})
}
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
};
This is kind of related to what I am trying to achieve:
https://codesandbox.io/s/3vqyo8xlx5
But I want the child to appear under the preference of where the "Add child" button is clicked, not on the last one.
I am working on making multiple fields to appear under each line when a button beside line1, line2 and so on is clicked, but currently, the field jump to last lines when a button is clicked, it is not appearing on the appropriate line.
I have been able to show lines when the "Add Line" button is clicked and I have also been able to show the field when the button beside "Line #1" is clicked.
I want the fields for "Line #1" to show under the line1 when the field button is clicked, the field for "Line #2" to show under the line2 when the field button beside it is clicked and so on
You can try something like this:
const AddButton = ({ label, onClick }) => (
<button onClick={onClick}>
{label} [+]
</button>
)
const FieldData = ({ label, criterias, onAdd, onDelete }) => (
<React.Fragment>
<div
className='field-button'
style={{
marginTop: '20px',
paddingLeft: '20px'
}}
>
<h4 className='field-button'>
<AddButton
label='Add criteria'
onClick={onAdd}
/>
<span>
{label}
</span>
</h4>
{criterias.map((item, idx) => (
<p key={idx}>{item.id}</p>
))}
<button
className='field-button'
onClick={onDelete}
>
Del [-]
</button>
</div>
<br />
</React.Fragment>
)
class App extends React.PureComponent {
state = {
lines: []
}
handleSubmit = e => {
e.preventDefault()
console.log('DATA TO SAVE ' + JSON.stringify(this.state.lines))
}
handleAddLine = event => {
event.preventDefault()
this.setState(prevState => ({
...prevState,
lines: [
...prevState.lines,
{
id: (prevState.lines.length + 1),
fields: []
}
]
}))
}
handleAddField = lineId => e => {
e.preventDefault()
this.setState(prevState => {
const newLines = [ ...prevState.lines ]
const curLine = newLines[newLines.findIndex(({ id }) => id === lineId)]
curLine.fields.push({
id: curLine.fields.length + 1,
criterias: []
})
return {
...prevState,
lines: newLines
}
})
}
handleAddCriteria = (lineId, fieldId) => event => {
event.preventDefault()
this.setState(prevState => {
const newLines = [ ...prevState.lines ]
const curLine = newLines[newLines.findIndex(({ id }) => id === lineId)]
const curField = curLine.fields[curLine.fields.findIndex(({ id }) => id === fieldId)]
curField.criterias.push({
id: curField.criterias.length + 1
})
return {
...prevState,
lines: newLines
}
})
}
handleDeleteField = (lineId, fieldId) => event => {
event.preventDefault()
this.setState(prevState => {
const newLines = [ ...prevState.lines ]
const curLine = newLines[newLines.findIndex(({ id }) => id === lineId)]
curLine.fields = curLine.fields.filter(item => item.id !== fieldId)
return {
...prevState,
lines: newLines
}
})
}
render() {
const { lines } = this.state
return (
<React.Fragment>
<div className='form-wrapper'>
<div className='row'>
<div className='col-10 container'>
<form
onSubmit={this.handleSubmit}
className='card'
>
<div className='card-header'>
<h3 className='card-title'>
Criteria Set
</h3>
</div>
<div className='card-body'>
<div className='row'>
<div className='col-md-7 col-lg-8'>
<div className='add-line'>
<AddButton
label='Add Line'
onClick={this.handleAddLine}
/>
{lines.map((line, idx) => {
return (
<React.Fragment key={idx}>
<div
className='field-line'
style={{ marginTop: '30px' }}
>
<h4 className='field-button'>
Line {idx+1}
</h4>
<AddButton
label='Add field'
onClick={this.handleAddField(line.id)}
/>
</div>
{line.fields.map((lField, idx) => (
<FieldData
label={idx+1}
criterias={lField.criterias}
onAdd={this.handleAddCriteria(line.id, lField.id)}
onDelete={this.handleDeleteField(line.id, lField.id)}
/>
))}
</React.Fragment>
)
})}
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I defined a new data structure for the state, so It is better to think about nested data, and I refactored the code a little.

Categories

Resources