I have a list of products with different values.
const products = [{id: 2, value: 'A'}, {id:3, value: '3'}] // sample input
const RenderProduct = products.map((product) => {
return(
<div key={product.id}>
<MinusIcon onClick={SHOULD_DEDUCT_THE_QUANTITY}/>
<input type="text" value={SHOULD_DISPLAY_THE_QUANTITY_HERE} />
<AddIcon onClick={SHOULD_ADD_THE_QUANTITY}/>
</div>
)
});
return <RenderProduct />
How can I retrieve the current quantity of each product and display it on inputbox?
You can create a state using useState and based on the operation and id you can get the updated value
CODESANDBOX LINK
function Button({ onClick, operation }: any) {
return <button onClick={onClick}> {operation} </button>;
}
export default function App() {
const [products, setProducts] = useState([
{ id: 2, value: 2 },
{ id: 3, value: 3 }
]);
function handleChange(id: number, operation: "minus" | "add") {
setProducts((p) => {
return p.map((product) => {
if (product.id !== id) return product;
return {
...product,
value: operation === "minus" ? product.value - 1 : product.value + 1
};
});
});
}
return (
<>
{products.map((product) => {
return (
<div key={product.id}>
<Button
operation="-"
onClick={() => handleChange(product.id, "minus")}
/>
<input type="text" value={product.value} />
<Button
operation="+"
onClick={() => handleChange(product.id, "add")}
/>
</div>
);
})}
</>
);
}
I create a react pagination component. Typical pagination logic, nothing especial, but I can not add dots logic, for example like this: < 1 2 ... 20 > when I could add the dots after certain page number and before last one. Also this dots element necessary to move while clicking forward like this: < 1 ... 5 6 ... 20 >
How to do it by right way?
export const Pagination = ({ newsList }) => {
const pageNumberLimit = 3;
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [maxPageLimit, setMaxPageLimit] = useState(4);
const [minPageLimit, setMinPageLimit] = useState(0);
const [postsPerPage] = useState(3);
const totalPosts = posts.length;
const lastPostIndex = currentPage * postsPerPage;
const firstPostIndex = lastPostIndex - postsPerPage;
const currentPost = posts.slice(firstPostIndex, lastPostIndex);
const paginate = (pageNumber) => setCurrentPage(pageNumber);
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
const prevPage = () => {
setCurrentPage((prev) => prev - 1);
};
const nextPage = () => {
setCurrentPage((prev) => prev + 1);
};
useEffect(() => {
const getPosts = () => {
setPosts(newsList);
};
getPosts();
}, []);
return (
<div className="paginationPerPage">
{currentPost.map() => (
<div className="card"/>
))}
<ul className="ctyledPageNumbers">
<button className="styledPageNumbersButton" onClick={prevPage} type="button" disabled={currentPage === pageNumbers[0]}>
‹ Anterior
</button>
{pageNumbers.map((number) => {
if (number >= maxPageLimit && number < pageNumbers.length) {
return (
<button onClick={nextPage} type="button">
…
</button>
);
}
return (
<li key={number} className={currentPage === number ? 'active' : null}>
<button type="button" onClick={() => paginate(number)}>
{number}
</button>
</li>
);
})}
<button className="styledPageNumbersButton"
onClick={nextPage}
type="button"
disabled={currentPage === pageNumbers[pageNumbers.length - 1]}
>
Seguinte ›
</button>
</ul>
</div>
);
};
I have a quiz and it has an arr of questions, a question has value.
I want to assign the total of questions question.value to the quiz.value as the value updates.
Here is how i am currently handling the updating (obviously i havent dealt with if a question is removed yet)
const handleUpdateQuestionScore = (e) => {
setQuiz({...quiz, questions: quiz.questions.map((child, index) => index === selectedQuestion ? { ...child, value: parseInt(e.target.value) } : child)})
setReUpdateTotal(true);
}
useEffect(() => { // this has weird behaviour
console.log("my total", quiz.questions.reduce((total, question) => total = total + question.value, 0))
if(quiz.questions.length < 1) {
setQuiz({ ...quiz, value: 0 } )
}
else{
setQuiz({...quiz, value: quiz.questions.reduce((total, question) => total = total + question.value, 0) } )
}
},[reUpdateTotal])
The issue with this is, when i set question 1, its correct.
When i add question 2 and set its value, sometimes it only interpolates the first number.
going back off the form and loading it up will update.
Other times it will not update the value at all.
For a much more indept demonstration, this codeSandbox demonstrates functionality:
https://codesandbox.io/s/stupefied-shadow-i9z2wx?file=/src/App.js
Here is the default code in the codeSandbox
import { useEffect, useState } from "react";
export default function App() {
const [quiz, setQuiz] = useState({
title: "",
number: 6,
questions: [
{
question: "q1",
value: 0
},
{
question: "q2",
value: 0
}
]
});
const [selectedQuestion, setSelectedQuestion] = useState(undefined);
useEffect(() => {
console.log("quiz", quiz);
}, [quiz]);
const [reUpdateTotal, setReUpdateTotal] = useState();
const handleUpdateQuestionScore = (e) => {
setQuiz({
...quiz,
questions: quiz.questions.map((child, index) =>
index === selectedQuestion
? { ...child, value: parseInt(e.target.value) }
: child
)
});
setReUpdateTotal(true);
};
useEffect(() => {
// this has weird behaviour
console.log(
"my total",
quiz.questions.reduce(
(total, question) => (total = total + question.value),
0
)
);
if (quiz.questions.length < 1) {
setQuiz({ ...quiz, value: 0 });
} else {
setQuiz({
...quiz,
value: quiz.questions.reduce(
(total, question) => (total = total + question.value),
0
)
});
}
}, [reUpdateTotal]);
return (
<div className="App">
<div>
{quiz.questions.map((question, index) => (
<div key={index}>
<p>
{question.question} - {question.value}
<button onClick={() => setSelectedQuestion(index)}>select</button>
</p>
<br />
</div>
))}
</div>
<br />
<small>change value for question #{selectedQuestion}</small>
{selectedQuestion !== undefined ? (
<div>
<input
type="number"
id="Value"
name="Value"
required="required"
className="edit-input shadow"
min="0"
value={quiz.questions[selectedQuestion].value}
onChange={(e) => {
handleUpdateQuestionScore(e);
}}
/>
<button onClick={() => setSelectedQuestion(undefined)}>remove</button>
</div>
) : (
<div>nothing selected</div>
)}
<h1>current total score = {quiz.value}</h1>
</div>
);
}
Technically - your useEffect that has a [reUpdateTotal] as a dependency array executes only once. When switching value from false to true. Im not seeing where you are switching it back. But still, you dont even need this variable and rely on the useEffect with [quiz] which will execute only 1 function - setQuiz. And no, it will not cause infinite rerenderings, if used correctly. It can accept a function with 1 parameter, which will be your "current" value of a quiz. I used prev but actually it is curr. And this function can return either a new value and fire a rerender, either the current value and do nothing in this case.
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [quiz, setQuiz] = useState({
title: "",
number: 6,
questions: [
{ question: "q1", value: 0 },
{ question: "q2", value: 0 }
]
});
const [selectedQuestionIndex, setSelectedQuestionIndex] = useState(undefined);
const handleUpdateQuestionScore = (e) => {
setQuiz((prev) => {
prev.questions[selectedQuestionIndex].value = parseInt(
e.target.value,
10
);
return { ...prev };
});
};
useEffect(() => {
setQuiz((prev) => {
const currValue = prev.value;
const newValue =
prev.questions?.reduce(
(total, question) => (total = total + question.value),
0
) || 0;
if (newValue === currValue) return prev;
return {
...prev,
value: newValue
};
});
}, [quiz]);
return (
<div className="App">
<div>
{quiz.questions?.map((question, index) => (
<div key={index}>
<p>
{question.question} - {question.value}
<button onClick={() => setSelectedQuestionIndex(index)}>
select
</button>
</p>
<br />
</div>
))}
</div>
<br />
<small>change value for question #{selectedQuestionIndex}</small>
{selectedQuestionIndex !== undefined ? (
<div>
<input
type="number"
id="Value"
name="Value"
required="required"
className="edit-input shadow"
min="0"
value={quiz.questions[selectedQuestionIndex].value}
onChange={handleUpdateQuestionScore}
/>
<button onClick={() => setSelectedQuestionIndex(undefined)}>
remove
</button>
</div>
) : (
<div>nothing selected</div>
)}
<h1>current total score = {quiz.value}</h1>
</div>
);
}
Here is a bit more clear way of handling "quiz.value" using useMemo:
import "./styles.css";
import { useMemo, useState } from "react";
export default function App() {
const [quiz, setQuiz] = useState({
title: "",
number: 6,
questions: [
{ question: "q1", value: 0 },
{ question: "q2", value: 0 }
]
});
const [selectedQuestionIndex, setSelectedQuestionIndex] = useState(undefined);
const selectedQuestion = useMemo(() => {
if (!quiz?.questions || selectedQuestionIndex === undefined)
return undefined;
return quiz.questions[selectedQuestionIndex];
}, [quiz, selectedQuestionIndex]);
const quizTotalValue = useMemo(() => {
if (!quiz || !quiz.questions) return 0;
return quiz.questions.reduce((acc, curr) => acc + curr.value, 0);
}, [quiz]);
const handleUpdateQuestionScore = (e) => {
if (!selectedQuestion) return;
selectedQuestion.value = parseInt(e.target.value, 10);
setQuiz((prev) => ({ ...prev }));
};
return (
<div className="App">
<div>
{quiz.questions?.map((question, index) => (
<div key={index}>
<p>
{question.question} - {question.value}
<button onClick={() => setSelectedQuestionIndex(index)}>
select
</button>
</p>
<br />
</div>
))}
</div>
<br />
<small>change value for question #{selectedQuestionIndex}</small>
{selectedQuestion !== undefined ? (
<div>
<input
type="number"
id="Value"
name="Value"
required="required"
className="edit-input shadow"
min="0"
value={selectedQuestion.value}
onChange={handleUpdateQuestionScore}
/>
<button onClick={() => setSelectedQuestionIndex(undefined)}>
remove
</button>
</div>
) : (
<div>nothing selected</div>
)}
<h1>current total score = {quizTotalValue}</h1>
</div>
);
}
I am doing the implementation of list pagination through a custom hook. The handleSetCurrentPage() function gets the correct number, it uses setCurrentPage(number). Consolelog setCurrentPage(number) showed undefined.
if you do all the same code only within one file (put everything in ListOfItems) it works fine.
Hook:
export const usePagination = (users = [], defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [currentUsers, setCurrentUsers] = useState([]);
const [amountOfPages, setAmountOfPages] = useState(0);
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, []);
const updateUsers = () => {
const indexOfLastPost = currentPage * amountPerPage;
const indexOfFirstPost = indexOfLastPost - amountPerPage;
const updatedUsers = users.slice(indexOfFirstPost, indexOfLastPost);
setCurrentUsers(updatedUsers);
};
const updateAmountOfPages = () => {
const updatedAmount = Math.ceil(users.length / amountPerPage);
setAmountOfPages(updatedAmount);
};
return {
setCurrentPage,
amountOfPages,
currentUsers,
};
};
list of items:
export function ListOfItems() {
const users = useSelector(state => state);
const { setCurrentPage, currentUsers, amountOfPages } = usePagination(users);
let {url} = useRouteMatch();
let items = currentUsers.map(function (value, index) {
return (
<form key={index}>
<div className="input-group">
<div className="input-group-prepend">
<Link className="input-group-text" to={`${url}/${index}`}>
{value.name}, {index}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
{/*<form className="card">*/}
{/* <Search setSearch={setSearch} />*/}
{/*</form>*/}
<div>{items}</div>
<div>
<Pagination amountOfPages={amountOfPages} setCurrentPage={setCurrentPage}/>
</div>
</div>
)
}
pagination component:
const Pagination = ({amountOfPages, setCurrentPage}) => {
const [pageNumbers, setPageNumbers] = useState([]);
useEffect(() => {
calculatePageNumbers();
}, [amountOfPages]);
function calculatePageNumbers() {
const updatedPageNumbers = [];
for (let i = 1; i <= amountOfPages; i++) {
updatedPageNumbers.push(i);
}
setPageNumbers(updatedPageNumbers);
}
function handleSetCurrentPage(number) {
console.log(number);
return console.log(setCurrentPage(number));
}
return (
<nav>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<button
onClick={() => handleSetCurrentPage(number)}
type="button"
className="page-link"
>
{number}
</button>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, [currentPage]);
I want to add a filter in arrays of filters, but all filters are added in the same array in filters. There are some blocks for filters and every filter must be added in its array. Now, every filter is added in its array, but all other filters are updated in that array too.
export const DropDownBlock = () => {
const [filters, setFilters] = useState({
type: [],
license: [],
tag: [],
format: [],
});
const filterKey = Object.keys(item.filters);
const [checked, setChecked] = useState([]);
return (
<section className="filterSection">
{filterKey.map((f, index) => {
const filterArray = [];
const photoItems = photos.map((p) => {
return p.filters[filterKey[index]];
});
photoItems.map((p) => {
if (filterArray.indexOf(p) < 0) {
filterArray.push(p);
}
});
const handleFilters = (filters, category) => {
const newFilters = { ...filters };
newFilters[category] = filters;
setFilters(newFilters);
};
return (
<div className="" key={f}>
<div
className="dropDownTitleBlock"
onClick={() => (isOpen ? setIsOpen(false) : setIsOpen(true))}
>
{isOpen ? <MdKeyboardArrowDown /> : <MdKeyboardArrowRight />}
<h5 className="dropDownTitle">{f}</h5>
</div>
{isOpen && (
<div className="dropDownCategoryBlock">
{filterArray.map((filter) => {
switch (f) {
case filterKey[index]:
return (
<Checkbox
filter={filter}
handleFilters={(filters) =>
handleFilters(filters, filterKey[index])
}
checked={checked}
setChecked={setChecked}
/>
);
}
})}
</div>
)}
</div>
);
})}
</section>
);
};