Reduce onIncrement() and onDecrement() to 1 function Reactjs? - reactjs

I am coding a counters app. So I want to replace onIncrement, onDecrement function by onChange function.
I want to remove onIncrement and onDecrement function. Then I want to add new onChange function
The onChange function should take a single parameter and it can decrement or increment a counter.
my code:
const Counter = props => {
const { onIncrement, onDecrement, count, index } = props;
return (
<div className="counter">
<b>{count}</b>
<div className="counter-controls">
<button
onClick={() => onDecrement(index)}
className="button is-danger is-small"
>
-
</button>
<button
onClick={() => onIncrement(index)}
className="button is-success is-small"
>
+
</button>
</div>
</div>
);
};
const Total = props => {
const { sum } = props;
return (
<div>
<p>Total: {sum} </p>
</div>
);
};
export default function App() {
const [data, setData] = useState([
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 }
]);
const total = data.map(item => item.value).reduce((p, n) => p + n);
const [sum, setSum] = useState(total);
const handleIncrement = index => {
const clone = [...data];
clone[index - 1].value += 1;
setData(clone);
setSum(sum + 1);
};
const handleDecrement = index => {
const clone = [...data];
clone[index - 1].value -= 1;
setData(clone);
setSum(sum - 1);
};
return (
<div className="App">
{data.map(counter => (
<Counter
key={counter.id}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
index={counter.id}
count={counter.value}
/>
))}
<Total sum={sum} />
</div>
);
}

You can write a single onChange function and pass on the change value while calling the function. Use the changeValue to add to the previous data. For a decrement implementation change value will be -1 and for increment it will be +1
const Counter = props => {
const { onChange, count, index } = props;
return (
<div className="counter">
<b>{count}</b>
<div className="counter-controls">
<button
onClick={() => onChange(index, -1)}
className="button is-danger is-small"
>
-
</button>
<button
onClick={() => onChange(index, 1)}
className="button is-success is-small"
>
+
</button>
</div>
</div>
);
};
const Total = props => {
const { sum } = props;
return (
<div>
<p>Total: {sum} </p>
</div>
);
};
export default function App() {
const [data, setData] = useState([
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 }
]);
const total = data.map(item => item.value).reduce((p, n) => p + n);
const [sum, setSum] = useState(total);
const handleChange = (index, changeValue) => {
const clone = [...data];
clone[index - 1].value += changeValue;
setData(clone);
setSum(prevSum = prevSum + changeValue);
};
return (
<div className="App">
{data.map(counter => (
<Counter
key={counter.id}
onChange={handleChange}
index={counter.id}
count={counter.value}
/>
))}
<Total sum={sum} />
</div>
);
}

Related

How to get the values of multiple input

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

React pagination with dots

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

React - reduce strange interaction on input setState

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

How to passing functions to components?

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

React js add filter

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

Resources