Ext-
Ext-

Reputation: 531

React hooks map is not a function

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

Answers (6)

Mujahidul Islam
Mujahidul Islam

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

S&#233;rgio Correia
S&#233;rgio Correia

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

Francis Leigh
Francis Leigh

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

Vencovsky
Vencovsky

Reputation: 31713

Your code have several problems.

1. You can't declare a variable inside the return

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 (
    ...
)

2. You can't just call setRows({editing: true}), this will rerender the component infinitely

Wrong

    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

3. You need to return something in the .map function

Wrong

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

OBSERVATION

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

Zohaib Ijaz
Zohaib Ijaz

Reputation: 22935

You are doing multiple wrong things.

  1. onClick should be given a callback/function instead of function execution output
  2. You are overriding list/array value with an object

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

Jairo
Jairo

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

Related Questions