ReactJS - app functions in different files

873 Views Asked by At

I'm trying to make React-based web game. I have an App component which holds pretty much all non-UX state. To avoid code duplication I also hold most functions in it and pass it down as prop to child components.

But now I'm starting to get cluttered by different functions, all in the App body. Is there any simple way to satisfactory structure this in different files? Should I already look into state management libraries?

Currently stuff looks like:

    class App extends Component {
  constructor(props) {
    super(props);
    this.state = gameInitialize();
    this.modifyState = this.modifyState.bind(this);
    this.moveUnit = this.moveUnit.bind(this);
    this.progressMission = this.progressMission.bind(this);
    this.timeJump = this.timeJump.bind(this);
    this.competenceAfterTimeJump = this.competenceAfterTimeJump.bind(this);
    this.save = this.save.bind(this);
    this.load = this.load.bind(this);
  }
  componentDidMount() {
    this.timerID = setInterval(this.modifyState, this.state.interval);
    window.addEventListener('beforeunload', this.save);
    this.load();
  }
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
  save() {
    localStorage.setItem("gameSave", toJson(this.state));
  }
  load() {
    let state = 0;
    try {
      state = fromJson(localStorage.getItem("gameSave"));
    } catch (error) {
      console.log(error);
      return 0;
    }
    state.units.map(unit => {
      delete unit.__parent;
      delete unit.attributes.__parent
      return 0;
    });
    state.missions.map(mission => delete mission.__parent);
    this.setState(state);
  }
  modifyState() {
    this.setState(this.state.units.map(this.progressMission));
    this.setState(this.state);
  }
  progressMission(unit) {
    const mission = unit.currentMission;
    let increment = unit.attributes[mission.type].total() - mission.complexity;
    if (increment < 0) increment = 0;
    mission.progress += increment * this.state.interval / 1000 * unit.competence / 10;
    if (mission.progress >= mission.difficulty) {
      mission.progress = 0;
      this.state.experience.get(mission.reward);
      mission.completions += 1;
    }
  }
  moveUnit(unit, mission) {
    unit.currentMission = mission;
    this.setState(this.state);
  }
  timeJump() {
    const game = this.state;
    while (game.units.length > 2) {
      game.units.pop();
    };
    game.units.map(function (unit) {
      Object.keys(unit.attributes).map((key) => { unit.attributes[key] = newAttribute() });
      unit.currentMission = game.missions[0];
    });
    game.missions.map((mission) => {mission.progress = 0});
    game.units[0].competence = this.competenceAfterTimeJump();
    game.experience.current = 0;
    this.setState(game);
  }
  competenceAfterTimeJump() {
    return (10 + Math.sqrt(this.state.experience.total) / 10);
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1 className="title">Time-traveling Hero: eventually I'll save the world, or maybe not if I don't feel it</h1>
        </header>
        <SaveLoad game={this} />
        <Prestige game={this} />
        <MissionWrapper>
          <MissionList missions={this.state.missions} game={this} />
        </MissionWrapper>
        <UnitWrapper>
          <ExpWrapper>
            <div>
              Available Experience: {this.state.experience.current.toFixed(1)}
            </div>
            <div>
              Total Experience: {this.state.experience.total.toFixed(1)}
            </div>
          </ExpWrapper>
          <UnitList units={this.state.units} game={this} />
        </UnitWrapper>
      </div>
    );
  }
}

function gameInitialize() {
  let game = { units: [], missions: [], currentUnit: undefined };

  game.interval = 10;

  game.missions = generateMissions(50);

  game.experience = {
    current: 0, total: 0,
    get: function (amount) { this.current += amount; this.total += amount },
    spend: function (amount) {
      if (this.current >= amount) {
        this.current -= amount;
        return true;
      }
      else return false;
    }
  };

  game.units.push({ name: "Hero", attributes: newAttributes(), competence: 10, currentMission: game.missions[0] });
  game.units.push({ name: "Childhood Friend", attributes: newAttributes(), competence: 15, currentMission: game.missions[0] });

  game.currentUnit = game.units[0];

  game.missionsWithUnits = function () {
    this.missions.map()
  }
  return game;
}

How should I proceed?

1

There are 1 best solutions below

0
On

Yes, it's super easy to organize JS code! Use modules. Here's how to do it.

  1. Export functions from a file

adders.js:

export function addTwo (number) {
  return number + 2
}
  1. Then use it:

This could be in a component file:

import { addTwo } from './path/to/adders.js'
console.log(addTwo(5)) // logs 7

You can organize this super well for a lot of things. If you have a group of related functions, use a module like this. here's the file structure:

mathStuff/
  adders.js
  index.js

You have all of your related files in the same folder and your functions exported from the individual files like above. Then set up index like this:

index.js:

import * as adders from './adders.js'
// Set up your object however you want. 
const MathStuff = {
  ...adders
}
export default MathStuff

Then in any component you can do this:

import MathStuff from './path/to/mathStuff'

MathStuff.addTwo(7) // 9

For even more organization, you could set your index up to have functions like this:

index.js:

import * as adders from './adders.js'
import * as dividers from './dividers.js' // another math file with division functions or something

// Set up your object however you want. 
const MathStuff = {
  adders,
  dividers
}
export default MathStuff

And use it like this:

import MathStuff from './path/to/mathStuff' // points to directory, NOT individual file

MathStuff.adders.addTwo(7) // 9

I would definitely suggest organizing code like this. One thing this improves is testability - it's very easy to test pure functions with no side effects.

I like to put my database code in one module and import it wherever to access all my database functions.

I like to put all of my business logic in different modules by category - for instance GameLogic or something like that.

This will also help you write more functional code. Currently, you have a lot of state modification within individual functions - you won't be able to do that in modules without binding individual functions to the this context of your react component. Instead, I would suggest passing all necessary parameters to the function and having it return a value. This moves business logic away, making it easier to manage state.

For instance, your progressMission function accesses this.state.interval. You can pass interval to the function itself.

One thing I'm noticing is that your code has a lot of dependency on each other - functions often have to access lots of things outside of itself, rather than being self-contained. It would probably help you a lot to try to refactor into a modular system, where functions are much more pure - only accessing what is passed to them, and returning values which get used. Using actual modules like above definitely helps do that - my code got better the more I did it. It helps you reason about your code better. Additionally, once/if you start implementing tests, you'll find that all of the tangled-ness of the code makes it hard to test - there are a lot of side effects.

Finally, redux and external state management probably won't help a ton in your case, but they might. Redux can help you achieve state that's easier to reason about, but it won't help you organize code better per se. I hope that helps!