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
Related
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>
);
}
I am trying to add a functionality wherein when a user clicks on add channel button he can add the channel and it will show the newly added channel name in the respective teams. Similarly, when a user wants to delete a channel he can delete it so by clicking on delete button.
However, I figured out most of the code but seems like when am updating my newchannel list using useState it is showing me the error saying teams.channels.map is undefined and can not read properties.
If anyone can help on this would really be helpful and appreciated.
Please find the source code link below
Codesandbox link
You should create a InputAddChannel component
const InputAddChannel = ({ handleAddChannel }) => {
const [inputChannelValue, setInputChannelValue] = useState("");
const handlInputChange = (event) => {
event.preventDefault();
const newChannelName = event.target.value;
setInputChannelValue(newChannelName);
};
return (
<>
<input
placeholder="add channel"
value={inputChannelValue}
onChange={handlInputChange}
/>
<button
disabled={!inputChannelValue}
onClick={() => handleAddChannel(inputChannelValue)}
>
Add channel
</button>
</>
);
};
and pass handleAddChannel function to it
const App = () => {
const [newchannel, setNewChannel] = useState(teams);
const [addteam, setAddteam] = useState("");
const handleAddChannel = (team, i) => {
const newData = newchannel.map((channel, index) => {
if (i === index)
return {
...channel,
channels: [
...channel.channels,
{
channelName: team,
channelid:
channel.channels[channel.channels.length - 1]?.channelid + 1 ||
1
}
]
};
return channel;
});
setNewChannel(newData);
};
const handleDeleteChannel = (cid, teamid) => {
const newData = newchannel.map((channel, index) => {
if (index === teamid)
return {
...channel,
channels: channel.channels.filter((c, i) => i !== cid)
};
return channel;
});
setNewChannel(newData);
};
const handleAddteam = (event) => {
event.preventDefault();
const addteaminput = event.target.value;
setAddteam(addteaminput);
};
const handlSubmitAddteam = () => {
const newaddedteam = newchannel.concat({
name: addteam,
channels: [
{
channelid: newchannel.length + 1,
channelName: "new"
}
]
});
setNewChannel(newaddedteam);
};
return (
<>
<div className="App">
{newchannel &&
newchannel.map((team, i) => (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
alignContent: "center",
justifyContent: "center",
border: "1px solid black",
padding: "0.5rem"
}}
key={i}
>
<h1> {team.name} </h1>
<InputAddChannel
handleAddChannel={(value) => handleAddChannel(value, i)}
/>
<div>
{team.channels.map((c, cid) => (
<div
style={{
display: "flex",
gap: "1rem",
alignItems: "center",
justifyContent: "center",
border: "1px solid lightgray"
}}
key={cid}
>
<h6> {c.channelName}</h6>
<button
style={{ width: "5rem", height: "2rem" }}
onClick={() => handleDeleteChannel(cid, i)}
>
Delete
</button>
</div>
))}
</div>
</div>
))}
<input
placeholder="add team"
value={addteam}
onChange={handleAddteam}
/>
<button disabled={!addteam} onClick={handlSubmitAddteam}>
Add team
</button>
</div>
</>
);
};
You can check in my codesandbox. Hope it help!
PROBLEM:
I am currently creating a react app that allows you to checkout a book to a professor. In this app it has a couple of things that need to obviously update when a user checks out a book.
So first off there is the number of totalBooks that is checked out, or just when the entire book object changes then the component should re-render.
I have a useEffect function that is making an api call to a mongodb database and is accessing a document that will yield a book object in the response to the react app. Here is that useEffect function:
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
setBook({...book.data.data})
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
I have even subbed out the book object dependency for something like book.checkedOutCopies. Which should return a number and if that number is different from the last then it should re-render the component. This is however, not the case. No matter what I try I am unable to re-render the component when this document changes. I even created a number called reRender and updated it when the api call to checkout a book finished its call. This would be undesired even if it worked because it would not change for someone who was already on the page, but not on the same computer as the person that clicked the checkout button.
I simply just want this component to re-render when the book object in the mongo db database has changed. Which from my understanding the right way to do it is above. The problem is that even after I successfully checkout a book the state never updates. The number of checked out books on the screen stays static:
Here is a screen shot of what the page looks like:
The green books should turn to red when the update button is clicked and a success status is responded. The Total Checked Out should also change. Neither of these happen.
Here is the book object:
const Book = new Schema(
{
bookName: { type: String, required: true },
bookDesc: { type: String, required: false},
numCheckedOut: { type: Number, required: false },
copiesAvail: {type: Number},
whosChecked: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'prof'
}],
copies: [Copy],
ISBN: { type: Number, required: true },
nextDue: {type: String},
nextProf: {type: Schema.Types.ObjectId, ref: 'prof'}
},
{ timestamps: true },
)
I don't understand why it isn't updating would appreciate any help here is the file in its entirety:
import React, { useState, useEffect, useRef } from 'react'
import api from '../api'
import { InputGroup, Form, FormControl, Container, Button, Col, Row, Toast } from 'react-bootstrap'
import { FaBook } from 'react-icons/fa'
import BookIconFunc from '../components/helperComponents/bookIconFunc'
import Select from 'react-dropdown-select';
import './bookslist.css'
import Axios from "axios";
import BookIcoContext from '../context/BookIconContext';
import DatePicker from "react-datepicker";
import ColoredLine from '../components/helperComponents/ColoredLine'
import CheckoutBookHistroy from '../components/CheckoutBookHistory/CheckoutBookHistory'
import { useHistory } from 'react-router-dom';
const handleCheckout = async (e) => {
e.preventDefault();
}
const topLeftBook = {
marginTop: "1.0rem",
display: "flex",
width:"fit-content",
height: "fit-content",
justifyContent: "left",
flexDirection: "column",
alignItems: "center",
borderRadius: "0px",
border: "2px solid #73AD21",
padding: "0.5rem 2.5rem",
}
const booksRows = {
// marginTop: "4.0rem",
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
padding: "1.0rem",
justifyContent: "left",
// border: "2px solid black",
width: "50%",
marginLeft: "1.0rem"
}
const bottomForm = {
flexGrow: "1"
}
const indBook = {
margin: "0.5rem",
color: "green"
}
const updateButtonStyle = {
display: "flex",
width: "100%",
justifyContent:"center"
}
const topOfPage = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
verticalAlign: "middle"
}
const bookIcon = {
width: "10.0rem",
}
const bottomOfPage = {
display: "flex",
flexDirection: "row",
marginBottom: "1.0rem",
verticalAlign: "middle",
flexGrow: "1",
marginLeft: "1.0rem"
}
const topForm = {
width: "100%",
marginLeft: "2.0rem",
verticalAlign: "middle",
alignItems: "center",
justifyContent: "center"
}
export default function BooksUpdate(props) {
/* TOP FORM VARIABLES */
const [book, setBook] = useState(null)
const [bookName, setBookName] = useState()
const [desc, setDesc] = useState()
const [ISBN, setISBN] = useState()
const [copies, setCopies] = useState()
const history = useHistory();
/* BOOK ICON CONTEXT STATE */
const [iconState, setIconState] = useState(false)
/* re render state when */
const [reRender, setReRender] = useState(0);
/* BOTTOM FORM VARIABLES */
const [newCopies, setNewCopies] = useState()
const [checkoutCopiesNum, setCheckoutCopiesNum] = useState(0)
const [numCheckedOut, setNumCheckedOut] = useState(0)
const [allProfs, setAllProfs] = useState()
const [profChosen, setProfChosen] = useState()
const [bookicoref, setIcoRefs] = useState([]);
const [dueDate, setDueDate] = useState(new Date());
var anotherRender = 0;
const submitCheckout = async (e) => {
e.preventDefault();
try {
const checkoutBookData = {book, checkoutCopiesNum, profChosen, dueDate};
const checkoutBookRes = await Axios.post("http://localhost:8174/api/book/checkout/" + props.id, checkoutBookData)
if(checkoutBookRes.statusText === 'OK'){
setShowCheckoutToast(!showCheckoutToast)
}
/* Display toast that confirms the book was checked out */
setReRender(reRender+1)
anotherRender = anotherRender + 1
// history.push("/Books/Update/" + props.id)
}
catch (err) {
alert(err)
}
}
const handleSetBook = (bookData) => {
setBook({...bookData})
}
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
await handleSetBook(book.data.data)
// setBook(book.data.data)
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
useEffect( () => {
const getProfs = async () => {
await Axios.get('http://localhost:8174/user/professors').then((ps) => {
var array = []
ps.data.data.map((prof, index) => {
array.push({label: prof.name, value: prof, key: prof._id})
})
setAllProfs(array)
})
}
getProfs()
}, [])
/* EFFECT TO CREATE REFS FOR EACH BOOK ICON */
useEffect(() => {
// add or remove refs
copies &&
setIcoRefs(bookicorefs => (
Array(copies.length).fill().map((_, i) => bookicorefs[i] || React.createRef())
))
}, [copies]);
const handleUpdate = () => {
console.log("handling update")
}
const findCheckedOut = (array) => {
var numChecked = 0;
array.filter(arr => {
if(arr.checkedOut){
numChecked = numChecked + 1
}
})
return numChecked
}
const [showCheckoutToast, setShowCheckoutToast] = useState(false)
const toggleCheckToast = () => {
setShowCheckoutToast(!showCheckoutToast)
}
/* EFFECT TO VALIDATE THE INFORMATION INSIDE THE CHECKOUT BOOKS FIELD */
useEffect(() => {
if(!copies){
return
}
if(checkoutCopiesNum > copies.length){
alert("There isn't that much of this book available")
return;
}
// console.log(numCheckedOut)
if(checkoutCopiesNum > (copies.length - numCheckedOut)){
setCheckoutCopiesNum(0)
alert('You cannot checkout that many copies as there is already to many checked out')
return;
}
// for(var i = 0; i < checkoutCopiesNum; i++){
// }
},[checkoutCopiesNum, numCheckedOut])
return (
book ?
copies ?
<div>
<Container>
{/* Show the book icon with the title of the book underneath */}
<div style={topOfPage}>
<div style={topLeftBook}>
<FaBook style={bookIcon} size={200}/>
<h4 className="">{book.bookName}</h4>
</div>
<Form style={topForm}>
<Row>
<Col className="pl-0">
<InputGroup className="mb-3 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Book Name</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setBookName(e.target.value)}
defaultValue={book.bookName}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Form.Group controlId="exampleForm.ControlTextarea1">
<Form.Control as="textarea" rows={5} defaultValue={book.bookDesc}/>
</Form.Group>
</Col>
<Col className="m-0 pr-0">
<InputGroup className="mb-4 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">ISBN</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setISBN(e.target.value)}
defaultValue={book.ISBN}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<InputGroup className="mb-4 mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Copies</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={copies.length}
/>
</InputGroup>
<InputGroup className="mb-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Checked Out</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={findCheckedOut(book.copies)}
/>
</InputGroup>
</Col>
<Button style={updateButtonStyle} onClick={handleUpdate}>Update</Button>
</Row>
</Form>
</div>
<Row style={{justifyContent: "space-between", verticalAlign: "middle"}}>
<Toast
show={showCheckoutToast}
onClose={toggleCheckToast}
style={{
position: 'absolute',
top: 0,
right: 0,
}}
>
<Toast.Header>
<img
src="holder.js/20x20?text=%20"
className="rounded mr-2"
alt=""
/>
<strong className="mr-auto">Success!</strong>
</Toast.Header>
<Toast.Body>Successfully Checked out a Book</Toast.Body>
</Toast>
<div style={bottomOfPage}>
<Form style={bottomForm} onSubmit={submitCheckout}>
<InputGroup className="mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon4">Checkout Out Copies:</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
onChange={(e) => setCheckoutCopiesNum(e.target.value)}
placeholder={checkoutCopiesNum}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Select
className="mt-4"
style={{width: "100%"}}
name="Select"
required
// loading
searchable
placeholder="To:"
options={allProfs}
onChange={(values) => {setProfChosen(values[0].value)}}
/>
<DatePicker className="mt-4" selected={dueDate} onChange={date => setDueDate(date)} />
<Button type="submit" className="mt-3 w-100">Checkout</Button>
</Form>
</div>
<BookIcoContext.Provider value={{iconState, setIconState}}>
<div style={booksRows} onClick={() => setIconState(true)} onMouseUp={() => setIconState(false)}>
{
copies ? copies.map((copy, index) => {
return <div
key={index}
>
<BookIconFunc
checkedOut={copy.checkedOut}
ref={bookicoref[index]}
>
</BookIconFunc>
</div>
})
:
<div>none</div>
}
</div>
</BookIcoContext.Provider>
</Row>
</Container>
<ColoredLine color="grey" m={20} height={1}/>
<Container>
{/* {book.whosChecked.map(prof => {
// console.log(prof)
// <Col>{prof}</Col>
})} */}
<CheckoutBookHistroy book_id={props.id} book={book} reRender={reRender}></CheckoutBookHistroy>
</Container>
</div>
:
<div>no data</div>
:
<div>no data</div>
)
}
I am creating a game which user can tap a button as much as they can in a range of time.
I have use react-countdown to create Timer. But when I tap to a button, timer is auto reset.
Here my code.
import React, { useState } from "react"
import Countdown from 'react-countdown';
function App() {
const [name, setName] = useState("")
const [isLogin, setIsLogin] = useState(false)
const [counter, setCounter] = useState(0)
const submitName = () => {
setIsLogin(true)
}
const updateCounter = () => {
console.log(counter)
setCounter(counter + 1)
}
return (
<div>
{!isLogin ? (
<div>
<input
style={{ width: 300, height: 30 }}
placeholder="Input name here"
value={name}
onChange={e => setName(e.target.value)}
/>
<button
style={{ width: 50, height: 20, background: "red" }}
onClick={() => submitName()}
>Go</button>
</div>
) : (
<div
style={{ width: "100vw", height: "100vh", background: "yellow" }}>
<Countdown date={Date.now() + 100000} />
<h1>{name}</h1>
<h3>Click counter: {counter} </h3>
<button
style={{
width: 100,
height: 50,
background: "red",
border: "none",
borderRadius: 25,
color: "white",
cursor: "pointer"
}}
onClick={() => updateCounter()}>Click here!</button>
</div>
)
}
</div>
);
}
export default App;
The problem is when I tap the button, setCounter(counter + 1) is running and rerender the page. That made the Counter reset.
I know why it have issue but I cannot fix it.
You need to memoize the Countdown component:
const CountdownWrapper = () => <Countdown date={Date.now() + 100000} />;
const MemoCountdown = React.memo(CountdownWrapper);
// usage
<MemoCountdown/>
Beforehand, on every render Date.now() gets a new value which resets the timer.
import React from 'react';
const time = React.useMemo(() => {
return Date.now() + 120 * 1000;
}, []);
I am able to delete the last view/item from viewPager on android and it would switch the view to the previous screen. But the same thing doesn't work in iOS. On iOS it would delete the last view but wouldn't switch and shows a blank page, but when you scroll old view renders again.
ViewPages.js
import React, { useState, useEffect } from "react";
import { Image, ScrollView } from "react-native";
import {
Container,
View,
Button,
Text,
Left,
Right,
Content,
Icon,
Header,
Fab
} from "native-base";
import { useTemplateState } from "../../../contexts/TemplateContext";
import {
useScreenTypeState,
useScreenTypeUpdate
} from "../../../contexts/ScreenTypeContext";
import CardView from "./CardView";
import ViewPager from "#react-native-community/viewpager";
export const ScreenTypeSelection = ({ route }) => {
let { screenCount } = useTemplateState();
let dispatch = useScreenTypeUpdate();
let value = useScreenTypeState();
const [active, setActive] = useState(false);
useEffect(() => {
dispatch({ type: "screen", payload: { screenCount } });
}, []);
const [currentIndex, setCurrentIndex] = useState(0);
const [currentPage, setCurrentPage] = useState(0);
return (
<ScrollView contentContainerStyle={{ flex: 1 }}>
<ViewPager
style={{ flex: 1 }}
initialPage={currentPage}
onPageSelected={e => setCurrentIndex(e.nativeEvent.position)}
showPageIndicator={true}
>
{value.length > 0 &&
value.map((card, index) => {
return (
<View style={{ alignItems: "center" }} key={index}>
<CardView key={card.pageNumber} card={card} />
</View>
);
})}
</ViewPager>
<Fab
active={active}
direction="up"
containerStyle={{}}
style={{ backgroundColor: "#5067FF" }}
position="bottomRight"
onPress={() => setActive(!active)}
>
<Icon name="create" />
<Button
style={{ backgroundColor: "#3B5998" }}
onPress={() => {
dispatch({
type: "delete_page",
payload: { currentIndex }
});
setCurrentPage(currentIndex - 1 < 0 ? 0 : currentIndex - 1);
}}
>
<Icon name="remove" />
</Button>
<Button
style={{ backgroundColor: "#34A34F" }}
onPress={() => {
dispatch({
type: "add_page",
payload: { currentIndex }
});
setCurrentPage(currentIndex + 1 )
}}
>
<Icon name="add" />
</Button>
</Fab>
</ScrollView>
);
};
package: #react-native-community/react-native-viewpager
Github: https://github.com/react-native-community/react-native-viewpager
After doing some research found that it is an open issue on GitHub
Is there any solution to it or any workaround that helps to reflect the same functionality on iOS as well?
I know there are other third-party packages available as well, but I would like to see if I can do it using react-native core modules.
If there is any more detail needed, let me know.