Simple React and Flux application to heal a player

Posted on

Problem

I’m just trying to learn the basics of React and Flux, and the above code works and I can see it works nicely.

index.js

import React from 'react'
import ReactDOM from 'react-dom'

import UI from './components/ui.js'

ReactDOM.render(<UI />, document.getElementById('ui'));

component/ui.js

import React, { Component } from 'react'
import Dispatcher from '../dispatcher/dispatcher'
import PlayerStore from '../stores/playerstore'
import PlayerActions from '../actions/playeractions'
import { ActionTypes } from '../constants/actions'

export default class UI extends Component {

    constructor(props, context) {
        super(props, context)

        // keep contexts
        this.onPlayerStoreChange = this.onPlayerStoreChange.bind(this)

        this.state = {
            health: PlayerStore.health
        }
    }

    componentDidMount() {

        // listen for events that are broadcasted by the stores
        PlayerStore.on('change', this.onPlayerStoreChange)
    }

    componentWillUnmount() {
        PlayerStore.off('change', this.onPlayerStoreChange)
    }

    onPlayerStoreChange() {

        // request the new data needed via the stores' public getter methods
        const health = PlayerStore.health

        // then call its own setState() or forceUpdate() method
        // causing its render() method and the render() method of all its descendants to run.
        this.setState({
            health
        })
    }

    healPlayer() {

        // the dispatcher exposes a method that allows us to trigger a dispatch to the stores,
        // and to include a payload of data, which we call an action
        PlayerActions.healPlayer();
    }

    render() {
        return (
            <div>
                <h2>UI</h2>
                <p>hello gamemakers</p>
                <p>health: { this.state.health }</p>
                <p onClick={ this.healPlayer }><u>add</u></p>
            </div>
        )
    }
}

stores/playerstore.js

import { EventEmitter } from 'events'
import Dispatcher from '../dispatcher/dispatcher.js'
import { ActionTypes } from '../constants/actions'

const state = {
    health: 100,
    fatigue: 100
}

const stat = {
    strength: 1, // affects rof and damage
    agility: 1, // affects speed
    endurance: 1 // affects rate of fatigue
}

class PlayerStore extends EventEmitter {

    constructor() {
        super()

        // register this store with the dispatcher
        this.dispatchToken = Dispatcher.register(this.onAction.bind(this))
    }

    onAction(payload) {

        // provide hooks to each respective action
        switch(payload.type) {
            case ActionTypes.PLAYER_HEALED:
                this.increaseHealth()
                break
            case ActionTypes.PLAYER_INJURED:
                this.iHaveNoMadeThisBitYet()
                break
        }
    }

    get health() {
        return state.health
    }

    increaseHealth() {
        state.health += 6

        // broadcast an event declaring that the state has changed
        // so that whoever is listening to this store can update themself
        this.emit('change')
    }
}

module.exports = new PlayerStore();

dispatcher/dispatcher.js

const Dispatcher = require('flux').Dispatcher;

module.exports = new Dispatcher();

actions/playeractions.js

import { ActionTypes } from '../constants/actions'
import Dispatcher from '../dispatcher/dispatcher'

export default {

    healPlayer: function() {
        Dispatcher.dispatch({
            type: ActionTypes.PLAYER_HEALED
        })
    }

}

Here are my points:

  1. It feels weird to have my variables defined above my class and use public getters and so on to retrieve the values (instead of defining it on the constructor. Is there a benefit to this? It looks cleaner but it feels odd.
  2. Have I used import correctly? I want to share the Dispatcher across multiple files so I use import in those files. Will both of those files will contain the reference to the same dispatcher ‘object’?
  3. Why do you often see that people take their actions out of the component and into an actions file? Is it to share actions between components?
  4. Shouldn’t I just bind the actions straight onto the elements? In the UI I bind healPlayer which triggers an action. Is it okay just to bind the action?
  5. And following this, couldn’t I also just dispatch the event through the Dispatcher in healPlayer instead of dispatching it in the actions/playeractions.js file?

Solution

  1. Stores are just storages of data. You can implement it in any way you want.

  2. Yes and no. Yes, you’re using import correctly. No, you don’t need Dispatcher on the component since you have action files.

  3. The dispatcher is, well… a dispatcher. It marshals calls and route them to stores. On the other end, stores should only manipulate state and nothing more. There’s no room for logic and feedback.

    Action files serve as an API layer. If the action isn’t supported, the function won’t be present and calling an undefined function throws an error. You can also serve deprecation notices, route calls to other actions, do complex actions, backwards compatibility logic, etc. Also works well for IDEs that have autocomplete.

  4. Yes

  5. Yes, but refer to #3 for the benefits of having an action file.

In general, the code looks ok. The idea of flux and react is to have a clear view of the data flow and logic. I wouldn’t be surprised with the amount of code.

Leave a Reply

Your email address will not be published. Required fields are marked *