DEV Community

André Gtz
André Gtz

Posted on

Cómo configuro un proyecto simple en React

Cookie Clicker App con React

Instalación

Para crear la aplicación se requiere instalar create-react-app.

$ yarn global add create-react-app
$ yarn create react-app cookie-clicker
$ cd cookie-clicker

Instalar eslint

eslint es la forma en la cual el IDE para desarrollar con javascript verifica si hay errores de sintaxis y se refuerza el uso de estilos populares ya aceptados.

yarn busca en los módulos instalados del proyecto eslint y lo ejecuta. No hay necesidad de instalarlo ya que create-react-app lo instala por su cuenta.

$ yarn eslint --init
yarn run v1.15.2
$ /.../cookie-clicker/node_modules/.bin/eslint --init
? How would you like to use ESLint? (Use arrow keys)
  To check syntax only 
  To check syntax and find problems 
> To check syntax, find problems, and enforce code style 

Seleccionar To check syntax, find problems, and enforce code style

? What type of modules does your project use? (Use arrow keys)
> JavaScript modules (import/export) 
  CommonJS (require/exports) 
  None of these

Seleccionar JavaScript modules (import/export)

? Which framework does your project use? (Use arrow keys)
> React 
  Vue.js 
  None of these 

Seleccionar React

? Where does your code run? (Press <space> to select, <a> to toggle all, <i> to invert selection)
>◉ Browser
 ◉ Node

Seleccionar ambos <a> <enter>

? How would you like to define a style for your project? (Use arrow keys)
> Use a popular style guide 
  Answer questions about your style 
  Inspect your JavaScript file(s) 

Seleccionar Use a popular style guide

? Which style guide do you want to follow? (Use arrow keys)
> Airbnb (https://github.com/airbnb/javascript) 
  Standard (https://github.com/standard/standard) 
  Google (https://github.com/google/eslint-config-google) 

Seleccionar Airbnb

? What format do you want your config file to be in? JavaScript

Checking peerDependencies of eslint-config-airbnb@latest
Local ESLint installation not found.
The config that you've selected requires the following dependencies:

eslint-plugin-react@^7.11.0 eslint-config-airbnb@latest eslint@^4.19.1 || ^5.3.0 eslint-plugin-import@^2.14.0 eslint-plugin-jsx-a11y@^6.1.1
? Would you like to install them now with npm? (Y/n) 

Como estamos usando yarn en lugar de npm le decimos que no, vamos a instalar estos paquetes manualmente usando yarn.

$ yarn add eslint-plugin-react@^7.11.0 eslint-config-airbnb@latest eslint-plugin-import@^2.14.0 eslint-plugin-jsx-a11y@^6.1.1 --dev

Asegurarse de agregar --dev al final, ya que solo se necesita durante el desarrollo del proyecto.

Además se tiene que instalar @babel/plugin-transform-runtime

$ yarn add @babel/plugin-transform-runtime --dev

Y se puede personalizar el archivo .eslintrc.js, para que se adecúe al estilo de cada equipo.

En este caso agregaremos:

{
    .
    .
    .
    parser: 'babel-eslint',
    rules: {
        'react/prop-types': [0,],
    },
}

Nota: Según el IDE que se utilice habrá que habilitar que lea el .eslintrc.js

Editores como VS Code ya lo traen integrado.

Ahora si abres el archivo src/App.js debería marcar un error diciendo que los archivos con jsx deberían tener una extensión .jsx en lugar de .js.

Crear el Layout de la aplicación

Utilizaremos material-ui como soporte para varios componentes, iconos y los estilos.

$ yarn add @material-ui/core

Modificar App.js por App.jsx.

Eliminar import App.css ya que no se utilizará de esta manera los estilos.

Crear 3 contentedores.

  1. Contenedor que contendrá la información de cuantas galletas tienes
  2. Contenedor con la imagen de la galleta
  3. Contenedor con lista de upgrades
import React, { Component } from 'react';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import logo from './logo.svg';

class App extends Component {
  state = {

  };

  render = () => (
    <div className="App">
      <div className="info">
        <Typography variant="subtitle1">
          Tienes X galletas.
        </Typography>
      </div>
      <div className="cookie">
        <img src={logo} alt="" />
      </div>
      <div className="upgrades">
        <Card className="card">
          <CardContent>
            <Typography className="" color="textSecondary" gutterBottom>
              +1 Cookie per click [30 cookies]
            </Typography>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

export default App;

Ahí utilizamos los componentes de material-ui Typography, Card y CardContent. Para más información sobre los componentes visitar la página de material-ui.

Si corres la aplicación usando

$ yarn start

Se puede observar que aún no tiene estilos más que lo poco que trae el componente de material-ui.

Para agregar los estilos, necesitamos utilizar withStyles que viene incluido en el paquete de material-ui.

import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import logo from './logo.svg';

const styles = {
  App: {
    height: '100%',
    width: '100%',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'flex-start',
  },
  info: {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  },
  cookie: {
    width: '100%',
    maxWidth: '500px',
  },
  upgrades: {
    width: '90%',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'flex-start',
    alignItems: 'center',
  },
  card: {
    minWidth: '100%',
  },
};

class App extends Component {
  state = {

  };

  render = () => {
    const { classes } = this.props;
    return (
      <div className={classes.App}>
        <div className={classes.info}>
          <Typography variant="subtitle1">
            Tienes X galletas.
          </Typography>
        </div>
        <div className={classes.cookie}>
          <img src={logo} alt="" />
        </div>
        <div className={classes.upgrades}>
          <Card className={classes.card}>
            <CardContent>
              <Typography color="textSecondary" gutterBottom>
                +1 Cookie per click [30 cookies]
              </Typography>
            </CardContent>
          </Card>
        </div>
      </div>
    );
  };
}

export default withStyles(styles)(App);

No es muy cómodo rellenar cada Upgrade manualmente, así que podemos hacer un archivo js para guardar y obtener los upgrades.

Creamos un archivo llamado upgrades.js

const upgrades = [
  {
    mejora: 1,
    costo: 30,
    actived: false,
  },
  {
    mejora: 2,
    costo: 100,
    actived: false,
  },
  {
    mejora: 3,
    costo: 200,
    actived: false,
  },
  {
    mejora: 4,
    costo: 300,
    actived: false,
  },
  {
    mejora: 5,
    costo: 600,
    actived: false,
  },
  {
    mejora: 6,
    costo: 800,
    actived: false,
  },
];

export default upgrades;

Y lo utilizamos dentro de App.js

import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import logo from './logo.svg';

// Importamos los upgrades
import UPGRADES from './upgrades';

const styles = {
  App: {
    height: '100%',
    width: '100%',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'flex-start',
  },
  info: {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  },
  cookie: {
    width: '100%',
    maxWidth: '500px',
  },
  upgrades: {
    width: '90%',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'flex-start',
    alignItems: 'center',
  },
  card: {
    minWidth: '100%',
  },
  // Nuevo estilo para mostrar si ya se activó un upgrade
  activedBg: {
    backgroundColor: 'greenyellow',
  },
};

class App extends Component {
  // Agregamos el estado de los upgrades, el cual vamos a modificar para
  // actualizar si ya se activó o aún no. 
  state = {
    upgrades: [],
  };

  // Es importante utilizar componentDidMount para cargar todos los datos
  // que se van a utilizar al renderizar el componente.
  // Si se necesita cargar la información antes de renderizar, si utiliza
  // componentWillMount
  componentDidMount = () => {
    // Cargamos upgrades al estado
    this.setState({ upgrades: UPGRADES });
  };

  render = () => {
    // Es una buena práctica descomponer el estado y los props
    const { classes } = this.props;
    const { upgrades } = this.state;
    return (
      <div className={classes.App}>
        <div className={classes.info}>
          <Typography variant="subtitle1">
            Tienes X galletas.
          </Typography>
        </div>
        <div className={classes.cookie}>
          <img src={logo} alt="" />
        </div>
        <div className={classes.upgrades}>
          {/* Mapeamos los upgrades para ponerlos en su Card*/}
          {upgrades.map(upgrade => (
            <Card className={classes.card}>
              <CardContent>
                <Typography
                  className={upgrade.actived ? classes.activedBg : ''}
                  color="textSecondary"
                >
                  {`+${upgrade.mejora} Cookie per click [${upgrade.costo} cookies]`}
                </Typography>
              </CardContent>
            </Card>
          ))}
        </div>
      </div>
    );
  };
}

export default withStyles(styles)(App);

Implementando Estados

  • Cuando se haga click en la galleta, aumentar el total de galletas por la cantidad adecuada.
  • Cuando se haga click en una mejora, aumentar la cantidad de galletas por click
  • Cuando se haga click en una mejora y se tiene cantidad suficiente de galletas, restar las galletas del total y aumentar el costo de la mejora.

A partir de esas necesidades podemos determinar un estado:

state = {
    upgrades: [],
    cookiesPerClick: 1,
    totalCookies: 0,
  };

El handler del click a la galleta

cookieClick = (amount) => {
  const { totalCookies } = this.state;
  this.setState({ totalCookies: (amount + totalCookies) });
};

El handler del upgrade

clickMejora = (upgrade) => {
  const { totalCookies, cookiesPerClick, upgrades } = this.state;
  if (totalCookies >= upgrade.costo) {
    // findIndex es un método de los arreglos, si la condición es true, regresa el index
    const upgradeIndex = upgrades.findIndex(up => up.mejora === upgrade.mejora);
    const newCosto = Math.round(upgrade.costo * 1.15);
    // Probar que pasa si se hace: 
    // upgrades[upgradeIndex].costo = newCosto;
    upgrades[upgradeIndex] = {
      ...upgrades[upgradeIndex],
      costo: newCosto,
    };
    this.setState({
      totalCookies: (totalCookies - upgrade.costo),
      cookiesPerClick: (cookiesPerClick + upgrade.mejora),
      upgrades,
    });
  }
};

Se agregan los eventos onClick

render = () => {
    const { classes } = this.props;
    const { upgrades, totalCookies, cookiesPerClick } = this.state;
    return (
      <div className={classes.App}>
        <div className={classes.info}>
          <Typography variant="subtitle1">
            {`Tienes ${totalCookies} galletas. Ratio: ${cookiesPerClick}`}
          </Typography>
        </div>
        <div
          className={classes.cookie}
          onClick={() => this.cookieClick(cookiesPerClick)}
          onKeyPress={() => {}}
          role="button"
          tabIndex="0"
        >
          <img src={logo} alt="" />
        </div>
        <div className={classes.upgrades}>
          {upgrades.map(upgrade => (
            <Card
              className={classes.card}
              key={upgrade.mejora}
              onClick={() => this.clickMejora(upgrade)}
            >
              <CardContent>
                <Typography
                  className={upgrade.actived ? classes.activedBg : ''}
                  color="textSecondary"
                >
                  {`+${upgrade.mejora} Cookie per click [${upgrade.costo} cookies]`}
                </Typography>
              </CardContent>
            </Card>
          ))}
        </div>
      </div>
    );
  };

Latest comments (0)