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.
- Contenedor que contendrá la información de cuantas galletas tienes
- Contenedor con la imagen de la galleta
- 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>
);
};
Top comments (0)