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)