I have a page of just a list of divs calling an onClick function. Pretty much this...
<div
className="square card-container bg-primary"
points="200"
id="2"
onClick={e => select(200, "cat1", e.target.id)}
>
<h3 className="text-light">200</h3>
</div>
Just 30 of them. With sequential ID's. It's easy enough to push the ID's to a new array when clicked, or to add className once they're clicked, and when I step through the code in Debugger I can see that the class is Added and the styles change, but it is immediately set back to the previous "Un-clicked" className. I was thinking that it might work to make each of these div it's own component and than use useState, but since they're all mostly identical I would still need a key or id, so I'm not sure how I would pass that in to a state and use that conditionally? I'm sorry if this doesn't make sense. Basically I just want to change styles slightly once an item has been selected, without going back to the initial className when it is re-rendered.
This is the script that gets called onClick.
const selected = []
const change = id => {
selected.push(id);
console.log(selected);
};
const select = (points, cat, id) => {
let newArr = questions.filter(
q => q.category === "Sample 1" && q.points === points
);
change(id);
if (newArr.length > 1) {
let randomOutput = Math.floor(Math.random() * newArr.length);
console.log(newArr[randomOutput]);
let out = newArr[randomOutput];
props.setQuestion({ out });
props.detail();
return out;
} else {
let out = newArr;
props.setQuestion({ out });
props.detail();
console.log(points, cat);
}
}
You need to store a list of selected items using state and make className dynamic
Example: https://jsfiddle.net/78bsnhgw/
Related
I have a span which has a className . The span has two state, one is like
<span key={uuid()} ref = {colorRef} className = 'singleWord'>{word}</span>
and the other is like
<span key={uuid()} ref = {colorRef} className = 'singleWord redword'>{word}</span>
Now what I want is to check the className value like className.classList.contain("oneClass') which we do in vanilla js but I need in react hook(may be with ref hook) and also add className and remove className . also is it possible to check the length of className .for example 'singleword redword' should give length 2.
So overall I need -
add or remove classes
check length
check if class contains or not
I need to check them in react hook
could anybody suggest how to do ??
Thanks in advance
You can store in state an array of classes and use that to get all the information you need plus it gives an easy way to toggle any class in a set of classes.
basically this handleClasses function takes a class argument and then checks whether that class is already in your state. If it is, it removes it, if it's not, it add it to the state. Then in your span you join all the classes in the array in state to make a single string of classes.
Also you get the number of classes applied to your element very easily by doing classes.length
import React, { useState } from 'react';
const Component = () => {
const [classes, setClasses] = useState(['singleWord']);
const numberOfClasses = classes.length;
const handleClasses = (c) => {
setClasses(classes.includes(c)
? classes.filter((cls) => cls !== c)
: [...classes, c]
)
}
return (
<span
onClick={() => handleClasses(passClassToToggleHere)}
className={classes.join(' ')}
>
text
</span>
)
};
If you are using colorRef = useRef() hook, then you can obtain the target node by calling colorRef.current and proceed with vanilla js methods like
colorRef.current.classList.contain("oneClass') and so on.
I have a table with checkboxes and I want to save selected checkboxes' ids in state. So here's my code.
Input looks like this (I use coreui so this is the inside of a table's scopedSlots):
selected: (item) => {
return (
<td style={{ width: '40px' }}>
<CInputCheckbox
className="mx-auto"
id={item.id}
onChange={(e) => handleSelect(e)}
/>
</td>
);}
And this is the rest:
const [selectedRows, setSelectedRows] = useState([]);
const handleSelect = (e) => {
const id = e.target.id;
const index = selectedRows.indexOf(id);
const rows = [...selectedRows];
if (index === -1) {
rows.push(id);
} else {
rows.split(index, 1);
}
setSelectedRows(rows);
};
And the weirdest thing happens - in the chrome's react devtools I see that the first id is being added to the selectedRows array and then when I select another row - the previous item in the array is being overwritten. When i console.log my selectedRows array it shows empty array always (even if I see in the devtools that there's one item). I have no idea what I'm doing wrong here.
There is not a lot of information to work with in your question, so I had to make many assumptions about how are they suppose to work, but this is what I got so far:
the handleSelect function has a little error when it comes to removing items from the list, your function is using split but I think what you meant was slice
I think the scopes of handleSelect and const [selectedRows, setSelectedRows] = useState([]); are wrong. For what I can see, they are at the same level of selected component, which will fail as they will only be able to keep track of one selected component at the time, and I think you are needing to keep track of multiple selected components.
Here is a working version of what I think you are trying to do
https://codepen.io/richard-unal/pen/eYWXXOo?editors=1111
If you need clarification on something, please let me know, I'm aware that I'm not the best explainer.
I have simple list which is dynamically added on add button click. in my list there is a checkbox is also present .so I have an issue when I toggle the checkbox my whole list is re render why ?
let take example I added A,B,C,D in my list when I toggle D checkbox it should only render D item currently it render whole list why ?
here is my code
https://codesandbox.io/s/stupefied-wildflower-gv9be
const Item = ({ text, checked, onCheckedHandler }) => {
console.log(checked, "ssss");
return (
<div className={checked ? "bg" : ""}>
<span>{text}</span>
<input type="checkbox" onChange={e => onCheckedHandler(e, text)} />
</div>
);
};
Every time items changes (whether by adding a new item or checking a value), you are creating a new onCheckedHandler in your App. This propagates down to your Item component. Since the previous onCheckedHandler property is not referentially equivalent to the previous one, it renders (and you see that console log for each item). Memoizing the component alone won't help because a property being passed to it is changing every time.
To get around that, you need to memoize the onCheckedHandler, try this:
const onCheckedHandler = useCallback((e, selectedText) => {
const target = e.target
setItems(items => {
const i = items.findIndex(i => i.text === selectedText);
let obj = items[i];
obj.checked = target.checked;
return [...items.slice(0, i), obj, ...items.slice(i + 1)];
})
}, [setItems])
The you can wrap your Item compoennt with React.memo, and it should work as expected. You'll also need to import the useCallback the same way you import useState
I am learning react through the university of helsinki's full stack open course. The point of the code is there are two buttons and a quote shown on page. One button goes to a random quote and the other lets you put in a vote and shows how many votes that quote has total. The problem is that when I click vote, it adds the vote in the background but doesn't re-render the total amount of votes unless I change the quote.
I've tried different ways to go about conducting state change such as creating a function specifically for the setVote but I can't get it to work.
const App = () => {
const [selected, setSelected] = useState(0)
let [vote, setVote] = useState([...copyVote])
const changeAnecdote = () => {
setSelected(Math.floor(Math.random() * 6))
}
const addVote = () => {
copyVote[selected] = copyVote[selected] + 1
setVote(vote = copyVote)
}
return (
<div>
<Button onClick={changeAnecdote} text='Next Anecdote'/>
<Button onClick={addVote} text='Vote'/>
<div>
{anecdotes[selected]}
<DisplayVotes vote={vote} selected={selected}/>
</div>
</div>
)
}
copyVote is a copy of a zero-filled array and DisplayVotes simply shows how many votes total for that quote on screen.
When I check for changes in the array of votes after hitting vote through developer tools, the array doesn't change until I go to another quote.
Anyone have any ideas as to what I'm doing wrong?
There is a few things that should be updated here.
Firstly, copyVote is an array containing the number of votes for each quote. So ...copyVote will give you each item in that array. You only want the vote for the current quote. Your initial vote state should be
const [vote, setVote] = useState(copyVote[selected])
You also want to update the addVote function the way DSCH mentioned.
const addVote = () => {
// copyVote[selected]++ is the same as copyVote[selected] += 1 and copyVote[selected] = copyVote[selected] + 1
setVote(copyVote[selected]++)
}
Finally, you want to add a way to update the vote each time the anecdote is changed. You could do this in the changeAnecdote function, but a better approach would be to use an effect hook that is dependent on the selected state.
useEffect(() => {
// Set the value of vote to match the newly selected quote
setVote(copyVote[selected])
}, [selected])
Using this, the DisplayVotes vote prop is only going to display the vote for the currently selected quote. You may need to updated that component to handle this.
In setVote(vote = copyVote) the expression within the parentheses is an expression to set the vote variable with the value in copyVote. I assume that's why you use let in let [vote, setVote] = useState([...copyVote]), since probably you got an error setting a value to a const.
As setVote is what return from the useState what you probably want to do is:
const addVote = () => {
copyVote[selected] = copyVote[selected] + 1
setVote(copyVote[selected])
}
there are three drop down menus in the initial state.
after I select the first drop down the second drop down values gets loaded
after I select the second drop down values.
a new set of drop down loads.
when I select remove button of the second set.
it doesnt remove that set but it removes the first set.
when I debugged removeSelectedValue method there slices are happening correctly but its not updating in the updating
can you tell me how to pass the queryComponents values so that it will update in the UI.
can you tell me how to fix it.
so that in future I will fix it myself.
providing my relevant code snippet and sandbox below.
all my code is in demo.js
https://codesandbox.io/s/4x9lw9qrmx
removeSelectedValue = index => {
console.log("removeSelectedValue--->", index);
let seletedValues = this.state.queryComponents;
seletedValues.splice(index, 1);
console.log("spliced Values--->", seletedValues);
this.setState({ queryComponents: seletedValues });
};
render() {
let queryComp = this.state.queryComponents.map((value, index) => {
return (
<AutoCompleteComponent
key={index}
value={value}
index={index}
valueSelected={this.getSelectedValue}
removeSeleted={this.removeSelectedValue}
/>
);
});
return <div>{queryComp}</div>;
}
When you do let seletedValues = this.state.queryComponents;
you're creating a reference to that variable, instead of making a copy.
You need to make sure you replace your state with a new object/array for the re-render to happen.
Please try this:
removeSelectedValue = index => {
this.setState(prevState => ({
queryComponents: prevState.seletedValues.filter((a, i) => (i !== index));
});
};
That filter function is equivalent to the splice you were using, but returns a new array instead of modifying the original one.
On the other hand, I 'm passing setState a function that uses prevState making the code shorter.