2

I have a block of code like this:

    randomArray(arrayOfImage){
        //algorithm goes here
        return shuffledArray;
}

    shuffle(){       
        this.randomArray(images);
        this.forceUpdate();
} 

    render(){
        let array = this.randomArray(images);       //images is a declared array
        let squares = array.map((item,index) => <Squares/> )

        return(
            <div>
                <div id='square-container'>
                    {squares}
                </div>
                <button className='btn' onClick = {this.shuffle.bind(this)}>Shuffle</button>
            </div>
        )
    }

Basically, I have an array images declared. The function randomArray() return a shuffled version of images. And then, for each of the item inside the shuffled array array the browser will render a div with the given props.

Now I have a button so that the user can shuffle the array themselves. Here, I use forceupdate() because even though the array is shuffled when the button is clicked, the DOM won't update because there is no changes in the state.

It works! But since using forceupdate() is not encouraged, how should I make this less... say, amateur?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
KyoKatarz
  • 101
  • 1
  • 5
  • 1
    Does it return a shuffled array, or does it shuffle the provided array in-place? Based on the usage in shuffle, it seems like the latter. Why not make the array part of the state, and actually *create a new one* to update the state with when shuffling? – jonrsharpe May 18 '20 at 22:24
  • Yeah, use local state, or some form of state library – Icepickle May 18 '20 at 22:25
  • So you are saying that I SHOULD use array as part of the state? I've always thought it's a bad way to code :D – KyoKatarz May 18 '20 at 22:51
  • It depends :) It doesn't mean that your data is static, you could have a load event that fills up the state (after which it would also re-render) or the data comes from props above but you are only interested in displaying / shuffling the array you have been given from a parent (so you might as well just shuffle indexes) – Icepickle May 18 '20 at 23:06

1 Answers1

1

Sure, React will render an element again as soon as it's state gets changed, or it receives new props from it's parent component (or a state library).

You can read more about how react handles the rendering in their documentation

So, to handle this, just create a new array based on your original one, and then set it to the state again.

In the olden days, you would require a class for setting the state on component level

const target = document.getElementById('container');

class ArrayOfImages extends React.Component {
  constructor() {
    // don't forget to call super
    super();
    // set the initial state
    this.state = {
      images: [
      'https://c402277.ssl.cf1.rackcdn.com/photos/18357/images/featured_story/Medium_WW252652.jpg?1576698319',
      'https://c402277.ssl.cf1.rackcdn.com/photos/882/images/circle/African_Elephant_7.27.2012_hero_and_circle_HI_53941.jpg?1345532748',
      'https://c402277.ssl.cf1.rackcdn.com/photos/1732/images/circle/Asian_Elephant_8.13.2012_Hero_And_Circle_HI_247511.jpg?1345551842'
      ]
    };
    // bind the function we are going to call from the render function
    this.shuffle = this.shuffle.bind( this );
  }
  shuffle() {
    // create a shallow copy of the state object
    const copyOfState = this.state.images.slice();
    // shuffle it a bit (Durstenfeld shuffle: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm)
    for (let i = copyOfState.length; i--;) {
      let target = Math.floor( Math.random() * (i+1) );
      [copyOfState[i], copyOfState[target]] = [copyOfState[target], copyOfState[i]];
    }
    // update the existing state with the new array
    this.setState({ images: copyOfState });
  }
  render() {
    const { images } = this.state;
    return (
      <div>
        <h1>Some elephants</h1>
        { images.map( img => <img key={ img } alt={ img } src={ img } /> ) }
        <div><button type="button" onClick={ this.shuffle }>Shuffle images</button></div>
      </div>
    );
  }
}

ReactDOM.render( <ArrayOfImages />, target );
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<div id="container"></div>

But now, you could do it by using the useState helper for that

// this would be an import in normal code
const { useState } = React;

const target = document.getElementById('container');

const shuffle = arr => {
  // create a shallow copy of the array
  const copy = arr.slice();
  // shuffle it a bit (Durstenfeld shuffle: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle#The_modern_algorithm)
  for (let i = copy.length; i--;) {
    let target = Math.floor( Math.random() * (i+1) );
    [copy[i], copy[target]] = [copy[target], copy[i]];
  }
  return copy;
}

const ArrayOfImages = () => {
  const [images, updateImages] = useState([
    'https://c402277.ssl.cf1.rackcdn.com/photos/18357/images/featured_story/Medium_WW252652.jpg?1576698319',
    'https://c402277.ssl.cf1.rackcdn.com/photos/882/images/circle/African_Elephant_7.27.2012_hero_and_circle_HI_53941.jpg?1345532748',
    'https://c402277.ssl.cf1.rackcdn.com/photos/1732/images/circle/Asian_Elephant_8.13.2012_Hero_And_Circle_HI_247511.jpg?1345551842'
  ]);
  return (
    <div>
      <h1>Some elephants</h1>
      { images.map( img => <img key={ img } alt={ img } src={ img } /> ) }
      <div><button type="button" onClick={ () => updateImages( shuffle( images ) ) }>Shuffle images</button></div>
    </div>
  );
}

ReactDOM.render( <ArrayOfImages />, target );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="container"></div>

The shuffle algorithm comes from this answer

Icepickle
  • 12,689
  • 3
  • 34
  • 48