cleaning setstate in react - javascript

I have a function called getLines which tries to find out when text is going to wrap in another line it is async and it is so time-consuming
and I have an API that gives a string with a given length (query)
import { useState, useEffect } from "react";
import getLines from "./getline"; //this function take time according to input
export default function App() {
const [text, setText] = useState<string>("");
const [arr, setArr] = useState<string[] | "error" | undefined>([]);
const [time, setTime] = useState<100 | 200 | 300 | "un">("un");
const [pending, setPending] = useState(false);
useEffect(() => {
if (time !== "un") {
(async () => {
setPending(true);
let res = await fetch(
`https://random-word-genrator.vercel.app/words?w=${time}`
);
let { response } = await res.json();
let a = await getLines(800, response, "1.2rem", "3px");
setArr(a);
setText(response);
setPending(false);
})();
}
}, [time]);
return (
<div>
<div style={{ display: "flex", gap: "1rem" }}>
<div
onClick={() => {
setTime(100);
}}
>
100
</div>
<div
onClick={() => {
setTime(200);
}}
>
200
</div>
<div
onClick={() => {
setTime(300);
}}
>
300
</div>
</div>
{pending ? (
<div>loading</div>
) : (
<div>
<div>value:={text}</div>
<div>array:={JSON.stringify(arr)}</div>
<div>length:={text.split(" ").length}</div>
</div>
)}
</div>
);
}
video of problem
you can see in video that when I click 100 it give me 100 words when I click to 200 it give me 200 words but when I click multiple button it gives me different length then what I last clicked
can anyone tell me how can i remove this problem?
sanboxLink:- sendbox link
**recreate error by quickly clicking 300 and 100 with this order 300--quickly-->100

Clicking multiple times invokes multiple operations. Since the operations are asynchronous, they can complete in any order.
If you don't want the user to be able to invoke multiple simultaneous operations, prevent/ignore the click when an operation is still pending. The pending state value you already have seems like a reasonable way to check that.
For example, you can display "loading" instead of the "buttons" (well, clickable divs):
return (
<div>
{pending ? (
<div>loading</div>
) : (
<div style={{ display: "flex", gap: "1rem" }}>
<div onClick={() => { setTime(100); }} >
100
</div>
<div onClick={() => { setTime(200); }} >
200
</div>
<div onClick={() => { setTime(300); }} >
300
</div>
</div>
<div>
<div>value:={text}</div>
<div>array:={JSON.stringify(arr)}</div>
<div>length:={text.split(" ").length}</div>
</div>
)}
</div>
);
Alternatively, you could keep the UI but just ignore the click:
<div onClick={() => {
if (!pending) {
setTime(100);
}
}} >
100
</div>

Related

Add a success notification for an action

The site has a button to remove the device from the shopping cart.
The principle of the button is as follows:
the user clicks the delete button;
a window opens (something like a modal window made using Dialog mui) with a warning about the danger of deletion and two buttons: cancel and confirm;
2a) when you click the cancel button, the window closes;
2b), when the confirmation button is pressed, the deletion process begins, which is accompanied by a scroll wheel. After deletion, the window closes and the user can continue working on the site.
I would like, after closing the window, to display a notification for a few seconds that the item was successfully deleted. The whole difficulty lies in the fact that there is no fixed deletion time (the deletion time is different depending on the amount of information about the device) and it is not clear to me when the notification window should be called.
Help to understand please.
Here is my short working code
export function Delete() {
const [alertShown, setAlertShown] = useState(false);
const [alertLoading, setAlertLoading] = useState(false);
const onNo = () => {
setAlertShown(false);
};
const onYes = async () => {
setAlertLoading(true);
await deleteItem();
setAlertShown(false);
setAlertLoading(false);
};
return <ButtonGroup >
<div onClick={() => setAlertShown(true)}>
<DeleteForeverIcon/>
</div>
{alertShown && (
<Dialog open={onYes}>
{alertLoading
? <div ><Spinner/></div>
: <DialogActions >
<Button onClick={onNo}>Cancel</Button >
<Button onClick={onYes}>Confirm </Button >
</DialogActions>}
</Dialog>
)}
</ButtonGroup>
}
Here, for a better understanding, I demonstrate a demo version of what I have going on at the moment. The code in the codeSandbox is slightly different from the code I showed above. But the principle is the same. I will be grateful for any help
I just added some basic things on what you can do here:
import { useState } from "react";
import "./styles.css";
const sleep = (ms = 1000) => new Promise((resolve) => setTimeout(resolve, ms));
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
async function deleteItem(item, firestore, urlParams) {
await deleteRecord(firestore, item.id);
}
async function deleteRecord(firestore, recordId) {
await firestore.collection("records").doc(recordId).delete();
}
const firestoreDummy = {
collection: () => ({
doc: () => ({
delete: async () => {
// Simulating delete action on firestore
await sleep(randomIntFromInterval(3000, 10000));
}
})
})
};
const Dialog = ({ loading, onYes, onNo }) => {
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.6)"
}}
>
<div style={{ backgroundColor: "white", padding: 16 }}>
<h4>Are you sure</h4>
<p>Do you really want to delete this item?</p>
{loading && (
<div style={{ marginBottom: "8px" }}>
<span style={{ fontSize: "12px", color: "gray" }}>
Item is being deleted. Please wait...
</span>
</div>
)}
<button disabled={loading} onClick={onYes}>
Yes
</button>
<button disabled={loading} onClick={onNo}>
No
</button>
</div>
</div>
);
};
const SuccessDialog = ({ setSuccessAlertShown }) => {
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.6)"
}}
>
<div style={{ backgroundColor: "white", padding: 16 }}>
<h4>Sucessfully deleted!</h4>
<button onClick={() => setSuccessAlertShown(false)}>Close</button>
</div>
</div>
);
};
export default function App() {
const [alertShown, setAlertShown] = useState(false);
const [alertLoading, setAlertLoading] = useState(false);
const [alertSucess, setSuccessAlertShown] = useState(false);
const onNo = () => {
setAlertShown(false);
};
const onYes = async () => {
setAlertLoading(true);
try {
await deleteItem({}, firestoreDummy);
setAlertShown(false);
} finally {
setAlertLoading(false);
// A simple notification
alert("The record deleted!");
// A more classic notification
setSuccessAlertShown(true);
}
};
return (
<div className="App">
<button onClick={() => setAlertShown(true)}>Delete item</button>
{alertShown && (
<Dialog onNo={onNo} onYes={onYes} loading={alertLoading} />
)}
{alertSucess && (
<SuccessDialog setSuccessAlertShown={setSuccessAlertShown} />
)}
</div>
);
}
This is the sandbox: https://codesandbox.io/s/nostalgic-hill-r8sh1x

React limits the number of renders to prevent an infinite loop error, while using empty array dependencies in useEffect()

I am trying to render some fetched data to the DOM from an API which is called in useEffect() hook. I set a boolean "isFetching" to start rendering the data after the fetch process ends, but I get an error for infinite loop instead.
The only way that my fetched data appears on render is while I use setTimeout() and wait for the data to be fetched, but this is only for testing...
import "bootstrap/dist/css/bootstrap.css";
import { useEffect } from "react";
import axios from "axios";
export default function App() {
//the array of loadedQuestions that is fetched from the API
let [loadedQuestions, setLoadedQuestions] = React.useState([]);
let [answers, setAnswers] = React.useState([]);
let [isFetching, setIsFetching] = React.useState(true);
async function fetchData() {
const response = await axios.get(
"https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple"
);
console.log(response);
const data = await response.data;
loadedQuestions = data.results;
setLoadedQuestions(loadedQuestions);
//adding the correct answer to incorrect_answers array
for (let obj of loadedQuestions)
obj.incorrect_answers.splice(
Math.floor(Math.random() * 4),
0,
obj.correct_answer
);
setIsFetching(false);
isFetching = false;
console.log(isFetching);
}
useEffect(() => {
fetchData();
}, []);
const [isClicked, setIsClicked] = React.useState(false);
const [currentQuestion, setCurrentQuestion] = React.useState(0);
const [score, setScore] = React.useState(0);
const [finished, setFinished] = React.useState(false);
const [beforeFinish, setBeforeFinish] = React.useState("");
//setTimeout only for testing....
// setTimeout(() => {
if (!isFetching) {
setAnswers(
loadedQuestions[currentQuestion].incorrect_answers.map((element) => (
<li>
<span
key={currentQuestion.toString()}
onClick={() => handleClick(element)}
style={{ cursor: "pointer" }}
className={isClicked ? textColor(element) : "bg"}
>
{element}
</span>
</li>
))
);
setBeforeFinish(
<div>
<h5 className="m-3" style={{ textDecoration: "underline" }}>
{loadedQuestions[currentQuestion].question}
</h5>
<ol style={{ listStyleType: "lower-latin" }}>{answers}</ol>
<button
onClick={() => nextQuestionFunction()}
style={{
backgroundColor: "#0c88fb",
color: "white",
marginLeft: 10,
}}
>
Next Question
</button>
<h5 style={{ marginTop: 15, marginLeft: 10 }}>
Your score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>!
</h5>
</div>
);
}
// }, 2000);
const afterFinish = (
<div>
<h1>Finished!</h1>
<h5>
Your Score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>
</h5>
<button
onClick={() => tryAgain()}
style={{ backgroundColor: "#0c88fb", color: "white", marginLeft: 2 }}
>
Try Again
</button>
</div>
);
function handleClick(element) {
setIsClicked(true);
textColor(element);
if (element === loadedQuestions[currentQuestion].correct_answer) {
setScore(score + 100 / loadedQuestions.length);
}
}
function textColor(element) {
let classN = "bg ";
element === loadedQuestions[currentQuestion].correct_answer
? (classN += "bg-info")
: (classN += "bg-secondary");
return classN;
}
function nextQuestionFunction() {
if (currentQuestion + 1 === loadedQuestions.length) {
setFinished(true);
} else {
setCurrentQuestion(currentQuestion + 1);
setIsClicked(false);
}
}
function textDisplay() {
if (finished) {
return afterFinish;
} else {
return beforeFinish;
}
}
function tryAgain() {
setCurrentQuestion(0);
setScore(0);
setIsClicked(false);
setFinished(false);
}
return textDisplay();
}
if (!isFetching) {
setAnswers(
loadedQuestions[currentQuestion].incorrect_answers.map((element) => (
// etc
))
);
setBeforeFinish(
<div>
// etc
</div>
);
}
You are calling setAnswers and setBeforeFinish in the middle of rendering. Setting state causes the component to rerender and when it renders you set state again, which renders again, which sets state again, etc.
These don't look like they should even be states at all. loadedQuestions and isFetching are the true state, and then answers and beforeFinish are just values calculated from that state. I recommend you delete the answers and beforeFinish states, and where you used to have the if (!isFetching) code you do:
let answers = null;
let beforeFinish = null;
if (!isFetching) {
answers = loadedQuestions[currentQuestion].incorrect_answers.map(
(element) => (
<li>
<span
key={currentQuestion.toString()}
onClick={() => handleClick(element)}
style={{ cursor: "pointer" }}
className={isClicked ? textColor(element) : "bg"}
>
{element}
</span>
</li>
)
);
beforeFinish = (
<div>
<h5 className="m-3" style={{ textDecoration: "underline" }}>
{loadedQuestions[currentQuestion].question}
</h5>
<ol style={{ listStyleType: "lower-latin" }}>{answers}</ol>
<button
onClick={() => nextQuestionFunction()}
style={{
backgroundColor: "#0c88fb",
color: "white",
marginLeft: 10,
}}
>
Next Question
</button>
<h5 style={{ marginTop: 15, marginLeft: 10 }}>
Your score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>!
</h5>
</div>
);
}

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.

Form resets stored components upon submission

I want to make an editable piece of display text, such that:
The text itself is not a textarea/input, it is just standard text
Clicking on the text will open a text area and edit button, which allows you to edit the text
Clicking the edit button will update the display text, and hide the textarea/edit button (display:none)
I have implemented every part of this, and now have a bug where any time I hit the 'edit' button, all of the text components are erased from the page entirely.
To achieve this, I have a Page component which stores a series of EditableText components (you can add more EditableText components via a + button at the bottom of the page):
const Page = (props) => {
const classes = useStyles()
var [components, setComponents] = useState([])
var [displayButton, setDisplayButton] = useState(false)
const toggleButton = () => {
displayButton = !displayButton
setDisplayButton(displayButton)
}
const addCaution = () => {
setComponents(components.concat(<Caution />))
displayButton = !displayButton
setDisplayButton(displayButton)
}
const addImportant = () => {
setComponents(components.concat(<Important />))
displayButton = !displayButton
setDisplayButton(displayButton)
}
const opbutton = (
<div>
<Button onClick={addHeader}>header</Button>
<Button onClick={addText}>text</Button>
<Button onClick={addCaution}>caution</Button>
<Button onClick={addImportant}>important</Button>
</div>
)
return (
<div className={classes.page}>
{components}
{displayButton ? opbutton : null}
<Button onClick={toggleButton}>+</Button>
</div>
)
The EditableText components are added into an array via useState.
Here is the code for the EditableText:
const EditableText = (props) => {
const classes = useStyles()
const { inputs, handleInputChange, handleSubmit } = useSubmit()
var [displayText, setDisplayText] = useState("click here to add notes!")
var [showBox, setShowBox] = useState(false)
var [showClickArea, setClickArea] = useState(true)
const inputBox = (
<form
onSubmit={handleSubmit}
style={{ display: "flex", flexDirection: "row", width: "100%" }}
>
<textarea
type="text"
name="displayText"
className={classes.textbox}
onChange={handleInputChange}
value={inputs}
style={{ borderColor: props.border }}
onSubmit={"return inputs"}
>
{inputs}
</textarea>
<Button id="editbutton" type="submit" className={classes.button}>
edit
</Button>
</form>
)
const toggleEdit = () => {
showClickArea = !showClickArea
setClickArea(showClickArea)
showBox = !showBox
setShowBox(showBox)
}
const clickArea = (
<div
style={{ width: "80%", height: "200%", position: "absolute" }}
onClick={toggleEdit}
/>
)
return (
<div>
{showClickArea ? clickArea : null}
<div className={classes.background} style={{ color: props.color }}>
{inputs}
<div>{showBox ? inputBox : displayText}</div>
</div>
</div>
)
}
You may notice the use of useSubmit which is a custom hook:
const useSubmit = (callback) => {
const [input, setInput] = useState({})
const handleSubmit = (event) => {
callback()
}
const handleInputChange = (event) => {
event.persist()
setInput((input) => ({
...input,
[event.target.name]: [event.target.value],
}))
}
return (handleSubmit, handleInputChange, input)
}
What I figure is that this may be an issue with the custom hook, or the use of form and submit. I think form is supposed to clear the page once submitted. But I'm not entirely sure. Any idea how to prevent this?
Solved by creating a function and an 'edit' variable:
function setDisplayState() {
if (edit === false) {
return (
<div
style={{
color: props.color,
fontWeight: props.weight ? props.weight : 400,
fontSize: props.size ? props.size : 14,
}}
>
{displayText}
</div>
)
} else {
return (
<div>
<textarea
className={classes.textbox}
style={{ color: props.color }}
value={displayText}
onChange={(e) => {
displayText = e.target.value
setDisplayText(displayText)
}}
/>
<Button className={classes.button} onClick={saveChange}>
save
</Button>
</div>
)
}
}

TypeError: Cannot read property 'data' of undefined for shopping cart functionality

I keep getting this error: TypeError: Cannot read property 'data' of undefined, when there is no data being passed to my shopping cart page. How can I fix this error? Ideally, I would just like the page to display: "This cart is empty". I tried adding a conditional statement above the UserCardBlock, but it did not change anything. Thank you
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import {
removeCartItem,
onSuccessBuy
} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
import { Result, Empty, Button } from 'antd';
import Paypal from '../../utils/Paypal';
function CartPage(props) {
const dispatch = useDispatch();
console.log(props)
const [Total, setTotal] = useState(props.location.state.data.price)
const [ShowTotal, setShowTotal] = useState(true)
const [ShowSuccess, setShowSuccess] = useState(false)
const removeFromCart = (productId) => {
dispatch(removeCartItem(productId))
}
const transactionSuccess = (data) => {
dispatch(onSuccessBuy({
cartDetail: props.user.cartDetail,
paymentData: data
}))
.then(response => {
setShowSuccess(true)
setShowTotal(false)
}
)
}
const transactionError = () => {
console.log('Paypal error')
}
const transactionCanceled = () => {
console.log('Transaction canceled')
}
const propductList = (data) =>{
console.log(data)
setTotal(data)
}
return (
<div style={{ width: '85%', margin: '3rem auto' }}>
<h1>My Cart</h1>
<div>
<UserCardBlock
productData={props.location.state.data}
removeItem={removeFromCart}
productList={data => propductList(data)}
/>
{ShowTotal ? (
<div style={{ marginTop: "3rem" }}>
<h2>Total amount: ${Total * 15} </h2>
</div>
) : ShowSuccess ? (
<Result status="success" title="Successfully Purchased Items" />
) : (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<br />
<Empty description={false} />
<p>No Items In The Cart</p>
</div>
)}
</div>
{/* Paypal Button */}
{ShowTotal &&
<Paypal
toPay={Total}
onSuccess={transactionSuccess}
transactionError={transactionError}
transactionCanceled={transactionCanceled}
/>
}
</div>
)
}
export default CartPage
Seems like your component is dependent on location state.
const [Total, setTotal] = useState(props.location.state.data.price)
and
<UserCardBlock
productData={props.location.state.data}
Try using optional chaining and nullish coalescing
const [Total, setTotal] = useState(props.location?.state?.data?.price ?? 0)
<UserCardBlock
productData={props.location.state?.data ?? []}
It seems like you are using redux so i will suggest you to use redux store instead of location state.

Categories

Resources