Reputation: 531
what I'm trying to do is simple. I have two buttons, one that says toggle on and one that says toggle off. When I click one of them, I want to set my state to true or false (depending on which button was clicked) and re render, using the newly set true false values. But for some reason, rows.map is not a function. Here is what I have so far:
function Calculator() {
const [rows, setRows] = useState([
{
rowVal: 0,
editing: false,
someVal: 20
},
{
rowVal: 1,
editing: true,
someVal: 20
},
])
return(
<div className="container">
<table className="table">
<thead>
<tr>
<th className="text-center" scope="col">SomeVal</th>
</tr>
</thead>
<tbody>
{
const togOff = <button onClick={setRows({editing: false})}>Toggle Off</button>
const togOn = <button onClick={setRows({editing: true})}>Toggle On</button>
rows.map(val => {
if (val.editing === true){
togOff
} else {
togOn
}
})
}
</tbody>
</table>
</div>
)
}
If anyone can point me in the right direction, that would be great.
Upvotes: 2
Views: 10835
Reputation: 563
rows.length>0 && rows.map((val) => {
if (val.editing === true) {
togOff;
} else {
togOn;
}
});
Just check the length before going for the map. If it is more than 0 then go for it.
Upvotes: 0
Reputation: 43
I believe that this is what you need, try change the useState to useReducer as shown bellow.
"The useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks."
function Calculator() {
const [rows, setRows] = useReducer(
(state, newState) => ([{ ...state, ...newState }]),
[
{
rowVal: 0,
editing: false,
someVal: 20
},
{
rowVal: 1,
editing: true,
someVal: 20
},
]);
Upvotes: 0
Reputation: 1960
As your toggle's aren't massively different you could use the editing
flag to conditionally apply your setRows
arguments and the Off/On
text.
This keeps you from using your const
toggles... best to keep them out of your JSX anyway, keep your variable declarations above your JSX.
Also don't forget, if you need to pass arguments to callback Functions that aren't implicitly passed you will need to wrap
your callback in a function otherwise your callback is invoked straight away.
return (
<div>
{rows.map(({ editing }) => (
<button onClick={() => setRows({editing: !editing})}>Toggle {editing ? 'Off' : 'On'}</button>
)}
</div>
)
Upvotes: 0
Reputation: 31713
Your code have several problems.
You need to declare the variable before the return to use it inside { }
Wrong
{
const togOff = <button onClick={setRows({editing: false})}>Toggle Off</button> // wrong
const togOn = <button onClick={setRows({editing: true})}>Toggle On</button> //wrong
rows.map(val => {
if (val.editing === true){
togOff
} else {
togOn
}
})
}
Right
...
const togOff = <button onClick={setRows({editing: false})}>Toggle Off</button> // outside the return
const togOn = <button onClick={setRows({editing: true})}>Toggle On</button> // outside the return
return (
...
)
setRows({editing: true})
, this will rerender the component infinitelyWrong
const togOff = <button onClick={setRows({editing: false})}>Toggle Off</button> // wrong
const togOn = <button onClick={setRows({editing: true})}>Toggle On</button> //wrong
Right
const togOff = <button onClick={ () => setRows({editing: false})}>Toggle Off</button> // Added arrow function
const togOn = <button onClick={ () => setRows({editing: true})}>Toggle On</button> //Added arrow function
.map
functionWrong
rows.map(val => {
if (val.editing === true){
togOff //wrong
} else {
togOn //wrong
}
})
Right
rows.map(val => {
if (val.editing === true){
return togOff // use return
} else {
return togOn // use return
}
})
Doing all of this, you still have problems because you can't do setRows({editing: true})
because rows
are an array, not an object like {editing: true}
Please clarify what you want to do in setRows
so I can come up with the answer, but until this, your erros are partially solved.
I can't be 100% sure on what are the expected result after the error is gone, but here is a working demo on what I think is what you want.
Upvotes: 2
Reputation: 22935
You are doing multiple wrong things.
You need to update editing
value on a specific index so a your toggle function will take index
and updated value.
const toggle = (index, value) => {
const newRows = [...rows];
newRows[index].editing = !value;
setRows(rows);
}
rows.map((row, index) => {
if (row.editing === true){
return <button onClick={() => toggle(index, row.editing)}>Toggle Off</button>
} else {
return <button onClick={() => toggle(index, row.editing)}>Toggle ON</button>
}
})
Upvotes: 0
Reputation: 426
What is happening here is that you are using setRows
just like the setState
method of a class component (which is fine for updating the rows
), but it won't work for setting anything other than rows
. As stated in the React documentation:
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax.
[...]
Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.
So, what you can do if you want two keys in your state is something like:
const [state, setState] = useState({
rows: [
{
rowVal: 0,
editing: false,
someVal: 20
},
{
rowVal: 1,
editing: true,
someVal: 20
},
],
editing: false,
});
And then
setState(prevState => {...prevState, editing: true|false})
Upvotes: 0