首页 > 解决方案 > How to reset multiple counters with one button in React

问题描述

The TLDR of my question is that each of my child components keeps and displays its own counter clicks. Each of those clicks also updates a Total that is displayed. I'd like to know how to have the Reset button to also have those children components reset.

I have an app that displays a total, a number of buttons that each increment the total in a different way and a reset button. In this small app weights is an array full of different weight objects

class CountersContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      total: 0,
    }
    this.resetTotal = this.resetTotal.bind(this)
    this.updateTotalByWeights = this.updateTotalByWeights.bind(this)
  }

  resetTotal = () => {
    this.setState({
      total: 0,
    })
  }

  updateTotalByWeights = value => {
    this.setState({ 
        total: this.state.total + value * 2,
      })
  }

  render() {
    return (
      <>
        <div className="container">
          {weights.map(w => (
            <Weight
              key={w.kg}
              incrementTotal={this.updateTotalByWeights}
              weight={w.kg}
            />
          ))}
        </div>
        <button className="click-target" onClick={this.resetTotal}>
          reset
        </button>
        <WeightOutput total={this.state.total} units={this.state.metric} />
      </>
    )
  }

/=============
// The weight component
/=============
import React from "react"

const Weight = ({ incrementTotal, weight }) => {

    return (
        <div onClick={() => { incrementTotal(weight) }} >
          {weight}
        </div>
    )
}

export default Weight

This works fine. I'd like each Weight component to keep count of the number of times its been clicked. So if the 5kg weight component is clicked twice it will show a count of 2, along with updating the total in the desired fashion. So I added the following to my weight component:

import React, { useState } from "react"

const Weight = ({ incrementTotal, weight }) => {
    const [count, setCount] = useState(0)

    return (
        <div
            onClick={
                () => { 
                        incrementTotal(weight) 
                        setCount(count + 1)
                    }
                }
         >  
                {count > 0 && <span>{count}</span>}
                {weight}
        </div>
    )
}

export default Weight

This achieves the desired result of total being incremented correctly and the components count being updated each click.

However, now I need to change my reset button to reset each weight components count and I'm completely at a loss for how to approach this. Some things I've considered are forcing each Weight to Re-render, keeping the "counts" further up the hierarchy. Perhaps a "hook" is the wrong implementation here (this little app is an attempt to get comfortable with hook primarily). Ideal answers will include both how to approach the problem along with potential solutions.

notes: I'm not sure that the above code is perfect, I've tried to strip out the parts that were not relevant to the question. Below I've added what the weight data looks like in case anyone is curious.

export const weights = [
         {
           color: "red",
           kg: 25,
           lbs: 55,
         },
         {
           color: "blue",
           kg: 20,
           lbs: 45,
         },
// this goes on a while.

UPDATE

I've updated my weight component to look like this:

import React, { useState, useEffect } from "react"

const Weight = props => {
    const [count, setCount] = useState(0)

    useEffect(() => {
      setCount(0)
    }, [props.reset])

    return (
      <div
        onClick={() => {
          props.incrementTotal(props.weight)
          setCount(count + 1)
        }}
      >
        {count > 0 && <span>{count}</span>}
        {props.weight}
      </div>
    )
}

export default Weight

And added reset: 0 to my reset setState function. This hasn't solved my problem but does seem a promising direction per the React Hook Docs.

Update 2 I accepted the answer below because it was correct though I failed to understand it fully. In order to implement the below solution I had to update the "reset" value. I did this by passing.

I'm not sure this is a good solution. I believe it's re-rendering all weight components. Which potentially is bad, especially if there's a 1000 components that are complicated.

this.setState({
   reset: !this.state.reset
})

标签: reactjsreact-hooks

解决方案


You can pass a prop with different value from parent to Weight child components whenever you want to reset counters and use useEffect hook with dependency as that prop. e.g

class Parent extends React.Component {
  state = { reset: Date.now() };
  
  resetCounters = () => {
      this.setState({ reset: Date.now() });
  }
  render () {
    <div>
      <button onClick={this.resetCounters} >Reset</button>
      <Wieght reset={this.state.reset} {..some other props} />
      <Wieght reset={this.state.reset} {..some other props} />
      <Wieght reset={this.state.reset} {..some other props} />
      <Wieght reset={this.state.reset} {..some other props} />
    </div>
  }
}


const Weight = props => {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    setCounter(0);
  }, [props.reset]);

}

Another option is to pass reset as key to Weight component so, on changing the key, it will remount. This is a simple solution but can cause performance issues for large number of Weight components


推荐阅读