Adding and removing objects in state of a component - javascript

I have a list of check inputs, and when they are selected and deselected I am trying to add/remove them from the state. But Ive noticed when I deselect one the one I selected prior is the object removed from the state. I think this is because when the onChange function is called the state hasnt updated (although the UI has) but I dont understand how to have a full list of all the checks selected with the state lagging behind one value. Heres my input and onChange func:
const [selected, setSelected] = useState([]);
const handleSelect = (e) =>
!!DATA &&
DATA.forEach((item, idx) => {
let name = e.target.name;
if (item.payerName === name && !selected.includes(item)) {
setSelected([...selected, item]);
return;
} else if (item.payerName === name && selected.includes(item)) {
// index varies
let index = selected.indexOf(item);
let clean = selected.splice(index, 1);
setSelected(clean);
}
});
DATA.map((item, idx) => {
return(
<input
name={item.payerName}
type="checkbox"
checked={selected.includes(item)}
onChange={handleSelect}
className="insurance-checkbox m-2 mt-3"
/>
)
}

Try this:
const [selected, setSelected] = useState([]);
const handleSelect = (e) => {
const { name } = e.target;
const item = DATA.find(({ payerName }) => payerName === name);
if (selected.includes(item)) {
setSelected(selected.filter((s) => s === item);
} else {
setSelected([...selected, item]);
}
}
or even better:
const handleSelect = (e) => {
const { name, checked } = e.target;
const item = DATA.find(({ payerName }) => payerName === name);
if (checked) {
if (!selected.includes(item)) { // probably not needed
setSelected([...selected, item]);
}
} else {
setSelected(selected.filter((s) => s === item);
}
}

Related

React Select All Checkbox not working properly

I am trying to implement Select All check box functionality to delete all/ multiple posts in one click
React Component:-
function AllPosts() {
const dispatch = useDispatch();
const [page, setPage] = useState(0);
const { posts } = useSelector((state) => state.posts);
const [deletePosts, setDeletePosts] = useState([]);
const [postsData, setPostsData] = useState([]);
const { totalPages } = posts;
// get posts
useEffect(() => {
dispatch(getPosts(page));
}, [dispatch, page]);
// setting posts state
useEffect(() => {
setPostsData(posts?.Allposts);
}, [posts]);
// ***********************************************
// * *
// * Problem Part *
// * *
// * *
// ***********************************************
const handleAllChecked = (e) => {
// checkbox name is id of the posts
const { name, checked } = e.target;
// update list for IDs of selected checkbox
let updateList;
if (name === "CheckAll") {
// cant add post._id as it shows undefined
// deletPosts is an array of Ids to delete.
postsData?.map((post) => [...deletePosts, post._id]);
// check all checkboxes
let tempPost = postsData?.map((post) => {
return { ...post, isChecked: checked };
});
setPostsData(tempPost);
} else
{
let tempPost = postsData?.map((post) =>
post._id === name ? { ...post, isChecked: checked } : post
);
// add IDs of posts to delete to deletePosts Array
if (deletePosts.includes(name)) {
updateList = deletePosts.filter((item) => item !== name);
} else {
updateList = [...deletePosts, name];
}
// set deletePosts array
setDeletePosts(updateList);
setPostsData(tempPost);
}
};
// Delete Posts
const handleDelete = () => {
dispatch(deletePost(deletePosts));
};
return (
<>
<div className="all-posts-wrapper">
<div className="all-posts-main-container">
<ActionBar>
{/* *******************************************
*
* Check Box and Delete Button component
*
*
*************************************************/}
<ActionBarBtns
checked={
!postsData?.some((post) => post?.isChecked !== true)
}
name="CheckAll"
onChange={handleAllChecked}
handleDelete={handleDelete}
/>
</ActionBar>
<div className="all-posts-main-content">
<div className="all-posts">
<>
{typeof postsData === typeof [] && (
<>
{postsData.map((post, index) => (
<Post
key={post._id}
postId={post._id}
postSubject={post.subject}
checked={post?.isChecked || false}
onChange={handleAllChecked}
checkBoxName={post._id}
/>
))}
</>
)}
</>
</div>
</div>
</div>
</div>
</>
);
}
export default AllPosts;
The problem part of the code
const handleAllChecked = (e) => {
// checkbox name is id of the posts
const { name, checked } = e.target;
// update list for IDs of selected checkbox
let updateList;
if (name === "CheckAll") {
// cant add post._id as it shows undefined
// deletPosts is an array of Ids to delete.
postsData?.map((post) => [...deletePosts, post._id]);
// check all checkboxes
let tempPost = postsData?.map((post) => {
return { ...post, isChecked: checked };
});
setPostsData(tempPost);
} else
{
let tempPost = postsData?.map((post) =>
post._id === name ? { ...post, isChecked: checked } : post
);
// add IDs of posts to delete to deletePosts Array
if (deletePosts.includes(name)) {
updateList = deletePosts.filter((item) => item !== name);
} else {
updateList = [...deletePosts, name];
}
// set deletePosts array
setDeletePosts(updateList);
setPostsData(tempPost);
}
};
My Intention:-
whenever any Checkbox is selected its Id should be added to the deletePosts
array
whenever the Select All checkbox is checked all posts Ids should be added to
deletePosts array
Code's current working:-
If any or all posts checkbox is checked then their IDs are added to deletePosts array.
This Works
If the Select All checkbox is checked then all posts IDs are not added to deletePosts
the deletePosts array is undefined when Select All is checked.

Simulate "shift" pressing key on checkbox to select multiple rows

I have the following input
<input type="checkbox" checked={isChecked}
onChange={handleOnChange}/>
and my function is this
const handleOnChange = () => {
let element:any = document.querySelector('input');
element.onkeydown = (e: { key: any; }) => alert(e.key);
element.dispatchEvent(new KeyboardEvent('keydown',{'key':'Shift'}));
setIsChecked(!isChecked);
};
This checkbox is created dinamically as I add new rows and I would like to simulate holding the key "shift" so that when I check multiple checkboxes these rows remain selected.
I am using reactjs.
There's no native way to do it but you can implement it based on the index you have checked.
const checkboxes = new Array(20).fill(null);
export default function App() {
const [checked, setChecked] = useState([]);
const lastChecked = useRef(null);
const handleChange = useCallback((e) => {
const index = Number(e.target.dataset.index);
if (lastChecked.current !== null && e.nativeEvent.shiftKey) {
setChecked((prev) => {
const start = Math.min(lastChecked.current, index);
const end = Math.max(lastChecked.current, index);
return uniq([...prev, ...range(start, end), end]);
});
return;
}
if (e.target.checked) {
lastChecked.current = index;
setChecked((prev) => [...prev, index]);
} else {
lastChecked.current = null;
setChecked((prev) => prev.filter((i) => i !== index));
}
}, []);
return (
<div>
{checkboxes.map((_, i) => (
<div key={i}>
<label>
<input
checked={checked.includes(i)}
data-index={i}
type="checkbox"
onChange={handleChange}
/>
checkbox {i}
</label>
</div>
))}
</div>
);
}

Rendered more hooks than during the previous render React issue

I've re edited the question as it was not relevant... I got an issue in appearing in my browser when I launch my app, this issue is:
Rendered more hooks than during the previous render.
I've look all over the internet, but still don't manage to make it work.
Here is my code:
const DefaultValue = () => {
let matchingOption = options.find((option) => option.value.includes(countryLabel))
let optionSelected = options.find((option) => option.value === value)
const hasCountryLabelChanged = countryHasChanged(countryLabel)
const [selectedPathway, changeSelectedPathway] = useState(matchingOption)
useEffect(() => {
if (hasCountryLabelChanged) {
if(matchingOption) {
changeSelectedPathway(matchingOption)
} else {
changeSelectedPathway(options[0])
}
} else {
changeSelectedPathway(optionSelected)
}
},[matchingOption, optionSelected, selectedPathway, hasCountryLabelChanged])
if(selectedPathway !== undefined) {
const newLevers = levers.map((lever, index) => {
lever.value = +pathways[selectedPathway.value][index].toFixed(1) * 10
return lever
})
dispatch(Actions.updateAllLevers(newLevers))
}
return selectedPathway
}
const countryHasChanged = (countryLabel) => {
const prevValue = UsePrevious(countryLabel)
return prevValue !== countryLabel
}
const UsePrevious = (countryLabel) => {
const ref = useRef()
useEffect(() => {
ref.current = countryLabel
})
return ref.current
}
the "selectedPathway" is shown in < select value={DefaultValue} />
Your optionValueCheck call should happen inside a useEffect with one of the dependency params as countryLabel. So that whenever countryLabel updates, your function is executed.

The component isn't updating when I pass in a filtered variable on a timer

So I was trying to implement a filter that is controlled by a search bar input. So I think part of the problem is that I have this filter hooked on a timer so that while the user is typing into the search bar, it isn't re-running for each letter typed in.
What it is currently doing is that after the item is typed in the search bar, the timer goes off and the filters are working but it doesn't appear that the app is re-rendering with the new filtered variable.
I suspect that it might have something to do with useEffect but I'm having trouble wrapping my head around it and it wasn't working out for whatever I was doing with it.
Here's the code:
const RecipeCards = (props) => {
const inputTypingRef = useRef(null);
let preparingElement = props.localRecipes;
let cardElement;
let elementsSorted;
const ingredientCountSort = (recipes) => {
elementsSorted = ...
}
const elementRender = (element) => {
cardElement = element.map((rec) => (
<RecipeCard
name={rec.name}
key={rec.id}
ingredients={rec.ingredients}
tags={rec.tags}
removeRecipe={() => props.onRemoveIngredients(rec.id)}
checkAvail={props.localIngredients}
/>
));
ingredientCountSort(cardElement);
};
if (inputTypingRef.current !== null) {
clearTimeout(inputTypingRef.current);
}
if (props.searchInput) {
inputTypingRef.current = setTimeout(() => {
inputTypingRef.current = null;
if (props.searchOption !== "all") {
preparingElement = props.localRecipes.filter((rec) => {
return rec[props.searchOption].includes(props.searchInput);
});
} else {
preparingElement = props.localRecipes.filter((rec) => {
return rec.includes(props.searchInput);
});
}
}, 600);
}
elementRender(preparingElement);
return (
<div className={classes.RecipeCards}>{!elementsSorted ? <BeginPrompt /> : elementsSorted}</div>
);
};
Don't worry about ingredientCountSort() function. It's a working function that just rearranges the array of JSX code.
Following up to my comment in original question. elementsSorted is changed, but it doesn't trigger a re-render because there isn't a state update.
instead of
let elementsSorted
and
elementsSorted = ...
try useState
import React, { useState } from 'react'
const RecipeCards = (props) => {
....
const [ elementsSorted, setElementsSorted ] = useState();
const ingredientCountSort = () => {
...
setElementsSorted(...whatever values elementsSorted supposed to be here)
}
Reference: https://reactjs.org/docs/hooks-state.html
I used useEffect() and an additional useRef() while restructuring the order of functions
const RecipeCards = (props) => {
//const inputTypingRef = useRef(null);
let preparingElement = props.localRecipes;
let finalElement;
const [enteredFilter, setEnteredFilter] = useState(props.searchInput);
let elementsSorted;
const [elementsFiltered, setElementsFiltered] = useState();
const refTimer = useRef();
const filterActive = useRef(false);
let cardElement;
useEffect(() => {
setEnteredFilter(props.searchInput);
console.log("updating filter");
}, [props.searchInput]);
const filterRecipes = (recipes) => {
if (enteredFilter && !filterActive.current) {
console.log("begin filtering");
if (refTimer.current !== null) {
clearTimeout(refTimer.current);
}
refTimer.current = setTimeout(() => {
refTimer.current = null;
if (props.searchOption !== "all") {
setElementsFiltered(recipes.filter((rec) => {
return rec.props[props.searchOption].includes(enteredFilter);
}))
} else {
setElementsFiltered(recipes.filter((rec) => {
return rec.props.includes(enteredFilter);
}))
}
filterActive.current = true;
console.log(elementsFiltered);
}, 600);
}else if(!enteredFilter && filterActive.current){
filterActive.current = false;
setElementsFiltered();
}
finalElement = elementsFiltered ? elementsFiltered : recipes;
};
const ingredientCountSort = (recipes) => {
console.log("sorting elements");
elementsSorted = recipes.sort((a, b) => {
...
filterRecipes(elementsSorted);
};
const elementRender = (element) => {
console.log("building JSX");
cardElement = element.map((rec) => (
<RecipeCard
name={rec.name}
key={rec.id}
ingredients={rec.ingredients}
tags={rec.tags}
removeRecipe={() => props.onRemoveIngredients(rec.id)}
checkAvail={props.localIngredients}
/>
));
ingredientCountSort(cardElement);
};
//begin render /////////////////// /// /// /// /// ///
elementRender(preparingElement);
console.log(finalElement);
return (
<div className={classes.RecipeCards}>{!finalElement[0] ? <BeginPrompt /> : finalElement}</div>
);
};
There might be redundant un-optimized code I want to remove on a brush-over in the future, but it works without continuous re-renders.

How to get sum of array from two different input in two different components? React

I am new to React and I am building a budget calculator. I am taking the amount from one input and adding it to another input so I can come up with the balance. I have tried reduce and concat and they are coming up to sum but the value is wrong. I don't know what I'm doing wrong. Can anyone point me in the right direction. I think the problem is that the values are rendering twice and that's throwing off the math. I don't know.
Here is my code:
// this is the component to get the balance
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
balance: []
}
}
getBalance = (total) => {
this.setState((prevState) => ({
balance: [prevState.balance, total].reduce((acc, currentVal) => {
return Number(currentVal) + Number(acc)
}, 0)
}));
}
render() {
return (
<div className="App" >
<div className="count">
<h2 className="balancetitle">Your Balance</h2>
<h1 style={{ color: this.state.balance >= 0 ? 'green' : 'red' }}>${this.state.balance}</h1>
</div>
<Transactions getBalance={(total) => this.getBalance(Number(total))} />
<Income getBalance={(total) => this.getBalance(Number(total))} />
</div>
);
}
}
// this is the code to get the transaction. I have another component that is identical to get the sum of the income.
const Transactions = (props) => {
const [expenses, setExpense] = useState([])
const [amount, setAmount] = useState([])
const [id, setId] = useState([])
const [listOfTrans, setListofTrans] = useState([])
const [total, setTotal] = useState([0])
//fires on click or enter
const handleSubmit = (e) => {
e.preventDefault()
addExpense({
amount,
expenses,
id
});
setAmount('')
setExpense('')
}
//get value of inputs
const getValue = (hookSetter) => (e) => {
let { value } = e.target;
return hookSetter(value)
}
// turn amount and expense into objects and put them setListofTranas
const addExpense = (expenseObject) => {
setListofTrans([...listOfTrans, expenseObject])
}
const show = () => {
if (listOfTrans.legnth > 1) {
return listOfTrans
} else return null
}
// get total amount of listoftrans
const getAmount = () => {
if (listOfTrans.length > 0) {
let listAmount = listOfTrans.map(list => {
if (list.amount) {
return -Math.abs(list.amount);
} else {
return 0;
}
})
return listAmount.reduce((acc, currentValue) => {
return Number(acc) + Number(currentValue)
}, 0)
} else return 0
}
//update amount total on click
useEffect(() => {
setTotal(getAmount())
props.getBalance(getAmount())
}, [listOfTrans])
// delete item from array
const deleteExpense = (i) => {
let objExpense = i
setListofTrans(listOfTrans.filter((list) => {
return list.id !== objExpense
}))
}
I am adding it here as the suggestion is not possible to add long description in comments section.
What you are doing buggy in the the solution above is making use of useEffect to do the calcualtions. The approach can be real buggy and difficult to debug.
//update amount total on click
useEffect(() => {
setTotal(getAmount())
props.getBalance(getAmount())
}, [listOfTrans])
In the code above listOfTans is an array , may be changing due to various operation, which cause the useEffect callback to run repeatedly. The callback is reponsible for updating the sum.
So instead of doing that, you should just call
props.getBalance(getAmount())
in onClick Handler.
This is just the suggestion for what I can understand from the question above.

Categories