React - combine functions

Viewed 682

How could I combine those two functions (handleSelectAll and handleSelectNone)? Toggle (on/off) wouldn't work here as in most cases some options would be 'checked' so you won't know whether to toggle it ALL or NONE so there need to be 2 separate buttons (at least that's what I think). What I was thinking was that the function can be shared

const handleSelectAll = () => {
    setCategories(oldCats => oldCats.map(category => {
        return {
          ...category,
          selected: true
        }
    }))
  }

  const handleSelectNone = () => {
    setCategories(oldCats => oldCats.map(category => {
        return {
          ...category,
          selected: false
        }
    }))
  }

and then the buttons in a component:

const Categories = (props) => {
  return(
    <div className="categories">
      <h2>Categories</h2>
      <form className="check-form">
        {props.categories.map(category => (
          <Category key={category.id} category={category} {...props} />
        ))}
      </form>
     <button onClick={props.handleSelectAll}>
          Select All
     </button>
     <button onClick={props.handleSelectNone}>
       Select None
     </button>
    </div>
  );
};

Would there be a way of just defining one function for both buttons?

4 Answers

Yes. You can wrap the call for props.handleSelectAll and props.handleSelectNone with a function and pass the new value as argument:

const Categories = (props) => {
  return(
    <div className="categories">
      <h2>Categories</h2>
      <form className="check-form">
        {props.categories.map(category => (
          <Category key={category.id} category={category} {...props} />
        ))}
      </form>
     <button onClick={()=>props.handleSelect(true)}>
          Select All
     </button>
     <button onClick={()=>props.handleSelect(false)}>
       Select None
     </button>
    </div>
  );
};

[ ()=>props.handleSelect(true) is an arrow function that calls handleSelect on click ]

And the new function will be:

  const handleSelect= (newValue) => {
    setCategories(oldCats => oldCats.map(category => {
        return {
          ...category,
          selected: newValue
        }
    }))
  }

You can make it one function using a parameter, such as

const handleChangeAll = (selected) => {
    setCategories(oldCats => oldCats.map(category => {
        return {
          ...category,
          selected: selected
        }
    }))
  }

Then you can call this function with a parameter in each button like this:

const Categories = (props) => {
  return(
    <div className="categories">
      <h2>Categories</h2>
      <form className="check-form">
        {props.categories.map(category => (
          <Category key={category.id} category={category} {...props} />
        ))}
      </form>
     <button onClick={() => props.handleChangeAll(true)}>
          Select All
     </button>
     <button onClick={() => props.handleChangeAll(false)}>
       Select None
     </button>
    </div>
  );
};

I usually do like this, keep things simple and legible, nothing too fancy:

const handleSelect = (selected = false) => {
  setCategories((oldCats) =>
    oldCats.map((category) => {
      return {
        ...category,
        selected,
      }
    })
  )
}

const handleSelectAll = () => {
  return handleSelect(true)
}

const handleSelectNone = () => {
  return handleSelect()
}

(the render part continues as is)

Doing like so avoids you creating that extra template function passing an argument and creating a new function on every render

selected: !category.selected

const handleSelectNone = () => {
    setCategories(oldCats => oldCats.map(category => {
        return {
          ...category,
          selected: !category.selected
        }
    }))
  }
Related