2

I am new to React and wanted to write a fun project to get a hang of the language. I wanted to write a bubble blowing app, like so I wrote the following JSFiddle:

I am rendering an array of bubble objects stored in the following manner:

history =
 [ { //first element is empty placeholder
    diameter: 0,
    x: 0,
    y: 0
  }, 
  { 
    diameter: 40,
    x: 158,
    y: 122
  },
  { 
    diameter: 86,
    x: 274,
    y: 214
  },
  { 
    diameter: 26,
    x: 158,
    y: 244
  },
]

This exact array will produce the following result:

enter image description here

Now, after mocking this up, I got the crazy idea to add some gravity and bouncing. I wanted the bubbles to bounce like in this JSFiddle:

However, the only way I got the moving bubbles to work was by storing the information directly in state, as in the code below. (vy is the vertical speed of the ball.)

class Box extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        diameter: 0,
        x: 0,
        y: 0,
        vy: 0,
        clicked: false,
    };
}

I use the following code to update the state of box in order to simulate the bouncing of the bubble:

fall(i){
    this.setState({
        y: this.state.y + i,
        vy: this.state.vy + .01,
    });
}

fallWrap(){
    this.fall(this.state.vy);
    this.u = setTimeout(this.fallWrap.bind(this), 1);



    if(this.state.y >= 400 - (this.state.diameter/2)){
        this.setState({
            y: 400 - (this.state.diameter/2),
            vy: -.9 * this.state.vy,
        }, function(){
            if(Math.abs(this.state.vy) < .2){
                clearTimeout(this.u);
            }
        });
    }

}

What I want to do is to store an array of multiple bubbles, like in the first JSfiddle, and have all the bubbles bounce around in the box like the second JS fiddle. This means updating value vy of multiple objects in the history array concurrently. I hope I have adequately described my needs. My central question is how can I use setstate to update a single value (vy) within each object stored within an array? For example, if I have

history =
 [ { 
    diameter: 0,
    x: 0,
    y: 0,
    vy: 0
  }, 
  { 
    diameter: 40,
    x: 158,
    y: 122,
    vy: .5 //update this
  },
  { 
    diameter: 86,
    x: 274,
    y: 214,
    vy: .4 //update this
  },
  { 
    diameter: 26,
    x: 158,
    y: 244
    vy: .23 //update this
  },
]

Then how I implement setState in a way that updates each vy? I know in React, state arrays should be immutable, so I am wondering if there is an established way to do this. The map function seems promising, but I don't know how to apply it here.

EDIT: Thanks everyone, I ended up solving my problem with Haken's solution. Here is a link to the JSFiddle

3
  • You don't; you get the current state, you modify it, you set it back to the state. You could update any particular bubble and slice everything back together as you go, but I wouldn't bother. Commented Oct 16, 2018 at 21:21
  • I don't mind updating them all at the same time. For instance making a copy and modifying all the vy values and then setting it as the state. Hmm maybe that would work. Commented Oct 16, 2018 at 21:26
  • You don't need to make an explicit copy, e.g., see Håken's answer. Commented Oct 16, 2018 at 21:29

3 Answers 3

3

You can pass an updater function to setState. Here's an example of how this might work.

The object returned from the updater function will be merged into the previous state.

const updateBubble = ({y, vy, ...props}) => ({y: y + vy, vy: vy + 0.1, ...props})

this.setState(state => ({bubbles: state.bubbles.map(updateBubble)}))

Change the updateBubble function to add bouncing and so on.

Sign up to request clarification or add additional context in comments.

2 Comments

This may be just what I'm looking for. I will try it out!
Thanks Haken! The balls bounce ad infinitum right now but I think I can fix that. here is the final.
2

You can do this with map indeed. Here's how I would have done it.

One liner

this.setState({bubbles: this.state.bubbles.map(x => ({...x, vy: x.vy + 1})})

More explicit

this.state = {bubbles: history}

this.setState({ bubbles: this.state.bubbles.map(x => { 

    // Option 1 - more verbose
    let newBubble = x; // or even let newBubble = {...x}
    newBubble.vy = x.vy + 1;

    // Option 2 - directly update x
    x.vy = x.vy + 1

    // Then
    return x;
})})

1 Comment

Thanks Asten, Upvoted for expanding on Haken's answer and explaining the updater function more thoroughly.
0

I would sugest you should change your approach. You should only manage the state as the first you showed, and then, in another component, manage multiple times the component you currently have.

You could use something like this:

import React from 'react'
import Box from './box'

export default class Boxes extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 
            started:[false,false,false,false] /*these flags will be changed on your "onClick" event*/
        } 
    }
    render() {
        const {started} = this.state
        return(
            <div>
              {started[0] && <Box />}
              {started[1] && <Box />}
              {started[2] && <Box />}
              {started[3] && <Box />}
            </div>
        )
    }
}

1 Comment

Do you think you can elaborate on this some? By the way, I am looking for multiple bubbles, not boxes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.