Assign active class to the clicked button - javascript

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>

Related

moving components up and down

I have a simple project.
I want to move components up and down with buttons. How can I do that?
for example; https://codesandbox.io/s/epic-water-k1m4fg
import React, { useState } from "react";
function arraymove(arr, fromIndex, toIndex) {
const arrCopy = [...arr];
var element = arrCopy[fromIndex];
arrCopy.splice(fromIndex, 1);
arrCopy.splice(toIndex, 0, element);
return arrCopy;
}
export default function App() {
const [components, setComponents] = useState([]);
function MoveUp(index) {
if (index === 0) return;
const updatedArray = arraymove(components, index, index - 1);
setComponents(updatedArray);
}
function MoveDown(index) {
if (index === components.length - 1) return;
const updatedArray = arraymove(components, index, index + 1);
setComponents(updatedArray);
}
const MovableComponent = (props) => (
<>
<div>{props.id}</div>
<button onClick={() => MoveUp(props.index)}> Move Up </button>
<button onClick={() => MoveDown(props.index)}> Move Down </button>
<br />
<br />
</>
);
return (
<div className="App">
{components.map(({ id }, index) => (
<MovableComponent key={index} id={id} index={index} />
))}
<button
onClick={() => {
setComponents((prev) => [
...prev,
{ id: `component #${components.length}` }
]);
}}
>
Add
</button>
</div>
);
}
Codesandbox

React / Nextjs: .map() output based on state array doesn't rerender because of keys don't change

I have a react component Matches.js that handles the output of tournament matches, sorted by their rounds. All the data is fetched from a parent component which fetches it via nextjs SSR and then gives the data as props to the child component. To avoid additional requests via page reloads based on data changes (new, update, delete), I'm trying to update the output via a state array matches, which is updated by child components if the backend request worked. It works flawlessly with add and delete operations, but updating gives me serious headaches. Only updating a match item doesn't rerender the match output at all.
For displaying the matches, I use the const displayMatches which maps matches according to their rounds, so two .map() functions. I've pinpointed the problem to the keys which react demands as unique props. When they don't change, displayMatches doesn't rerender with the updated data. I'm using match._id as unique values for the keys which always stay the same. I tried randomizing the the id a bit, but the results were weird and wonky at best.
How may I trigger a rerender with the updated values after an update operations? I'd like to avoid going the 'easy way' by just forcing the page to reload, which works fine.
The data of the state array is clearly updated on time, as seen in console.logs. The array consists of objects like this one:
{
tournamentId: tournamentId,
_id: id,
encho: encho,
round: round,
red: {
name: redName,
ippon: redIppon,
hansoku: redHansoku,
winByHantei: redWinByHantei
},
white: {
name: whiteName,
ippon: whiteIppon,
hansoku: whiteHansoku,
winByHantei: whiteWinByHantei
}
Matches.js:
import { useState } from "react"
import SingleMatchView from "./SingleMatchView"
import SingleMatchEdit from "./SingleMatchEdit"
import { roundMap, refreshPage } from "../../store/helpers"
import Button from "#mui/material/Button"
import Stack from "#mui/material/Stack"
import RefreshIcon from "#mui/icons-material/Refresh"
export default function Matches({
matches: matchData,
isLoggedIn,
tournamentId
}) {
const [matches, setMatches] = useState(matchData)
const [sortASC, setSortASC] = useState(true)
const addMatchToState = (match) => {
return setMatches((prev) => [...prev, match])
}
const updateMatchInState = (updateData) => {
setMatches((prev) => {
return prev.map((match) => {
if (updateData._id === match._id) {
return {
...match,
...updateData
}
}
return match
})
})
}
const deleteMatchInState = (matchId) => {
return setMatches((prev) => {
return prev.filter((match) => {
return match._id != matchId
})
})
}
const uniqueRounds = [...new Set(matches?.map((match) => match.round))]
const displayMatches = uniqueRounds.map((round) => {
return (
<div key={round}>
<h2>{roundMap[round]}</h2>
{matches
.filter((match) => match.round === round)
.map((match) => (
<SingleMatchView
key={match._id}
tournamentId={tournamentId}
matchData={match}
isLoggedIn={isLoggedIn}
deleteMatchInState={deleteMatchInState}
updateMatchInState={updateMatchInState}
/>
))}
</div>
)
})
console.log("Matches rerendered")
return (
<>
<Stack direction="row" spacing={2} my={2} justifyContent={"center"}>
<Button
onClick={() => setSortASC((prev) => !prev)}
variant="contained"
color="secondary"
>
{sortASC ? "Sort Pool ➝ Final" : "Sort Final ➝ Pool"}
</Button>
<Button
onClick={refreshPage}
variant="contained"
startIcon={<RefreshIcon />}
color="secondary"
>
Refresh
</Button>
</Stack>
{isLoggedIn && (
<SingleMatchEdit
addMatchToState={addMatchToState}
isNew={true}
tournamentId={tournamentId}
/>
)}
{matches.length === 0 && "No matches yet"}
{sortASC ? displayMatches : displayMatches.reverse()}
</>
)
}
/edit: additional code
SingleMatchView.js
import { useState } from "react"
import { roundMap } from "../../store/helpers"
import SingleMatchEdit from "./SingleMatchEdit"
import { httpDeleteIndividualMatch } from "../../hooks/requests"
import Stack from "#mui/material/Stack"
import Button from "#mui/material/Button"
import DeleteIcon from "#mui/icons-material/Delete"
import EditIcon from "#mui/icons-material/Edit"
import ClearIcon from "#mui/icons-material/Clear"
export default function SingleMatchView({
tournamentId,
matchData,
isLoggedIn,
updateMatchInState,
deleteMatchInState
}) {
const hansokuIcon = "▲"
const [editMode, setEditMode] = useState(false)
const [confirmDelete, setConfirmDelete] = useState(false)
const [encho, setEncho] = useState(matchData?.encho || false)
const [round, setRound] = useState(
matchData?.round || Object.keys(roundMap)[0]
)
const [redName, setRedName] = useState(matchData?.red?.name || "")
const [redIppon, setRedIppon] = useState(matchData?.red?.ippon || "")
const [redHansoku, setRedHansoku] = useState(matchData?.red?.hansoku || 0)
const [redWinByHantei, setRedWinByHantei] = useState(
matchData?.red?.winByHantei || false
)
const [whiteName, setWhiteName] = useState(matchData?.white?.name || "")
const [whiteIppon, setWhiteIppon] = useState(matchData?.white?.ippon || "")
const [whiteHansoku, setWhiteHansoku] = useState(
matchData?.white?.hansoku || 0
)
const [whiteWinByHantei, setWhiteWinByHantei] = useState(
matchData?.white?.winByHantei || false
)
const redPoints = redIppon.length + Math.floor(whiteHansoku / 2)
const whitePoints = whiteIppon.length + Math.floor(redHansoku / 2)
const redIpponIcons = redIppon?.split("").map((ippon, i) => {
return (
<span key={i} className="ippon-icon">
{ippon}
</span>
)
})
const whiteIpponIcons = whiteIppon?.split("").map((ippon, i) => {
return (
<span key={i} className="ippon-icon">
{ippon}
</span>
)
})
const isHikiWake = redPoints === whitePoints
// const dataObject = {
// encho: encho,
// round: round,
// red: {
// name: redName.trim(),
// ippon: redIppon,
// hansoku: redHansoku,
// winByHantei: redWinByHantei
// },
// white: {
// name: whiteName.trim(),
// ippon: whiteIppon,
// hansoku: whiteHansoku,
// winByHantei: whiteWinByHantei
// }
// }
let deleteTimer
const prepareDeleteFight = async () => {
setConfirmDelete(true)
deleteTimer = setTimeout(() => {
setConfirmDelete(false)
}, 5000)
}
const confirmDeleteFight = async () => {
try {
const response = await httpDeleteIndividualMatch(matchData._id)
if (response.acknowledged) {
return deleteMatchInState(matchData._id)
}
console.log("Error, match not deleted")
return
} catch (err) {
console.log(err)
} finally {
setConfirmDelete(false)
clearTimeout(deleteTimer)
}
}
return (
<>
<div className="display">
{/* {matchData._id} */}
{/* {round && <div className="round">{round}</div>} */}
<div className="board-single-results">
<div className="red-color stripe"></div>
<div
className="red-name"
// Warning: Prop `style` did not match. Server: "null" Client: "background-color:"
// style={{
// "background-color":
// redPoints > whitePoints ? "gold" : ""
// }}
>
{redName.toUpperCase() || "???"}
</div>
<div className="hansoku red-hansoku">
{hansokuIcon.repeat(redHansoku)}
</div>
<div className="ippon red-ippon">
{redIppon && redIpponIcons.reverse()}
</div>
<div className="points red-points">{redPoints}</div>
<div className="win-modifier">
{
// to do: Encho geht nicht gleichzeitig mit Hikiwake, check einfügen
}
{encho && !isHikiWake && "E"}
{!encho && isHikiWake && <ClearIcon />}
</div>
<div className="points white-points">{whitePoints}</div>
<div className="ippon white-ippon">
{whiteIppon && whiteIpponIcons}
</div>
<div className="hansoku white-hansoku">
{hansokuIcon.repeat(whiteHansoku)}
</div>
<div
className="white-name"
// Warning: Prop `style` did not match. Server: "null" Client: "background-color:"
// style={{
// "background-color":
// whitePoints > redPoints ? "gold" : ""
// }}
>
{whiteName.toUpperCase()}
</div>
<div className="white-color stripe"></div>
</div>
{isLoggedIn && (
<div className="toolbox">
<Stack direction="row" spacing={2}>
<Button
onClick={() => setEditMode((prev) => !prev)}
variant="contained"
startIcon={<EditIcon />}
size="small"
>
EDIT
</Button>
{!confirmDelete && (
<Button
onClick={prepareDeleteFight}
color="error"
variant="contained"
startIcon={<DeleteIcon />}
size="small"
>
Delete
</Button>
)}
{confirmDelete && (
<Button
onClick={confirmDeleteFight}
color="error"
variant="contained"
startIcon={<DeleteIcon />}
size="small"
>
Confirm Deletion
</Button>
)}
</Stack>
</div>
)}
</div>
{editMode && (
<SingleMatchEdit
matchData={matchData}
tournamentId={tournamentId}
setEditMode={setEditMode}
updateMatchInState={updateMatchInState}
/>
)}
</>
)
}
SingleMatchEdit.js (handleSubmitUpdate being the important part here)
export default function SingleMatchEdit({
matchData,
isNew,
tournamentId,
setEditMode,
updateMatchInState,
addMatchToState
}) {
const matchId = matchData?._id
const [encho, setEncho] = useState(matchData?.encho || false)
const [round, setRound] = useState(
matchData?.round || Object.keys(roundMap)[0]
)
const [redName, setRedName] = useState(matchData?.red?.name || "")
const [redIppon, setRedIppon] = useState(matchData?.red?.ippon || "")
const [redHansoku, setRedHansoku] = useState(matchData?.red?.hansoku || 0)
const [redWinByHantei, setRedWinByHantei] = useState(
matchData?.red?.winByHantei || false
)
const [whiteName, setWhiteName] = useState(matchData?.white?.name || "")
const [whiteIppon, setWhiteIppon] = useState(matchData?.white?.ippon || "")
const [whiteHansoku, setWhiteHansoku] = useState(
matchData?.white?.hansoku || 0
)
const [whiteWinByHantei, setWhiteWinByHantei] = useState(
matchData?.white?.winByHantei || false
)
const resetMatchData = () => {
setEncho(false)
setRound(Object.keys(roundMap)[0])
setRedName("")
setRedIppon("")
setRedHansoku(0)
setRedWinByHantei(false)
setWhiteName("")
setWhiteIppon("")
setWhiteHansoku(0)
setWhiteWinByHantei(false)
}
const addRedIppon = (e) => {
if (redIppon.length >= 2) return
setRedIppon((prev) => prev.concat(e.target.name))
}
const remRedIppon = () => setRedIppon("")
const addWhiteIppon = (e) => {
if (whiteIppon.length >= 2) return
setWhiteIppon((prev) => prev.concat(e.target.name))
}
const remWhiteIppon = () => setWhiteIppon("")
const redIpponButtons = ipponButtons(ipponMap, addRedIppon, "red")
const whiteIpponButtons = ipponButtons(ipponMap, addWhiteIppon, "white")
const matchDataToSend = {
tournamentId: tournamentId,
encho: encho,
round: round,
red: {
name: redName.trim(),
ippon: redIppon,
hansoku: redHansoku,
winByHantei: redWinByHantei
},
white: {
name: whiteName.trim(),
ippon: whiteIppon,
hansoku: whiteHansoku,
winByHantei: whiteWinByHantei
}
}
const handleSubmitNew = async (e) => {
e.preventDefault()
try {
const response = await httpSubmitMatch(matchDataToSend)
if (response.acknowledged) {
addMatchToState({
...matchDataToSend,
_id: response.insertedId
})
resetMatchData()
}
if (!response.acknowledged) {
// do something
throw new Error("Data not acklowleged!")
}
} catch (err) {
console.log(err)
}
}
const handleSubmitUpdate = async (e) => {
e.preventDefault()
try {
const response = await httpUpdateIndividualMatch(
matchId,
matchDataToSend
)
if (response.acknowledged) {
// To Do: Update State
updateMatchInState({ ...matchDataToSend, _id: matchId })
setEditMode(false)
}
if (!response.acknowledged) {
// do something
throw new Error("Data not acklowleged!")
}
} catch (err) {
console.log(err)
}
}
function removeRedHansoku() {
if (redHansoku <= 0) return
setRedHansoku((prev) => prev - 1)
}
function addRedHansoku() {
if (redHansoku >= 4) return
setRedHansoku((prev) => prev + 1)
}
function removeWhiteHansoku() {
if (whiteHansoku <= 0) return
setWhiteHansoku((prev) => prev - 1)
}
function addWhiteHansoku() {
if (whiteHansoku >= 4) return
setWhiteHansoku((prev) => prev + 1)
}
const rounds = Object.keys(roundMap).map((round) => {
return (
// <option key={round} value={round}>
// {roundMap[round]}
// </option>
<MenuItem key={round} value={round}>
{roundMap[round]}
</MenuItem>
)
})
return (
<div className={styles["edit-board"]}>
<div className={styles["board"]}>
<div className={styles["red-color"]}></div>
<div className={styles["red-name"]}>
{/* Replace with Autocomplete and a list of all names that were entered in the past, retrieved from DB */}
{/* <TextField
fullWidth
id="outlined-basic"
label="Name"
variant="outlined"
size="small"
margin="normal"
value={redName}
onChange={(e) => setRedName(e.target.value)}
/> */}
<AutocompletePlayerName
value={redName}
setNameFunc={setRedName}
/>
</div>
<div className={styles["white-name"]}>
{/* Replace with Autocomplete and a list of all names that were entered in the past, retrieved from DB */}
{/* <TextField
fullWidth
id="outlined-basic"
label="Name"
variant="outlined"
size="small"
margin="normal"
value={whiteName}
onChange={(e) => setWhiteName(e.target.value)}
/> */}
<AutocompletePlayerName
value={whiteName}
setNameFunc={setWhiteName}
/>
</div>
<div className={styles["white-color"]}></div>
<div className={`${styles.ippon} ${styles["red-ippon"]}`}>
<div className={styles["awarded-ippon"]}>
{redIppon ? `➝${redIppon}` : "(IPPON)"}
</div>
<div className={styles["add-ippon-icon-table"]}>
{redIpponButtons}
</div>
<Button
color="warning"
variant="contained"
startIcon={<CancelOutlinedIcon />}
size="small"
onClick={remRedIppon}
>
Reset Ippon
</Button>
</div>
<div className={`${styles.ippon} ${styles["white-ippon"]}`}>
<div className={styles["awarded-ippon"]}>
{whiteIppon ? `➝${whiteIppon}` : "(IPPON)"}
</div>
<div className={styles["add-ippon-icon-table"]}>
{whiteIpponButtons}
</div>
<Button
color="warning"
variant="contained"
startIcon={<CancelOutlinedIcon />}
size="small"
onClick={remWhiteIppon}
>
Reset Ippon
</Button>
</div>
<div
className={styles["red-hansoku"]}
style={{ justifyContent: "space-between" }}
>
<IconButton
onClick={removeRedHansoku}
sx={{ color: "#00000078" }}
>
<RemoveCircleIcon />
</IconButton>
{redHansoku ? "▲".repeat(redHansoku) : "(HANSOKU)"}
<IconButton
onClick={addRedHansoku}
sx={{ color: "#00000078" }}
>
<AddCircleIcon />
</IconButton>
</div>
<div
className={styles["white-hansoku"]}
style={{ justifyContent: "space-between" }}
>
<IconButton
onClick={removeWhiteHansoku}
sx={{ color: "#00000078" }}
>
<RemoveCircleIcon />
</IconButton>
{whiteHansoku ? "▲".repeat(whiteHansoku) : "(HANSOKU)"}
<IconButton
onClick={addWhiteHansoku}
sx={{ color: "#00000078" }}
>
<AddCircleIcon />
</IconButton>
</div>
<div className={styles["additional-information"]}>
<FormControl variant="standard" size="small">
<Select
labelId=""
id=""
value={round}
label="Round"
onChange={(e) => setRound(e.target.value)}
>
{rounds}
</Select>
{/* <select
name="round"
value={round}
onChange={(e) => setRound(e.target.value)}
>
{rounds}
</select> */}
<FormControlLabel
control={
<Checkbox
checked={encho}
onChange={() => setEncho((prev) => !prev)}
/>
}
label="Encho"
/>
</FormControl>
{/* <label>
<input
type="checkbox"
defaultChecked={encho}
onChange={() => setEncho((prev) => !prev)}
/>{" "}
Encho
</label> */}
</div>
</div>
<div className={styles.toolbox}>
{!isNew && (
<Stack direction="row" spacing={2}>
<Button
onClick={() => setEditMode((prev) => !prev)}
variant="contained"
startIcon={<EditIcon />}
size="small"
>
CANCEL
</Button>
<Button
onClick={handleSubmitUpdate}
variant="contained"
startIcon={<EditIcon />}
size="small"
>
Submit UPDATE
</Button>
</Stack>
)}
{isNew && (
<Stack direction="row" spacing={2}>
<Button
color="warning"
variant="contained"
startIcon={<CancelOutlinedIcon />}
size="small"
onClick={resetMatchData}
>
Reset
</Button>
<Button
onClick={handleSubmitNew}
variant="contained"
startIcon={<SendIcon />}
size="small"
>
Submit NEW
</Button>
</Stack>
)}
</div>
</div>
)
}
/edit 2: Solution
Based on Milos Pavlovic comment (marked solution), I've updated SingleMatchView.js. I put the matchData into it's own state and included a useEffect to listen to changes in matchData. Thanks!
Updated SingleMatchView.js:
// ...imports
export default function SingleMatchView({
tournamentId,
matchData,
isLoggedIn,
updateMatchInState,
deleteMatchInState
}) {
const hansokuIcon = "▲"
const [editMode, setEditMode] = useState(false)
const [confirmDelete, setConfirmDelete] = useState(false)
// deleted all individual state
// edit: even simpler, no state needed
// deleted: const [match, setMatch] = useState(matchData)
// added:
const match = matchData
useEffect(() => {
setMatch(matchData)
}, [matchData])
const redPoints =
match.red.ippon.length + Math.floor(match.white.hansoku / 2)
const whitePoints =
match.white.ippon.length + Math.floor(match.red.hansoku / 2)
...character limit
After looking at your code I would say that your problem lies inside SingleMatchView. As I can see you have multiple useStates that use matchData in order to fill state initial values. What is wrong here is the fact that you are not "resetting" those state values once you successfully update certain match.
SingleMatchView element will just rerender, and not to recreate, after update finish its work, meaning that you must find a way to reset(to new values) all those states that are using matchData prop for their values, otherwise you will end up with unchanged values across whole element lifecycle.
Let's explain problem using this line inside SingleMatchView:
const [redName, setRedName] = useState(matchData?.red?.name || "")
As we can see redName is initialized only once. Now imagine that inside SingleMatchEdit you are updating redName. What current code does is that inside handleSubmitUpdate you just call state updater from root component and that is all, problem is that SingleMatchView is not aware of update because this element uses its inner state, which is declared and assigned only once, and with that approach you decoupled from root component state and not listening to state updates at all - and that is why you will never rerender with updated info, because you never recompute your inner state inside SingleMatchView based on new prop value for matchData.
One solution would be to put useEffect inside SingleMatchView to listen for matchData change, and whenever prop value changes you should (re)set all states to new, latest, values, thus forcing content to rerender. Other solution is to intercept handler execution inside SingleMatchView, and before/after calling updateMatchInState you just (re)set state.

How to change this code more simply using for loop?

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;

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

Add prop when onClick in ReactJS

I would like to change current li item color when I click it.
How to add prop to item(using array map), when I click it? I use styled-components
const Li = styled.li`
color: ${props => (props.checked ? "red" : "green")};
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: []
};
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index}>
{item}
<button onClick={() => this.deleteItemHandler(index)}> Delete</button>
</Li>
);
});
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
}
}
Check out this code working on CodeSandBox
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import styled from "styled-components";
const Li = styled.li`
color: ${props => (props.checked ? "red" : "green")};
`;
class App extends Component {
state = {
value: "",
items: [],
selected: -1
};
handleChange = e => {
this.setState({
[e.currentTarget.name]: e.currentTarget.value
});
};
handleAdd = () => {
const { items, value } = this.state;
this.setState({
items: [...items, value],
value: ""
});
};
handleRemove = index => {
const { items, selected } = this.state;
items.splice(index, 1);
if (index < selected) index = selected - 1;
if (index === selected) index = -1;
if (index > selected) index = selected;
this.setState({
items: items,
selected: index
});
};
handleActiveItem = index => {
this.setState({ selected: index });
};
render() {
const { value, items, selected } = this.state;
return (
<div>
<input
type="text"
value={value}
name="value"
onChange={this.handleChange}
/>
<button
style={{ margin: "0px 5px" }}
disabled={!value}
className="btn btn-sm btn-success"
onClick={this.handleAdd}
>
+
</button>
<ul className="li">
{items.map((item, index) => (
<Li key={index} checked={selected === index}>
<span onClick={() => this.handleActiveItem(index)}>{item}</span>
<button
style={{ margin: "1px 5px" }}
className="btn btn-sm btn-danger"
onClick={() => this.handleRemove(index)}
>
X
</button>
</Li>
))}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Ignore the handlers if you don't need them. Thanks to this effort I learnt about styled-components and discovered CodeSandBox.
EDIT :
Added a <span> inside <li> to avoid nested onClick, previously <li> (parent) and <button> (child) both had onClick attribute. On button Click two onClick events were fired resulting in unexpected behaviour in some use cases. You must correct this in your code.
Added logic to keep item selected when an item before it is deleted.
Added button validation to avoid adding empty string/items in list.
Also updated CodeSandBox Code to reflect above changes.
So you need keep track of the active index, and use it too change the color of the active component color.
state ={
activeIndex: void 0
}
const Li = styled.li`
color: ${props => props.checked ? "red" : "green"};
;`
deleteItemHandler = (index) => {
this.setState({
activeIndex: index
})
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index} checked={index === this.state.activeIndex} > {item} < button onClick={() => this.deleteItemHandler(index)
}> Delete</button ></Li >
)
})
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
Try this
const Li = styled.li`
color: ${props => props.checked ? "red" : "green"};
;`
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
currentChecked: null
};
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index} checked={index === this.state.currentChecked} >
{item}
<button onClick={() => this.setState({currentChecked: index})}>Delete</button >
</Li >
)
})
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);

Categories

Resources