Mr. Pol Mr. Pol - 1 month ago 8
React JSX Question

Simple interactive React Tile Map

I'm struggling to achieve the simplest thing ever. I have this tile map:
screenshot

When clicking on a Tile I can get the index and from there the plan was to replace the color value and let State change the background of the Tile. Problem appears to be that all tiles with the same color change to black, not only the one that was clicked.

screenshot after clicking on a Land/Wheat Tile

Two components:

// Map.js

var mapData = [
1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1,0,1,1,1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,0,1,1,1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,0,1,1,1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,0,1,1,1,
1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0,0,1,1,1,
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,0,1,1,1,
]

var tileTypes = {
0: { name: 'Sea', color: 'lightBlue' },
1: { name: 'Land', color: 'wheat' },
2: { name: 'Ship', color: 'black' }
}

var temporalTiles=[];


export default class extends React.Component {


constructor(props) {

super(props)

this.state = {
tile: 0,
tiles:[]
}
}


componentDidMount() {


const numTiles = mapData.length;

for (let y = 0; y < numTiles; y++) {
const tileId = mapData[y]
const tileType = tileTypes[tileId]
temporalTiles.push(tileType);
this.setState({tiles: temporalTiles})
}

}



makeBlack() {
var idx= this.state.tile;

console.log(idx); // tile index
console.log(temporalTiles[idx].color); // current tile color

temporalTiles[idx].color = 'black'; // change color

console.log(temporalTiles[idx].color); // did it work ? yes(!)

this.setState({tiles: temporalTiles})

console.log(temporalTiles);
}


handleIndexToState(idx) {

this.setState({tile: idx})

}

render () {

var quickDemo ={
display:'block',
textAlign:'center'
}

return ( <div>

{this.state.tile ? (

<div>
<p style={quickDemo}> Index of clicked cell {this.state.tile}</p>
<p style={quickDemo}
onClick={this.makeBlack.bind(this)}>
Change to black
</p>
</div>
) : null
}

{this.state.tiles.map((tile,index) =>(

<Tile
bgcolor={tile.color}
key={index}
position={index}
onClick={this.handleIndexToState.bind(this, index)}
/>

))}

</div>)
}}


This is the parent component, the Tile component goes like this

// Tile.js


export default class extends React.Component {

render () {

var bgColor = {
backgroundColor: this.props.bgcolor ,
width: '83px',
height:'83px',
border:'1px solid rgba(0,0,0,.1)'

};

return (

<div
onClick={this.props.onClick}
style={bgColor}>

{this.props.position}

</div>


)
}
}


Any pointers on what I'm a missing ? I'm open to other strategies for Tile map 'mgmt' in react as I'm sure my approach to the issue is very naive.
TIA

UPDATE: The ultimate goal is to keep the color of each tile in the State so that I can do stuff with it, such as save positions to local storage for example.

Answer

You don't need to use states to change the color. Use event.target to get the clicked item and change the css directly.

Hope this helps!

var mapData = [
1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1,0,1,1,1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,0,1,1,1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,0,1,1,1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,0,1,1,1,
1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0,0,1,1,1,
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,0,1,1,1,  
]

var tileTypes = {
0: { name: 'Sea', color: 'lightBlue' },
1: { name: 'Land', color: 'wheat' },
2: { name: 'Ship', color: 'black' }
}

class Tiles extends React.Component{
  constructor(props){
    super(props)
    this.onClick = this.onClick.bind(this)
    this.state = {
      clickedIndex: []
    }
  }
  
  onClick(i){
    const index = this.state.clickedIndex.slice()
    if(index.indexOf(i) === -1){ //handle duplicates
      index.push(i)
      this.setState({clickedIndex: index})
     } 
  } 
  
  render() {
    console.log('clicked Index:', this.state.clickedIndex)
    const nodes = mapData.map((el, i) => {
      const bg = this.state.clickedIndex.indexOf(i) > -1 ? 'black' : tileTypes[el].color
      return <div className="box" onClick={() => this.onClick(i)} style={{background: bg}}>{i}</div>
    })
    return <div>{nodes}</div>
  }
}

ReactDOM.render(<Tiles/>, document.getElementById('app'))
.box{
  height: 40px;
  width: 40px;
  border: 1px solid grey;
  float: left;
  transition: all 0.2s ease;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>