Unable to connect react drag and drop with backend in MERN - javascript

I have implemented a simple drag and drop code here below using react-beautiful-dnd. The frontend alone works perfectly fine, however, when I tried to connect this with the backend the items in the droppable context are unable to be dragged. That is, I am unable to drag the items within the column and also between columns. Further, I am unable to figure out how to pass the index of the elements in the mongoDb database.
The code I used is here below
projectsDashboard.js
function ProjectsDashboard() {
const handleDragEnd = ({destination, source}) => {
if (!destination) {
return
}
if (destination.index === source.index && destination.droppableId === source.droppableId) {
return
}
// Creating a copy of item before removing it from state
const itemCopy = {...state[source.droppableId].items[source.index]}
setState(prev => {
prev = {...prev}
// Remove from previous items array
prev[source.droppableId].items.splice(source.index, 1)
// Adding to new items array location
prev[destination.droppableId].items.splice(destination.index, 0, itemCopy)
return prev
})
}
const dispatch = useDispatch();
useEffect(() => {
dispatch(getStages());
},[dispatch]);
const { stage } = useSelector(state => state.stage);
var formattedArray = stage.map(item => Object.keys(item).map(i => item[i]));
console.log(formattedArray)
return (
<DragDropContext onDragEnd={handleDragEnd}>
{_.map(state, (data, key) => {
return(
<div key={key} className={"column"}>
{console.log(key , "KEY")}
<ProjectWrapper className="border">
<h3 className="title">{data.title}</h3>
</ProjectWrapper>
<Droppable droppableId={key}>
{(provided, snapshot) => {
return(
<div>
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={"droppable-col"}
>
<hr className="line" style={{opacity: 10 }}></hr>
{stage.map(stages=>{
if(stages.status == key)
return(
<Draggable key={stages._id}
//index={index}
draggableId={stages._id} className="drag">
{(provided, snapshot) => {
console.log(snapshot)
return(
<div
className={`item ${snapshot.isDragging && "dragging"}`}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{/* card name */}
<button className="stageDetails" style={{padding: "0",border: "none", background: "none"}}>
{stages.stageName}
</button>
<DeleteOutlinedIcon className="delete" />
</div>
)
}}
</Draggable>
)
})}
{provided.placeholder}
</div>
</div>
)
}}
</Droppable>
</div>
)
})}
</DragDropContext>
</div>
);
};
export default ProjectsDashboard;

Related

React JS app freezing when useEffect is used

My React application started freezing when changing tabs with items previously set by useEffect by reading index tab and filtering array against item's categoryID. The reason is useEffect () but I have no other idea how to otherwise create a realtime array refresh depending on where the user clicks. There is no error in console.
I must add that the application was working normally a couple of minutes earlier.
useEffect(() => {
if(value && shopProducts){
const filtered = shopProducts.filter(item => item.categoryID === value)
setUserChoose(() => filtered)
}else if(value === 0 && shopProducts){
const filtered = shopProducts.filter(item => item.categoryID === value)
setUserChoose(() => filtered)
}
}, [value, shopProducts])
Items maping
<TabPanel value={0} index={0}>
{userChoose.map((item, index) => {
return(
<DishComponent name={item.name} price={item.price} description={item.description} id={item.id} img={item.img} clickedData={setItem}/>
)
})}
</TabPanel>
<TabPanel value={1} index={1}>
{userChoose.map((item, index) => {
return(
<DishComponent name={item.name} price={item.price} description={item.description} id={item.id} img={item.img} clickedData={setItem}/>
)
})}
</TabPanel>
<TabPanel value={2} index={2}>
{userChoose.map((item, index) => {
return(
<DishComponent name={item.name} price={item.price} description={item.description} id={item.id} img={item.img} clickedData={setItem}/>
)
})}
</TabPanel>
Dish Component
import React, { useEffect } from 'react'
import './dish.css'
const DishComponent = ({name,price,description,id, img, shopID, clickedData}) => {
return (
<>
<div className='dish-component' onClick={clickedData([{name: name, id: id, price: price}])}>
<div className='right-content'>
<p>{name}</p>
<p style={{marginTop: '-15px'}}>{price} eur.</p>
<p style={{color: 'gray',marginTop: '-15px'}}>{description}</p>
</div>
<div className='left-content'>
<img className='img' alt={name} src={img}/>
</div>
</div>
</>
)
}
export default DishComponent
I got that. For first deleted this bunch of code.
useEffect(() => {
if(value && shopProducts){
const filtered = shopProducts.filter(item => item.categoryID === value)
setUserChoose(() => filtered)
}else if(value === 0 && shopProducts){
const filtered = shopProducts.filter(item => item.categoryID === value)
setUserChoose(() => filtered)
}
}, [value, shopProducts])
I did a resarch on useMemo () and useCallback (). I used useMemo () in this case because it doesn't re-render until it finds a value change in state.
useMemo(() => {
const filtred = shopProducts.filter((item) => item.categoryID === value)
setUserChoose(() => filtred)
},[value, shopProducts])
And next i changed my code inside dishcomponent.js. To setup array with previous items and new items.
import React, { useEffect } from 'react'
import './dish.css'
const DishComponent = ({name,price,description,id, img, shopID, setItem}) => {
const setup = () => {
setItem((prev) => [...prev, {name: name,price: price,id:id, shopID:shopID}])
}
return (
<>
<div className='dish-component' onClick={() => setup()}>
<div className='right-content'>
<p>{name}</p>
<p style={{marginTop: '-15px'}}>{price} eur.</p>
<p style={{color: 'gray',marginTop: '-15px'}}>{description}</p>
</div>
<div className='left-content'>
<img className='img' alt={name} src={img}/>
</div>
</div>
</>
)
}
export default DishComponent

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.

list of ref shows undefined when logging

I've made a list of refs for each of my components that is being rendered in a map, I am assigning a ref to a button within EditWebAppTypeForm and am trying to use it within MappedAccordion but it shows undefined? what can I do to make sure ref is set before passing it in the MappedAccordion component?
The information logged in addtoRefs function is correct, it shows -
(2) [button, button]
I've removed a lot of the code so its easier to read.
function Admin() {
const allRefs = useRef([] as any);
allRefs.current = [];
const addtoRefs = (e: any) => {
if (e && !allRefs?.current?.includes(e)) {
allRefs?.current?.push(e);
}
console.log(allRefs.current); <-- Logs correct info
};
return (
<div className="adminContainer">
<Grid container spacing={2}>
<Grid item md={8} xs={12} sm={12}>
<div style={{ width: 725, paddingBottom: 150 }}>
{webAppTypes &&
webAppTypes.map((a: IWebAppType, index: number) => {
return (
<>
<Accordion
key={a.id}
defaultExpanded={a.id === 0 ? true : false}
>
<AccordionDetails>
<EditWebAppTypeForm
key={a.name}
setWebAppTypes={setWebAppTypes}
IWebAppTypeModel={a}
ref={addtoRefs} // <-- returning ref to add to list
/>
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={allRefs.current[index]} <-- using ref but showing undefined in MappedAccordion component
/>
</AccordionDetails>
</Accordion>
</>
);
})}
</div>
</Grid>
</Grid>
</div>
);
}
export default Admin;
EditWebAppTypeForm Component -
const EditWebAppTypeForm = (props: any, ref: any) => {
return (
<div className="editWebAppSContainer">
<form onSubmit={handleSubmit(onSubmit)} id="edit-app-form">
<button hidden={true} ref={ref} type="submit" /> // <-- Assiging ref to button
</form>
</div>
);
};
export default forwardRef(EditWebAppTypeForm);
type MappedAccordionProps = {
waobj: IWebAppType;
setWebAppTypes: Dispatch<SetStateAction<IWebAppType[]>>;
editRef: any;
};
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
}: MappedAccordionProps) {
const onSubmit = (data: FormFields) => {
console.log(editRef); // <-- Logs undefined
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
</form>
</div>
);
}
export default MappedAccordion;
I would create an extra component WebAppTypeAccordion like this :
function WebAppTypeAccordion({a, setWebAppTypes}) {
const [formEl, setFormEl] = useState(null);
function handleRef(el) {
if (el) {
setFormEl(el)
}
}
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionDetails>
<EditWebAppTypeForm
setWebAppTypes={setWebAppTypes}
IWebAppTypeModel={a}
ref={handleRef}
/>
<MappedAccordion
waobj={a}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
/>
</AccordionDetails>
</Accordion>
);
}
Then you can update the Admin component :
webAppTypes.map((a: IWebAppType) => (
<WebAppTypeAccordion key={a.id] a={a} setWebAppTypes={setWebAppTypes} />
))

React: onClick works but onDoubleClick doesn't

For some reason, this code works:
const Task = ({task, onDelete, onToggle}) => {
return (
<div className="task"
onClick={() => onToggle(task.id)}>
</div>
But this one doesn't:
const Task = ({task, onDelete, onToggle}) => {
return (
<div className="task"
onDoubleClick={() => onToggle(task.id)}>
</div>
The rest of the code is this, if it helps:
App.js
const toggleReminder = (id) => {
console.log("double click!", id);
}
return (
<div className="container">
<Header title="Task Tracker"/>
{tasks.length > 0 ? (
<Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder}/>
) : ("No Tasks to show")
}
Tasks.js
const Tasks = ({ tasks, onDelete, onToggle }) => {
return (
<>
{tasks.map((task, index) => (
<Task key={index} task={task} onDelete={onDelete} onToggle={onToggle} />
))}
</>
)
}
And Task.js
const Task = ({task, onDelete, onToggle}) => {
return (
<div className="task"
onDoubleClick={() => onToggle(task.id)}>
<h3>{task.text}
<FaTimes
style={{color:"red", cursor: 'pointer'}}
onClick={()=> onDelete(task.id)}/>
</h3>
<p>{task.day}</p>
</div>
)
}
I've been copying everything from a tutorial, and I really don't understand the problem. With onClick it works just fine. Do they work differently?
I saw a similar question but did not understand the answer
Thank you!

on click function not working because its in another return statement

I am trying to make history by pushing on button click
onclick function of the li is not working
as u can see in the code <SuggestionsList/> is in the last return statement its funtions are rendered in const SuggestionsList = (props) => { . The onclick funtion is comimg inside the const SuggestionsList funtion, this is making the onclick funtion not working
i created the exact working in codesand and its working there without any problem i dont get why its not working in my local
enter link description here
function finddoctor(e) {
console.log(e);
history.push(`/detiled/${e} `);
}
const onChange = (event) => {
const value = event.target.value;
setInputValue(value);
setShowResults(false);
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.firstname
.toString()
.toLowerCase()
.includes(value.toLowerCase()) ||
suggestion.id.toString().toLowerCase().includes(value.toLowerCase())
);
setFilteredSuggestions(filteredSuggestions);
setDisplaySuggestions(true);
};
const onSelectSuggestion = (index) => {
setSelectedSuggestion(index);
setInputValue(filteredSuggestions[index]);
setFilteredSuggestions([]);
setDisplaySuggestions(false);
};
const SuggestionsList = (props) => {
const {
suggestions,
inputValue,
onSelectSuggestion,
displaySuggestions,
selectedSuggestion,
} = props;
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list" style={styles.ulstyle}>
{suggestions.map((suggestion, index) => {
const isSelected = selectedSuggestion === index;
const classname = `suggestion ${isSelected ? "selected" : ""}`;
return (
<>
<li
style={styles.listyle}
onClick={finddoctor(suggestion.id)}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
</>
);
})}
</ul>
);
} else {
return <div>No suggestions available...</div>;
}
}
return <></>;
};
useEffect(() => {
axios
.get("admin-panel/all-doctors-list/")
.then((res) => {
const data = res.data;
setShowSerch(data);
});
}, []);
return (
<>
<div className="note-container" style={styles.card}>
<div style={styles.inner}>
<p style={{ textAlign: "left" }}>Search Doctors</p>
<form className="search-form" style={{}}>
{showResults ? (
<FontAwesomeIcon
style={{ marginRight: "-23px" }}
icon={faSearch}
/>
) : null}
<input
onChange={onChange}
value={inputValue}
style={styles.input}
type="Search"
/>
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
</form>
</div>
</div>
</>
);
};
Instead of onClick={finddoctor(suggestion.id)} (Here just function invocation is happening and expected to have the callback method)
should be
onClick={() => finddoctor(suggestion.id)}

Categories

Resources