How to increment a value in one sibling component from another on click?

Viewed 62
function LeagueBadge(props) {
    return (
        <img src={props.badgeUrl} alt="missing alt text" />
    );
}

class LeagueInfo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            amountOfPlayers: null,
            rpPerSecond: null,
            rpCost: null,
        };
    }
    render() {
        return (
            <div>
                <h4>{this.props.name} players: {this.props.amountOfPlayers}</h4>
                <h4>RP per second: {this.props.rpPerSecond}</h4>
                <h4>RP cost: {this.props.rpCost}</h4>
            </div>
        );
    }
}

class League extends Component {
    render() {
        return (
            <div>
                <LeagueBadge badgeUrl={this.props.badge} />
                <LeagueInfo name={this.props.name} />
            </div>
        );
    }
}

class App extends Component {
    render() {
        return (
            <div className="App">
                <h1>Players</h1>
                <League name="Bronze" badge={ require('./bronze.png') }></League>
                <League name="Silver" badge={ require('./silver.png') }></League>
                <League name="Gold" badge={ require('./gold.png') }></League>
                <League name="Platinum" badge={ require('./platinum.png') }></League>
                <League name="Diamond" badge={ require('./diamond.png') }></League>
                <League name="Master" badge={ require('./master.png') }></League>
                <League name="Challenger" badge={ require('./challenger.png') }></League>
            </div>
        );
    }
}

I want to be able to click at the image which is the LeagueBadge component and increment the value of amountOfPlayers in their sibling LeagueInfo. I've googled react siblings communication already and only found examples with input tag and onChange but here I want img tag or button and onClick.

2 Answers

You could lift the state for amountOfPlayers up into your <Leauge /> component, so that: - updates can be triggered from <LeagueBadge /> - and, that state can be passed down to your <LeagueInfo /> component

This would allow you to share and synchronize state between the <LeagueInfo /> and <LeagueBadge /> siblings as you require.

To do this, you would need to add an onClick callback to <LeagueBadge /> which is fired when the img element is clicked. In the <Leauge /> render method, you could provide the logic that increments the amountOfPlayers state in <Leauge />. When the amountOfPlayers is incremented, and setState is called (in <Leauge />), this would in turn cause your <Leauge /> component to re-render itself (and children/siblings). Because the updated value for amountOfPlayers is passed as a prop to <LeagueInfo /> component, this updated value would be rendered in <LeagueInfo />- effectively achieving what you're after.

Something like this might work for you:

class LeagueBadge extends Component {
    render() {

    // Add props.onClick callback to trigger click event in <League /> 
    // component
    return (
        <img src={this.props.badgeUrl} alt="missing alt text" 
             onClick={() => this.props.onClick()} />
    );
    }
}

class LeagueInfo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            // amountOfPlayers: null, // This is not needed, as it's supplied by props
            rpPerSecond: null,
            rpCost: null,
        };
    }
    render() {
        return (
            <div>
                <h4>{this.props.name} players: {this.props.amountOfPlayers}</h4>
                <h4>RP per second: {this.props.rpPerSecond}</h4>
                <h4>RP cost: {this.props.rpCost}</h4>
            </div>
        );
    }
}

class League extends Component {

    componentWillMount() {

        this.setState({
            amountOfPlayers : 0
        })
    }

    render() {

        // Locally defined function that increments amountOfPlayers and
        // updates state
        const incrementAmountOfPlayers  = () => {
            this.setState({ amountOfPlayers : 
            this.state.amountOfPlayers + 1 })
        }

        return (
            <div>
                <LeagueBadge badgeUrl={this.props.badge} 
                             onClick={ () => incrementAmountOfPlayers() } />
                <LeagueInfo name={this.props.name} amountOfPlayers={ this.state.amountOfPlayers } />
            </div>
        );
    }
}

Keep your state in the league component and pass the function responsible to change it to LeagueBadge like:

class League extends Component {
    constructor(props) {
        super(props);
        this.state = {
            amountOfPlayers: null,
        };
    }
    handleClick = () => {
    this.setState(prevState => {
       return {amountOfPlayers: prevState.amountOfPlayers + 1}
    })
  }
    render() {
        return (
            <div>
                <LeagueBadge badgeUrl={this.props.badge} incrementPlayers={this.handleClick}/>
                <LeagueInfo name={this.props.name} amountOfPlayers={this.state.amountOfPlayers}/>
            </div>
        );
    }
}


function LeagueBadge(props) {
    return (
        <img src={props.badgeUrl} alt="missing alt text" onClick={this.props.incrementPlayers}/>
    );
}

use this.props.amountOfPlayers in your Info component.

Related