Let's get to the code already!
(this course assumes you have git, yarn, a text editor, and a command line available)
Agenda:
- step 1: set up the dev environment
- step 2: see something run in the browser
- step 3: change some code
- step 4: see the app change in the browser
Let's make our work directory for the course
$ mkdir -p ~/code/react-course
$ cd ~/code/react-course
and clone the first workbook
$ git clone https://github.com/nikfrank/react-course-workbook-0
$ cd react-course-workbook-0
$ yarn
$ npm start
we're done step 1... we should have a browser tab open to localhost:3000
go to the browser, see what's there! (that was easy)
open up our text editor to ./src/App.js
and write our first line of code
before:
dealNextCard = ()=> 0 // noopafter:
dealNextCard = ()=> this.setState(({ cards, handStatus })=> ({
cards: (handStatus === 'hitting') ?
cards.concat( newCard() ) :
[ newCard(), newCard() ]
}), ({
hasAce = !!this.state.cards.find(({ rank })=> rank === 1),
total = this.state.cards.reduce((p, { rank })=> p + Math.min(10, rank), 0),
} = {})=> this.setState(({ cards })=> ({
handStatus: (total > 21) ? 'bust' :
(total >= 17) ? 'standing' :
(hasAce && (total === 11)) ? 'blackjack' :
(hasAce && (total >= 8) && (total < 11)) ? 'standing' :
'hitting'
}) )
)that was a lot of fun! (we're done step 3 now!)
now your app plays the dealer hand like a las vegas dealer, all in one line of code.
go have fun!
The purpose of this admittedly silly example is to intruduce you to the main topics we'll be covering:
- react (here we use React.Component's this.setState function)
- functional style programming
- es6 (destructuring, fat arrows, default params used here)
also, it's important to understand javascript is a language of many styles, finding what works for you is most important - this function is how I like to write things, but maybe isn't your cup of tea!
You'll read plenty of javascript, some of it will be good stuff - so keep your eyes open.
the precocious student will want to learn how this works (instead of just typing it in and believing in magic):
feel free to skip over this section the first time you go through this lesson!... which is to say, you're already done lesson 0
dealNextCard = ()=> this.setState(({ cards, handStatus })=> ({
cards: (handStatus === 'hitting') ?
cards.concat( newCard() ) :
[ newCard(), newCard() ]
}),first, we are declaring an instance method this.dealNextCard which takes no params ()=>
the body of the function is one line (it doesn't start with a { and end with a }) and consists of a call to this.setState
this.setState is from the React Component API, in this case using the updater function not the shallow merge (which is the simpler way)
after destructuring the cards and handStatus out of this.state, we return an object with a new value for this.state.cards
the new value we calculate is determined by handStatus:
- if we're hitting, our new cards array will be the old array with a
newCard()concatenated (newCard is a function declared earlier in the file which generates an object with a randomrankandsuitproperty, each being just a number) - if we're not hitting it is assumed the hand is over, so we return a two new cards
In total, the first chunk calculates the cards, either adding one new card or a new hand. After the comma }), we have finished our updater function as the first param, and are ready to pass the second param to this.setState (which is a callback function)
({
hasAce = !!this.state.cards.find(({ rank })=> rank === 1),
total = this.state.cards.reduce((p, { rank })=> p + Math.min(10, rank), 0),
} = {})=>Here, we're exploiting two facts to calculate the total and hasAce values without writing two lines of code:
- React's
this.setState's second param is a callback which receives no parameters (React calls it once the state update we're requesting is done) - We can set a default value for function params (here the {} near the end), which will always be used (as the function is called with
undefinedas the first param)... then we can destructure that empty object and set default values for fields of that object (which again, will always be used), and those default values are evaluated expressions - which since we are in the callback function, we know our state update has occured, and thus can use our new cards in our calculations.
this is actually bad code. It is hard to read (flow is backwards) and misuses React's setState's API. I only did this to avoid having a function body, which is totally irrelevant... just for fun!
after the => for one statement, we will have access to hasAce and total
hasAce is a boolean (!! is how to cast to boolean in javascript) which is true when we can .find a card in this.state.cards who has a field .rank which equals 1 (ie is an ace)
total is the sum of the cards, counting all face cards as 10, and aces as 1 (we'll manage the 'ace can be 11' rule in the body-statement of this callback function)
this.setState(({ cards })=> ({
handStatus: (total > 21) ? 'bust' :
(total >= 17) ? 'standing' :
(hasAce && (total === 11)) ? 'blackjack' :
(hasAce && (total >= 8) && (total < 11)) ? 'standing' :
'hitting'
}) )this statement is the body-statement of our callback function (fat arrow functions which are one line of code can be written without {curlies}!)
now that we know hasAce and total we can use our vast existing knowledge of the game of blackjack dealer rules to determine the next handStatus.
again we call this.setState with an updater function, destructuring cards from this.state
we return (for setState to do a shallow merge onto state) an object with handStatus calculated with chained ternary expressions.
let's take a minute to learn about ternary by flipping a coin
const condition = Math.random() > 0.5;
if( condition ) console.log('heads');
else console.log('tails');can be refactored down to
const condition = Math.random() > 0.5;
const output = condition ? 'heads' : 'tails';
console.log( output );the value of the ternary is that we could rewrite this into one line, without using an if block
console.log( Math.random() > 0.5 ? 'heads' : 'tails' );because ternary is an expression... not a block statement!
as it turns out, each of the two outcomes are just expressions, so we can plug ternaries in there (chaining)
const flip = ()=> (Math.random() > 0.5);
console.log( flip() ? 'heads first' :
flip() ? 'heads second' :
'two tails' );some people don't like doing this, even writing lint rules against it. I liked it for the handStatus example because it reads like proper functional pattern matching.
So that's all there is to it!, we check each possible outcome, and the first one to match (true) determines our handStatus value.
After the state has been updated, our render function is triggered, and the new card(s) and status are displayed to the user.
This is just a silly example meant to prime you for the rest of the course. Don't worry if the code made not much sense yet!
I'll leave it as an exercise for you to write this function HOW you're comfortable, when you're ready.
It's important here to find your own way in JavaScript - there's no other way to be found.
for more reading, here are some google search term links
- fat arrow
- ternary operator
- React setState - make sure to read about bothe updater (first param) and callback (second param) functions!
- destructuring
- default params
- array reduce
- array find
- array concat
- casting to boolean
- blackjack dealer rules
- my npm package react-deck-o-cards
think it through:
- play against dealer
- keep track of bets
- splitting, doubling down
- insurance
- multiplayer, online, ..!