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>
);
})}
</>
);
}
Related
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 have array of items and searching items function, that returns another array. When I delete or edit item finded items changes don't render, but when search string has another value React render changes.
I know that useEffect can resolve this problem, but dont what to put in callback.
How can resolve this problem?
export const ToDoList = (props: PropsType) => {
const [searchQuery, setSearchQuery] = useState('')
const searchedItems = useMemo(() => {
return props.ToDoData.filter(item => item.text.includes(searchQuery))
},
[searchQuery])
return (
{props.ToDoData.length ?
<>
<input
...
onChange={e => setSearchQuery(e.target.value)}
/>
<ItemsList
...
items={
searchQuery ?
searchedItems :
props.ToDoData
}
/>
</> :
...
}
)
}
export const ItemsList = (props: PropsType) => {
const [editedText, setEditedText] = useState('')
const onDeleteItem = (id: number) => {
props.dispatch(deleteItem(id))
},
onEditItemMode = (id: number, text: string) => {
props.dispatch(setEditMode(true, id))
setEditedText(text)
},
onEditText = (id: number) => {
props.dispatch(setEditedTextInItem(id, editedText))
props.dispatch(setEditMode(false, id))
setEditedText('')
},
onToggleCompletedStatus = (id: number, status: string) => {
...
}
return (
{props.items.length ?
props.items.map((object) => (
<div
className="Item"
key={object.id}
>
{props.inEditMode.some((id: number) => id === object.id) ?
<>
<input
value={editedText}
onChange={e => { setEditedText(e.currentTarget.value) }}
/>
<button onClick={() => onEditText(object.id)}>
Change text
</button>
</> :
<>
<div className="Item__textBlock">
<input
type='checkbox'
onClick={() => { onToggleCompletedStatus(object.id, object.status)}}
/>
<span className={
object.status === 'completed' ?
'completed' :
'in process'
}>
{object.text}
</span>
</div>
<div className="Item__buttonBlock">
<button
className="Item__button"
disabled={props.inEditMode.length !== 0}
onClick={() => onEditItemMode(object.id, object.text)}
>
<img src={editImg} />
</button>
<button
className="Item__button"
onClick={() => { onDeleteItem(object.id) }}
>
<img src={removeImg} />
</button>
</div>
</>
}
</div>
)) :
...
}
)
}
// This code creates a list that is ONLY updated when searchQuery is updated
const searchedItems = useMemo(() => {
return props.ToDoData.filter(item => item.text.includes(searchQuery))
}, [searchQuery]);
// This code creates the list every time the component renders,
// so it will always be correct
const searchedItems = props.ToDoData.filter(item => item.text.includes(searchQuery))
// If you absolutely need to optimize the render of this component
// This code will update the list whenever the reference for ToDoData is updated as well
const searchedItems = useMemo(() => {
return props.ToDoData.filter(item => item.text.includes(searchQuery))
}, [searchQuery, props.ToDoData]);
I would like to get the text entered in the below input textarea created dynamically after the selection of persons from the dropdown boxes. Would like to get output into a json format:
Now it is getting value from the last displayed text area. Could someone please advise ?
Provide sample codesandbox link below
Expected output:
[
{name:"Bader", reason:"Good news, please add the some data"},
{name:"Crots", reason:"Great person"},
{name:"Dan", reason:"Simple look"}
]
App.js
import React, { useRef, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Link, useHistory } from "react-router-dom";
import Multiselect from "multiselect-react-dropdown";
const options = [
{ key: "Aaron", id: 1 },
{ key: "Bader", id: 2 },
{ key: "Crots", id: 3 },
{ key: "Dan", id: 4 },
{ key: "Elep", id: 5 },
{ key: "Pal", id: 6 },
{ key: "Quilt", id: 7 }
];
const App = () => {
const maxOptions = 3;
const [selectedOption, setSelectedOption] = useState([]);
const [nomRegister, setNomRegister] = useState([]);
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm();
const handleTypeSelect = (e) => {
const copy = [...selectedOption];
copy.push(e);
setSelectedOption(copy);
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
};
const sendNomination = () => {
console.log("Doesn't print all, what the heck: " + nomRegister);
};
return (
<div className="App">
<h1>Person selection</h1>
<div className="nomineeSelectBox">
<div id="dialog2" className="triangle_down1"></div>
<div className="arrowdown">
<Multiselect
onSelect={handleTypeSelect}
onRemove={handleTypeRemove}
options={selectedOption.length + 1 === maxOptions ? [] : options}
displayValue="key"
showCheckbox={true}
emptyRecordMsg={"Maximum nominees selected !"}
/>
</div>
</div>
<form onSubmit={handleSubmit(sendNomination)}>
<div className="nomineesSelectedList">
<h3>Selected Persons</h3>
{selectedOption.map((x, i) => (
<div key={i}>
<div className="row eachrecord">
<div className="column">
<label className="nomlabel">
{x[i].key} <b>>></b>
</label>
</div>
<input
required
type="textarea"
key={i}
id={i}
name={x[i].key}
className="nomineechoosed"
onChange={(e) => setNomRegister(e.target.value)}
/>
</div>
</div>
))}
<div className="row">
<div className="buttongroup">
<input id="Submit" type="submit" value="Submit" />
<input id="Cancel" type="button" value="Cancel" />
</div>
</div>
</div>
</form>
</div>
);
};
export default App;
Codesandbox link:
https://codesandbox.io/s/elastic-elbakyan-uqpzy?file=/src/App.js
After few modifications, I got a solution.
import React, { useRef, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Link, useHistory } from "react-router-dom";
import Multiselect from "multiselect-react-dropdown";
const options = [
{ key: "Aaron", id: 1 },
{ key: "Bader", id: 2 },
{ key: "Crots", id: 3 },
{ key: "Dan", id: 4 },
{ key: "Elep", id: 5 },
{ key: "Pal", id: 6 },
{ key: "Quilt", id: 7 }
];
const App = () => {
const maxOptions = 3;
const [selectedOption, setSelectedOption] = useState([]);
const [nomRegister, setNomRegister] = useState([{}]);
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm();
// const onChange = (e) =>{
// e.persist();
// setNomRegister({ ...nomRegister, [e.target.id]: e.target.value });
// }
const handleTypeSelect = (e) => {
const copy = [...selectedOption];
copy.push(e);
setSelectedOption(copy);
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
console.log(copy);
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
// immutating state (best practice)
const updateList = nomRegister.map((item) => {
return { ...item };
});
//delete the specific array case depends on the id
updateList.splice(index, 1);
setNomRegister(updateList);
};
const sendNomination = () => {
console.log(
"Doesn't print all, what the heck: " + JSON.stringify(nomRegister) // i did JSON.stringify just to see the console
);
};
const handleChange = (e, i) => {
const { name, value } = e.target;
// immutating state (best practice)
const updateList = nomRegister.map((item) => {
return { ...item };
});
//change the specific array case depends on the id
updateList[i] = { ...updateList[i], name: name, reason: value };
setNomRegister(updateList);
};
return (
<div className="App">
<h1>Person selection</h1>
<div className="nomineeSelectBox">
<div id="dialog2" className="triangle_down1"></div>
<div className="arrowdown">
<Multiselect
onSelect={handleTypeSelect}
onRemove={handleTypeRemove}
options={selectedOption.length + 1 === maxOptions ? [] : options}
displayValue="key"
showCheckbox={true}
emptyRecordMsg={"Maximum nominees selected !"}
/>
</div>
</div>
<form onSubmit={handleSubmit(sendNomination)}>
<div className="nomineesSelectedList">
<h3>Selected Persons</h3>
{selectedOption.map((x, i) => (
<div key={i}>
<div className="row eachrecord">
<div className="column">
<label className="nomlabel">
{x[i].key} <b>>></b>
</label>
</div>
<input
required
type="textarea"
key={i}
id={i}
name={x[i].key}
className="nomineechoosed"
onChange={(e) => handleChange(e, i)}
/>
</div>
</div>
))}
<div className="row">
<div className="buttongroup">
<input id="Submit" type="submit" value="Submit" />
<input id="Cancel" type="button" value="Cancel" />
</div>
</div>
</div>
</form>
</div>
);
};
export default App;
check Codesandbox
I have an array of objects, and I rendered the names from it. What I would like to do is when I click on them, I get the exact object that contains the name I clicked on, and then render those other data out.
export default function App() {
const array = [
{
id: 1,
name: 'John',
num: '0123'
},
{
id: 2,
name: 'Dave',
num: '456'
},
{
id: 3,
name: 'Bruce',
num: '789'
},
]
const handleClick = (e) => {
console.log(e.target)
}
return (
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={handleClick}>{item.name}</p>
</div>
)
})}
</div>
);
}
You need:
add to function handleSelect(userID) with argument userID:
const handleSelect = (userId) => {
const currentUser = array.filter(item => item.id === userId)[0]
console.log(currentUser) // or return
}
Add onClick event to div or p
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={() => handleSelect(item.id)}>{item.name}</p>
</div>
)
})}
</div>
Give some data-* attribute to the clicked element.
Find the item by id.
Do not use the index as a key in React. Use unique id.
Wrap functions inside React.useCallback.
export default function App() {
const array = [
{
id: 1,
name: 'John',
num: '0123'
},
{
id: 2,
name: 'Dave',
num: '456'
},
{
id: 3,
name: 'Bruce',
num: '789'
},
]
const handleClick = React.useCallback((e) => {
const clickedId = e.target.getAttribute('data-id');
const item = array.find(({ id }) => id === clickedId);
console.log(item);
}, []);
return (
<div className="App">
{array.map((item) => {
return (
<div key={item.id}>
<p data-id={item.id} onClick={handleClick}>{item.name}</p>
</div>
)
})}
</div>
);
}
This would do:
const handleClick = (id, name, num) => {
console.log(`id=${id}, name=${name}, num=${num} `);
};
return (
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={()=>handleClick(item.id, item.name, item.num)}>{item.name}</p>
</div>
);
})}
</div>
);
Here is a codesandbox link: for the above
Ofcourse, you can try to re-access the object. Check this out:
const handleClick = (id, name, num) => {
console.log(`Easy way : id=${id}, name=${name}, num=${num} `);
//Re-access the object. But why?
const desiredObjArr = array.filter(obj=> obj.id=id);
console.log(`I get this in a tough way: ${desiredObjArr[0].name}, ${desiredObjArr[0].id}, ${desiredObjArr[0].num}`);
};
The temptation is to treat the React onClick as if it was the same as an onclick attribute in an html tag. But while in the latter you would then call the function you want to execute along with it's argument (say, like so: <p onclick="handleClick(item)", in the former you wrap the function you are calling in another anonymous function. Following those strictures, to make your own code work as you want it to, you only have to revise it slightly like so:
const handleClick = (item) => {
console.log(item.id, item.name, item.num)
}
return (
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={()=>{handleClick(item)}}>{item.name}</p>
</div>
)
})}
</div>
);
Here's a working Stackblitz: https://stackblitz.com/edit/react-6ywm5v
I am trying to push the {database, id} to the end of the databaseChanges object which will be stored in a state variable as I want to access all of them. However I am getting undefined when I try to set it a new state variable (setDatabaseArr).
Here is my code:
const UnitTestsDatabaseView = props => {
const [databaseArr, setDatabaseArr] = useState('')
const addToProduction = test => () => {
const databaseChanges = props.unitTestsData.map(test => {
return {
"unit_test_id": test.id,
"databases": test.databases
}
})
const { databases, id } = test
console.log(databases, id)
databaseChanges.push(databases, id)
setDatabaseArr(databases, id)
console.log( setDatabaseArr(databases, id))
console.log( databaseChanges.push(databases, id))
}
return (
<div>
<div className='Card' style={{marginTop: '40px', overflow: 'hidden'}}>
<div className='TableTopbar UnitTestsGrid'>
<div>ID</div>
<div>Name</div>
<div>Database</div>
<div />
</div>
{props.unitTestsData && props.unitTestsData.map(test =>
<div key={test.id} className='Table UnitTestsGrid' style={{overflow: 'hidden'}}>
<div>{test.id}</div>
<div>{test.unit_test_name}</div>
<div>{test.databases}
<div>
<Checkbox
mainColor
changeHandler={addToProduction(test)}
data={{}}
id={test.id}
/>
</div>
</div>
</div>
)}
</div>
</div>
)
}
export default withRouter(UnitTestsDatabaseView)
I review your code, It seems there is a problem with the implementation on how to push a value to the state.
I tried to reproduce the problem and try to implement of which I think a solution.
And here is the code
import React, { useState, useEffect } from "react";
import { Checkbox } from "#material-ui/core";
// In order to reproduce the propblem
// Lets that these are the values of the unitTestsData props
// and instead of passing this as value of a props
// I defined it right here.
const unitTestsData = [
{ id: 1, unit_test_name: "Unit I", databases: "test1" },
{ id: 2, unit_test_name: "Unit II", databases: "test2" },
{ id: 3, unit_test_name: "Unit III", databases: "test3" }
];
const UnitTestsDatabaseView = () => {
const [databaseArr, setDatabaseArr] = useState([]);
// Maybe you want to push data if the checkbox is checked
// and pop the data if checkbox is unchecked :: Yes ???
// This is how you do it.
const addToProduction = ({ target }, { id, databases }) => {
setDatabaseArr((previousState) => {
let newState = [...previousState];
if (target.checked) {
newState = [
...newState,
{ unit_test_id: newState.length + 1, databases }
];
} else {
const i = newState.findIndex(({ unit_test_id }) => unit_test_id === id);
if (i !== -1) newState.splice(i, 1);
}
return newState;
});
};
useEffect(() => {
console.log("databaseArr", databaseArr);
}, [databaseArr]);
return (
<div>
<div className="Card" style={{ marginTop: "40px", overflow: "hidden" }}>
<div className="TableTopbar UnitTestsGrid">
<div>ID</div>
<div>Name</div>
<div>Database</div>
</div>
{unitTestsData.map((test) => {
const { id, unit_test_name, databases } = test;
return (
<div
key={id}
className="Table UnitTestsGrid"
style={{ overflow: "hidden" }}
>
<div>{id}</div>
<div>{unit_test_name}</div>
<div>
{databases}
<div>
<Checkbox
color="secondary"
onChange={(e) => addToProduction(e, test)}
data={{}}
id={id.toString()}
/>
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
export default UnitTestsDatabaseView;
You may click the codesandbox link to see the demo
https://codesandbox.io/s/pushing-value-49f31