Search code examples
javascriptreactjsreduxreact-reduxconways-game-of-life

Game of Life using React-Redux


Currently I am working on Building Game of Life in FCC and I thought this would be a great project to test my knowledge on React-Redux.

Since I am a new to Redux, I am having difficulty to understand what is a dumb component vs a smart component in this project.

In my view, this is how I see:

NOTE: when I say "Smart Component" vs "Dumb Component" I simply mean that to be a "Smart Component" it is a container, or something that ties with the app's state, where as a "Dumb Component" simply means it's job is to simply render whatever it's being fed on.

If my assumption is correct, then I am having hard time to setup the app state correctly on the cells.

Here's what I've written so far:

component/board.js

import React, { Component } from 'react';
import Cell from '../containers/cell'

export default class Board extends Component {
  constructor(props){
    super(props);
    this.state = {height: 18, width: 40}
    this.test = this.test.bind(this)
  }
  test(){
    console.log(this.state)
  }
  render(){
    let rows = [];
    for (var i = 0; i < this.state.height; i++){
      let rowID = `row${i}`
      let bin = []
      for (var idx = 0; idx < this.state.width; idx++){
        let cellID = `cell${i}-${idx}`
        bin.push(<Cell key={cellID} id={cellID}/>)
      }
      rows.push(<tr key={i} id={rowID}>{bin}</tr>)
    }
    return(
      <div className="container">
        <div className="row">
          <div className="col s12 board"></div>
            <table id="simple-board">
               <tbody>
                 {rows}
               </tbody>
             </table>
          </div>
      </div>
    )
  }
}

container/cell.js

import React, { Component } from 'react';

// import from '../actions/index';
// import { connect } from 'react-redux';
// import { bindActionCreators } from 'redux';


export default class Cell extends Component{
  constructor(props){
    super(props);
    this.state = {
      color: 'dead'
    };
    this.test = this.test.bind(this);
  }

  test(){
    this.state.color === 'dead' ? this.setState({color:'alive'}) : this.setState({color:'dead'})
  }

  render(){
    return (
      <td
        className={this.state.color}
        key={this.props.cellID}
        id={this.props.cellID}
        onClick={this.test}>
      </td>
    )
  }
}

// function mapDispatchToProps(dispatch){
//   return bindActionCreators({}, dispatch)
// }

// export default connect(null,mapDispatchToProps)()

I'm not sure how I can approach at this point. At first I thought about having to wrap all the cells in an array and that would be the app's state, but I am not sure how to go on about that.

Any response will be greatly appreciated


Solution

  • I would suggest that you make your hierarchy like this. I'm going to represent the component hierarchy as a JSON-like syntax just so that you can understand:

    App (smart) {
      dispatchProps: {
        ...gameControlActions, // gets passed into GameControls
        onCellClick, // gets passed down to Board, and then to each Cell
      }
      stateProps: {
        cells: [{
          id: #
          status: 'dead', // or 'alive'
        }], // gets passed down to Board, to render cells
        ...generationData, // gets passed down to Generation
      }
    }
      // Children of App
      GameControls (dumb) // start, clear, randomize
      Board (dumb)
        Cells (dumb)
      Generation (dumb)
    

    When you render the Cells, you would render them like this:

    <Cell key={cellID} id={cellID} status={status} onClick={this.props.onCellClick} />
    

    Inside the Cell component the render function would look like this and you can now get rid of state:

    render(){
      return (
        <td
          className={this.props.status}
          id={this.props.id}
          onClick={this.props.onClick.bind(null, this.props.id)}>
        </td>
      )
    }
    

    Your onCellClick action would simply look like this:

    function onCellClick(cellId) {
      return {
        type: 'CELL_CLICK',
        payload: cellId,
      };
    }
    

    You would then process that action in your reducer by toggling the status of that cell in your cells array (remember to return a new copy of cells). Also, and if necessary, you might need to make your Cell component extend PureComponent, in order to speed up the reconciliation process or to implement shouldComponentUpdate.

    Let me know if this doesn't make sense, or if you have questions (I think you will).