{"id":15317621,"url":"https://github.com/tonymtz/react-workshop-second","last_synced_at":"2025-10-09T03:30:53.543Z","repository":{"id":68024081,"uuid":"46529361","full_name":"tonymtz/react-workshop-second","owner":"tonymtz","description":"ReactJS workshop","archived":false,"fork":true,"pushed_at":"2015-11-12T23:27:44.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-27T01:33:07.916Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"ryanez/react-workshop-second","license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonymtz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-20T00:43:06.000Z","updated_at":"2015-11-20T00:43:07.000Z","dependencies_parsed_at":"2023-03-09T19:30:17.588Z","dependency_job_id":null,"html_url":"https://github.com/tonymtz/react-workshop-second","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tonymtz/react-workshop-second","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymtz%2Freact-workshop-second","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymtz%2Freact-workshop-second/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymtz%2Freact-workshop-second/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymtz%2Freact-workshop-second/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonymtz","download_url":"https://codeload.github.com/tonymtz/react-workshop-second/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymtz%2Freact-workshop-second/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000716,"owners_count":26082911,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T08:57:53.875Z","updated_at":"2025-10-09T03:30:53.216Z","avatar_url":"https://github.com/tonymtz.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ReactJS workshop \n\nI'm assuming you already went throgh [first workshop](https://github.com/ryanez/react-workshop-first)\n\n# The setup\n\n- Create a directory for the project and then run `npm init`, follow your instincts to answer the wizard's questions.\n- Install React and its dependencies `npm install react react-dom babelify --save-dev`\n\n**Note**: At the moment of writing this tutorial the current version of react/react-dom/babelify are:\n* \"babelify\": \"^7.2.0\",\n* \"react\": \"^0.14.2\",\n* \"react-dom\": \"^0.14.2\"\n\n\n# Index file\n\n- On root directory create `.\\ui` \u0026 `.\\public` directories.\n- Create the index file `.\\ui\\index.js`\n\n```Javascript\n'use strict';\n\nvar React = require('react'),\n    ReactDOM = require('react-dom');\n\n// designed to be called once document is loaded.\nmodule.exports = function(elementId) {\n    var element = global.document.getElementById(elementId);\n\n    ReactDOM.render(React.createElement('div', null, 'here we will initialize the awesome'), element);\n};\n```\n\n# Browserify\n\nYou must have globaly installed browserify at this point (if you don't `npm install -g browserify`), so let's bundle our index file.\n\n`browserify ui/index.js -o public/game.js -s rockandroll`\n\n# Index.html\n\nLets create a HTML file that renders our React Application, create `index.html` on root directory.\n\n```Html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003ctitle\u003eReactJS Second Workshop\u003c/title\u003e\n    \u003cscript type=\"text/javascript\" src=\"public/game.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n\u003cbody onload=\"rockandroll('container')\"\u003e\n    \u003cdiv id=\"container\"\u003eHello world!\u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Note:** `rockandroll` is the `-s rockandroll` parameter we set while browserifying and we call `rockandroll('container')` because it's the elment Id.\n\nNow we can open `index.html` on browser and see how the \"Hello world!\" is replaced by our ReactJS component.\n\n# Babelify\n\nAs you noticed we are calling directly the `React` api to render components, in other words, not using **JSX** yet, lets use it.\n\n```Javascript\n'use strict';\n\nvar React = require('react'),\n    ReactDOM = require('react-dom');\n\n// designed to be called once document is loaded.\nmodule.exports = function(elementId) {\n    var element = global.document.getElementById(elementId);\n\n    ReactDOM.render(\u003cdiv\u003eInitialize the awesome ;)\u003c/div\u003e, element);\n};\n```\n\nTry to run again our old browserify command and see how it explodes in your face (`browserify ui/index.js -o public/game.js -s rockandroll`).\nAs you remember we've installed *babelify* among *react* and *react-dom*, *babelify* is the transformer that *browserify* will use to convert from **JSX** to regular **Javascript**.\n\n`browserify -t babelify ui/index.js -o public/game.js -s rockandroll`\n\nMore errors Uhh?\n\nBabelify will need a little help with the *JSX* syntax and *React*, install `npm install --save-dev babel-preset-react`, now lets use:\n\n`browserify -t [ babelify --presets [ react ] ] ui/index.js -o public/game.js -s rockandroll`\n\nProbably it's time to add this command to our `package.json` file.\n\n```Json\n\"scripts\": {\n    \"build\": \"browserify -t [ babelify --presets [ react ] ] ui/index.js -o public/game.js -s rockandroll\",\n    \"watch\": \"watchify -t [ babelify --presets [ react ] ] ui/index.js -o public/game.js -s rockandroll\"\n}\n```\n\nNow we can run `npm run build` or `npm run watch` for continuos building.\n\n# The Game\n\nWe want to build our digital version of the **Rompecocos game**\n\n![Rompecocos](rompecocos.JPG)\n\n# CSS\n\nLets write some *CSS* code on `./public/styles.css`\n\n```CSS\n.container {\n    margin: 0 auto;\n    border: solid 1px blue;\n    overflow: hidden;\n    box-sizing: border-box;\n    position: relative;\n}\n\n.square {\n    width: 50px;\n    height: 50px;\n    text-align: center;\n    box-sizing: border-box;\n    position: absolute;\n    background: #ccc;\n    cursor: pointer;\n    padding-top: 10px;\n}\n```\n\nDo not forget to add the styesheet reference on the `\u003chead\u003e` of the page\n```Html\n\u003chead\u003e\n    \u003ctitle\u003eReactJS Second Workshop\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"public/styles.css\"\u003e\n    \u003cscript type=\"text/javascript\" src=\"public/game.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n```\n\nWe will create two *React* components to build our game, the **Container** and the **Square**.\n\n* **Square** is the small piece of the game that represents a number that can be moved accross the **Container**\n* **Container** is the box in which the **Squares** are rendered inside.\n\n# The Square Component\n\nCreate a file `./ui/square.js`\n\n```Javascript\n'use strict';\n\nvar React = require('react');\n\nmodule.exports = React.createClass({\n    displayName: 'Square',\n\n    propTypes: {\n        index: React.PropTypes.number.isRequired,\n        number: React.PropTypes.number.isRequired\n    },\n\n    render: function() {\n        var style = {\n            left: (this.props.index % this.props.cols) * 50 + 'px',\n            top: Math.floor(this.props.index / this.props.cols) * 50 + 'px'\n        };\n\n        return (\u003cdiv className=\"square\" style={style}\u003e{this.props.number}\u003c/div\u003e);\n    }\n});\n```\n\nThe maths here are very simple the `\"row = index / numberOfColumns\"` and the `\"col = index % numberOfColumns\"` \nonce we calculated that number we multiply for the square height and width respectively.\n\nNow that we have our square component that we might [reuse](https://facebook.github.io/react/docs/reusable-components.html).\n\n# The Container Component\n\nCreate a file `./ui/contanier.js`\n\n```Javascript\n'use strict';\n\nvar React = require('react'),\n    _ = require('underscore'),\n    Square = require('./square');\n\nmodule.exports = React.createClass({\n    displayName: 'Container',\n\n    propTypes: {\n        rows: React.PropTypes.number,\n        cols: React.PropTypes.number\n    },\n\n    getDefaultProps: function() {\n        return {\n            rows: 4,\n            cols: 4\n        };\n    },\n\n    getInitialState: function() {\n        return {\n            squares: this.initSquares() \n        };\n    },\n\n    initSquares: function() {\n        var targetSquares = this.props.rows * this.props.cols,\n            squares = _.range(1, targetSquares);\n\n        squares.push(null);\n        return squares;\n    },\n\n    createSquare: function(number, index) {\n        return number ? \n            (\u003cSquare number={number} key={number} index={index} cols={this.props.cols}/\u003e) \n            : null;\n    },\n\n    render: function() {\n        var squares = _.map(this.state.squares, this.createSquare),\n            style = {\n                width: (50 * this.props.cols) + 'px',\n                height: (50 * this.props.rows) + 'px'\n            };\n\n        return (\u003cdiv className=\"container\" style={style}\u003e{squares}\u003c/div\u003e);\n    }\n});\n```\n\nOur `Container` *React Component* is the responsible of create and contain the *Squares*, we defined two optional properties\n`rows` and `cols` as numeric, then we assigned the default value to `rows: 4, cols:4`. \n\nThe `getInitialState()` method will create an `array of numbers` that will contain the `[cols * rows]` slots we need to \nrepresent our *Squares* on the game. **Note:** that the array is not a *matrix[cols][rows]* we will do the maths to break it down\ninto a logical matrix of _[cols][rows]_.\n\nIf you try to `npm run build` you will get an error. We are missing to install the *underscore* library. Lets install it\n`npm install --save-dev underscore`.\n\n## Invite the new components to the party\n\nNow that we have created *Container* and *Square* *ReactJS* components we need to render them.\n\n```Javascript\n'use strict';\n\nvar React = require('react'),\n    ReactDOM = require('react-dom'),\n    Container = require('./container');\n\n// designed to be called once document is loaded.\nmodule.exports = function(elementId) {\n    var element = global.document.getElementById(elementId);\n\n    ReactDOM.render(\u003cContainer /\u003e, element);\n};\n```\n\nIf you component is properly designed you can achieve great things.\n\nAt this point we should see something like this:\n\n![Progress 1](progress-1.JPG)\n\nAs you might notice we have our empty slot at the end of the container, this is because we are adding a null element at the end\nof the list, lets insert that empty one at the very beginning rather than into the end.\n\n~~sqares.push(null);~~\n```Javascript\nsquares.splice(0, 0, null);\n```\n\n# Reusable?\n\nWe've saying so much that *ReactJS* components are reusable, well, how much they are? lets try.\n\n```Javascript\n'use strict';\n\nvar React = require('react'),\n    ReactDOM = require('react-dom'),\n    Container = require('./container');\n\n// designed to be called once document is loaded.\nmodule.exports = function(elementId) {\n    var element = global.document.getElementById(elementId);\n\n    ReactDOM.render(\u003cdiv\u003e\u003cContainer /\u003e\u003cContainer rows=\"6\" cols=\"5\" /\u003e\u003c/div\u003e, element);\n};\n```\n\n# This dance needs some sexy moves\n\nLets add some event handling to our *Square* component.\n\n```Javascript\n    render: function() {\n        var index  = this.props.index,\n            number = this.props.number,\n            cols = this.props.cols,\n            style = {\n            left: (index % cols) * 50 + 'px',\n            top: Math.floor(index / cols) * 50 + 'px'\n        };\n\n        return (\u003cdiv onClick={this.props.onSquareClick.bind(null, index, number)}\n            className=\"square\" style={style}\u003e{number}\u003c/div\u003e);\n    }\n```\n\nThere are a few changes you may notice on the *Square's* `render()` method.\n\n* We've added `index, number, cols` vars to make code more readable\n* `onClick` this event is the `ReactJS` handler that the framework will attach to the DOM `\u003celement\u003e`\n* `this.props.onSquareClick` we are making a dangerous assumption, that this propery will have a valid `function` reference\n* `this.props.onSquareClick.bind` we are [binding](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) that `function` to our component and `(index, number)` arguments.\n\nWhat if the `onSquareClick` property is `null`? We have two options here\n\n1. To make this propery required\n```Javascript\n    propTypes: {\n        index: React.PropTypes.number.isRequired,\n        number: React.PropTypes.number.isRequired,\n        onSquareClick: React.PropTypes.func.isRequired\n    },\n```\n2. To define a default property value set to empty `function`.\n```Javascript\n    propTypes: {\n        index: React.PropTypes.number.isRequired,\n        number: React.PropTypes.number.isRequired,\n        onSquareClick: React.PropTypes.func\n    },\n\n    getDefaultProps: function() {\n        return {\n            onSquareClick: function() {}\n        };\n    },\n```\n\nThe `click` event handler is not defined inside our *Square* component because it doesn't know how to handle it, \nrather than that it will notify its *parent*, because parent is the one that assigned the `\u003cSquare onSquareClick=eventHandler /\u003e`.\n\n# Time to party\n\nWe said that parent will be resposible of event handling and that takes us to our *Container* component.\n\n```Javascript\n    onSquareClick: function(index, number) {\n        var options = [\n                index - 1, //left\n                index + 1, //right\n                index - this.props.cols, // top\n                index + this.props.cols //bottom\n            ],\n            targetSquares = this.props.rows * this.props.cols,\n            squares = this.state.squares,\n            moveTo = _.find(options, function(option) {\n                return option \u003e= 0 \u0026\u0026 option \u003c targetSquares \u0026\u0026 squares[option] === null;\n            });\n\n        if (!isNaN(moveTo)) {\n            squares[index] = null;\n            squares[moveTo] = number;\n            this.setState({\n                squares: squares\n            });\n        }\n    },\n\n    createSquare: function(number, index) {\n        return number ? \n            (\u003cSquare number={number} key={number} index={index} cols={this.props.cols}\n                onSquareClick={this.onSquareClick}/\u003e) \n            : null;\n    },\n```\n\nLets start for the easiest part\n\n* `createSquare` method is now assiging `onSquareClick` property of *Square*, there are some factors to notice here:\n    - `onSquareClick={this.onSquareClick}`, **this** is the *Container* instance, remember that react bind all functions to the Component instance.\n    - We are trasnfering the same `onSquareClick` handler to all the *Square*s instances,\n    - That's why each *Square* instance creates its own **binded** reference to this handler.\n* `onSquareClick` this event handler has two arguments `(index, number)`\n    - `index` is the position in the *array* of the element that triggered the event.\n    - `number` is the displaying number on the *Square*\n* What we need to do is to find if there is an empty slot close to the *Square* that triggered the event.\n    - The left slot is `index - 1`\n    - The right slot is `index + 1`\n    - The slot above is `index - cols` [do the maths ;)]\n    - The slot below is `index + cols` [again don't be that lazzy]\n    - Now we use *underscore*'s `_.find` to look the empty slot, while beign careful with ranges.\n* If we find an empty slot close to the given *Square* (`!isNaN`) we clear the current index and move the number to the empty slot.\n    * By calling `this.setState` will triger *ReactJS* rendering mechanisims","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonymtz%2Freact-workshop-second","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonymtz%2Freact-workshop-second","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonymtz%2Freact-workshop-second/lists"}