Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/tonymtz/react-workshop-second
ReactJS workshop
https://github.com/tonymtz/react-workshop-second
Last synced: about 1 month ago
JSON representation
ReactJS workshop
- Host: GitHub
- URL: https://github.com/tonymtz/react-workshop-second
- Owner: tonymtz
- License: gpl-2.0
- Fork: true (ryanez/react-workshop-second)
- Created: 2015-11-20T00:43:06.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2015-11-12T23:27:44.000Z (about 9 years ago)
- Last Synced: 2024-04-14T12:55:59.736Z (7 months ago)
- Language: JavaScript
- Size: 0 Bytes
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ReactJS workshop
I'm assuming you already went throgh [first workshop](https://github.com/ryanez/react-workshop-first)
# The setup
- Create a directory for the project and then run `npm init`, follow your instincts to answer the wizard's questions.
- Install React and its dependencies `npm install react react-dom babelify --save-dev`**Note**: At the moment of writing this tutorial the current version of react/react-dom/babelify are:
* "babelify": "^7.2.0",
* "react": "^0.14.2",
* "react-dom": "^0.14.2"# Index file
- On root directory create `.\ui` & `.\public` directories.
- Create the index file `.\ui\index.js````Javascript
'use strict';var React = require('react'),
ReactDOM = require('react-dom');// designed to be called once document is loaded.
module.exports = function(elementId) {
var element = global.document.getElementById(elementId);ReactDOM.render(React.createElement('div', null, 'here we will initialize the awesome'), element);
};
```# Browserify
You must have globaly installed browserify at this point (if you don't `npm install -g browserify`), so let's bundle our index file.
`browserify ui/index.js -o public/game.js -s rockandroll`
# Index.html
Lets create a HTML file that renders our React Application, create `index.html` on root directory.
```Html
ReactJS Second Workshop
Hello world!```
**Note:** `rockandroll` is the `-s rockandroll` parameter we set while browserifying and we call `rockandroll('container')` because it's the elment Id.
Now we can open `index.html` on browser and see how the "Hello world!" is replaced by our ReactJS component.
# Babelify
As you noticed we are calling directly the `React` api to render components, in other words, not using **JSX** yet, lets use it.
```Javascript
'use strict';var React = require('react'),
ReactDOM = require('react-dom');// designed to be called once document is loaded.
module.exports = function(elementId) {
var element = global.document.getElementById(elementId);ReactDOM.render(
Initialize the awesome ;), element);
};
```Try 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`).
As 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**.`browserify -t babelify ui/index.js -o public/game.js -s rockandroll`
More errors Uhh?
Babelify will need a little help with the *JSX* syntax and *React*, install `npm install --save-dev babel-preset-react`, now lets use:
`browserify -t [ babelify --presets [ react ] ] ui/index.js -o public/game.js -s rockandroll`
Probably it's time to add this command to our `package.json` file.
```Json
"scripts": {
"build": "browserify -t [ babelify --presets [ react ] ] ui/index.js -o public/game.js -s rockandroll",
"watch": "watchify -t [ babelify --presets [ react ] ] ui/index.js -o public/game.js -s rockandroll"
}
```Now we can run `npm run build` or `npm run watch` for continuos building.
# The Game
We want to build our digital version of the **Rompecocos game**
![Rompecocos](rompecocos.JPG)
# CSS
Lets write some *CSS* code on `./public/styles.css`
```CSS
.container {
margin: 0 auto;
border: solid 1px blue;
overflow: hidden;
box-sizing: border-box;
position: relative;
}.square {
width: 50px;
height: 50px;
text-align: center;
box-sizing: border-box;
position: absolute;
background: #ccc;
cursor: pointer;
padding-top: 10px;
}
```Do not forget to add the styesheet reference on the `` of the page
```HtmlReactJS Second Workshop
```
We will create two *React* components to build our game, the **Container** and the **Square**.
* **Square** is the small piece of the game that represents a number that can be moved accross the **Container**
* **Container** is the box in which the **Squares** are rendered inside.# The Square Component
Create a file `./ui/square.js`
```Javascript
'use strict';var React = require('react');
module.exports = React.createClass({
displayName: 'Square',propTypes: {
index: React.PropTypes.number.isRequired,
number: React.PropTypes.number.isRequired
},render: function() {
var style = {
left: (this.props.index % this.props.cols) * 50 + 'px',
top: Math.floor(this.props.index / this.props.cols) * 50 + 'px'
};return (
{this.props.number});
}
});
```The maths here are very simple the `"row = index / numberOfColumns"` and the `"col = index % numberOfColumns"`
once we calculated that number we multiply for the square height and width respectively.Now that we have our square component that we might [reuse](https://facebook.github.io/react/docs/reusable-components.html).
# The Container Component
Create a file `./ui/contanier.js`
```Javascript
'use strict';var React = require('react'),
_ = require('underscore'),
Square = require('./square');module.exports = React.createClass({
displayName: 'Container',propTypes: {
rows: React.PropTypes.number,
cols: React.PropTypes.number
},getDefaultProps: function() {
return {
rows: 4,
cols: 4
};
},getInitialState: function() {
return {
squares: this.initSquares()
};
},initSquares: function() {
var targetSquares = this.props.rows * this.props.cols,
squares = _.range(1, targetSquares);squares.push(null);
return squares;
},createSquare: function(number, index) {
return number ?
()
: null;
},render: function() {
var squares = _.map(this.state.squares, this.createSquare),
style = {
width: (50 * this.props.cols) + 'px',
height: (50 * this.props.rows) + 'px'
};return (
{squares});
}
});
```Our `Container` *React Component* is the responsible of create and contain the *Squares*, we defined two optional properties
`rows` and `cols` as numeric, then we assigned the default value to `rows: 4, cols:4`.The `getInitialState()` method will create an `array of numbers` that will contain the `[cols * rows]` slots we need to
represent our *Squares* on the game. **Note:** that the array is not a *matrix[cols][rows]* we will do the maths to break it down
into a logical matrix of _[cols][rows]_.If you try to `npm run build` you will get an error. We are missing to install the *underscore* library. Lets install it
`npm install --save-dev underscore`.## Invite the new components to the party
Now that we have created *Container* and *Square* *ReactJS* components we need to render them.
```Javascript
'use strict';var React = require('react'),
ReactDOM = require('react-dom'),
Container = require('./container');// designed to be called once document is loaded.
module.exports = function(elementId) {
var element = global.document.getElementById(elementId);ReactDOM.render(, element);
};
```If you component is properly designed you can achieve great things.
At this point we should see something like this:
![Progress 1](progress-1.JPG)
As 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
of the list, lets insert that empty one at the very beginning rather than into the end.~~sqares.push(null);~~
```Javascript
squares.splice(0, 0, null);
```# Reusable?
We've saying so much that *ReactJS* components are reusable, well, how much they are? lets try.
```Javascript
'use strict';var React = require('react'),
ReactDOM = require('react-dom'),
Container = require('./container');// designed to be called once document is loaded.
module.exports = function(elementId) {
var element = global.document.getElementById(elementId);ReactDOM.render(
, element);
};
```# This dance needs some sexy moves
Lets add some event handling to our *Square* component.
```Javascript
render: function() {
var index = this.props.index,
number = this.props.number,
cols = this.props.cols,
style = {
left: (index % cols) * 50 + 'px',
top: Math.floor(index / cols) * 50 + 'px'
};return (
{number});
}
```There are a few changes you may notice on the *Square's* `render()` method.
* We've added `index, number, cols` vars to make code more readable
* `onClick` this event is the `ReactJS` handler that the framework will attach to the DOM ``
* `this.props.onSquareClick` we are making a dangerous assumption, that this propery will have a valid `function` reference
* `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.What if the `onSquareClick` property is `null`? We have two options here
1. To make this propery required
```Javascript
propTypes: {
index: React.PropTypes.number.isRequired,
number: React.PropTypes.number.isRequired,
onSquareClick: React.PropTypes.func.isRequired
},
```
2. To define a default property value set to empty `function`.
```Javascript
propTypes: {
index: React.PropTypes.number.isRequired,
number: React.PropTypes.number.isRequired,
onSquareClick: React.PropTypes.func
},getDefaultProps: function() {
return {
onSquareClick: function() {}
};
},
```The `click` event handler is not defined inside our *Square* component because it doesn't know how to handle it,
rather than that it will notify its *parent*, because parent is the one that assigned the ``.# Time to party
We said that parent will be resposible of event handling and that takes us to our *Container* component.
```Javascript
onSquareClick: function(index, number) {
var options = [
index - 1, //left
index + 1, //right
index - this.props.cols, // top
index + this.props.cols //bottom
],
targetSquares = this.props.rows * this.props.cols,
squares = this.state.squares,
moveTo = _.find(options, function(option) {
return option >= 0 && option < targetSquares && squares[option] === null;
});if (!isNaN(moveTo)) {
squares[index] = null;
squares[moveTo] = number;
this.setState({
squares: squares
});
}
},createSquare: function(number, index) {
return number ?
()
: null;
},
```Lets start for the easiest part
* `createSquare` method is now assiging `onSquareClick` property of *Square*, there are some factors to notice here:
- `onSquareClick={this.onSquareClick}`, **this** is the *Container* instance, remember that react bind all functions to the Component instance.
- We are trasnfering the same `onSquareClick` handler to all the *Square*s instances,
- That's why each *Square* instance creates its own **binded** reference to this handler.
* `onSquareClick` this event handler has two arguments `(index, number)`
- `index` is the position in the *array* of the element that triggered the event.
- `number` is the displaying number on the *Square*
* What we need to do is to find if there is an empty slot close to the *Square* that triggered the event.
- The left slot is `index - 1`
- The right slot is `index + 1`
- The slot above is `index - cols` [do the maths ;)]
- The slot below is `index + cols` [again don't be that lazzy]
- Now we use *underscore*'s `_.find` to look the empty slot, while beign careful with ranges.
* 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.
* By calling `this.setState` will triger *ReactJS* rendering mechanisims