Cómo configuro un proyecto simple en React
Tags: spanish, español, react, eslint
Published: 2019-04-30 01:20:24

---
title: Cómo configuro un proyecto simple en React
published: true
description: Mi preferencia personal para crear y configurar proyectos de React, con ESLint, Create-React-App, Estilos con material-ui y withStyles.
tags: Spanish, Español, React, eslint
---

# Cookie Clicker App con React

## Instalación

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

```bash
$ 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.
```bash
$ 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`

```bash
? 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)`

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

Seleccionar `React`

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

Seleccionar ambos `<a> <enter>`

```bash
? 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`

```bash
? 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

```bash
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`.

```bash
$ 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`

```bash
$ 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:

```js
{
    .
    .
    .
    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](https://material-ui.com/) como soporte para varios componentes, iconos y los estilos.

```bash
$ 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

```jsx
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
```bash
$ 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.

```jsx
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`

```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`

```jsx
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:
```js
state = {
    upgrades: [],
    cookiesPerClick: 1,
    totalCookies: 0,
  };
```

El handler del click a la galleta
```js
cookieClick = (amount) => {
  const { totalCookies } = this.state;
  this.setState({ totalCookies: (amount + totalCookies) });
};
```
El handler del upgrade
```js
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
```jsx
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>
    );
  };
```