86
React.js Basics @RobertWPearce robertwpearce.com/converge-reactjs robertwpearce.com/converge-reactjs-demo

React.js Basics - ConvergeSE 2015

Embed Size (px)

Citation preview

Page 1: React.js Basics - ConvergeSE 2015

React.js Basics@RobertWPearce

robertwpearce.com/converge-reactjs robertwpearce.com/converge-reactjs-demo

Page 2: React.js Basics - ConvergeSE 2015

whoamiFrom Charleston, SCDigital NomadWork at 100% remote company, Articulate(articulate.com)Full-stack devWorked with React for almost a year

Page 3: React.js Basics - ConvergeSE 2015

Why You Should ListenI've made more small mistakes than you.

Page 4: React.js Basics - ConvergeSE 2015

Talk NotesSince requiring/exporting modules and dependencies takes

up too much space, I've omitted them from thepresentation.

Page 5: React.js Basics - ConvergeSE 2015

don't freak out. This is just a CoffeeScript way of assigningeach of these to variables which represent, for example

Talk NotesWhen you see

{div, ul, li, span} = React.DOM

React.DOM.div()React.DOM.ul()React.DOM.li()React.DOM.span()# etc

This lets you use div, ul, li and span without prefixing themwith React.DOM.___

Page 6: React.js Basics - ConvergeSE 2015

Talk NotesThere will not be Q&A at the end,

so ask questions as we go.

Page 7: React.js Basics - ConvergeSE 2015

The ProblemCan't see ConvergeSE's speaker details without navigating

to another page

Page 8: React.js Basics - ConvergeSE 2015
Page 9: React.js Basics - ConvergeSE 2015
Page 10: React.js Basics - ConvergeSE 2015

What We Want

Page 11: React.js Basics - ConvergeSE 2015
Page 12: React.js Basics - ConvergeSE 2015

What We Want

Page 13: React.js Basics - ConvergeSE 2015

vanilla JS or jQueryWe could use data selector finagling to hide/show

information and divs accordingly, but this can get very uglyand difficult to maintain.

Page 14: React.js Basics - ConvergeSE 2015

vanilla JS or jQueryFor example, we could

Save each bit of data in data-* attributes for each itemonClick -> Calculate the offset top and height for an itemGive it sufficient padding underneathApply the offset top + height to an absolutely positioneddiv (100% width) containing the infoHope things don't explodeDiscover that our maths suck when there's already an infosection on the pageStart maintaining state somewhere

Page 15: React.js Basics - ConvergeSE 2015

vanilla JS or jQueryor, we could

Determine an item's position on a "row"onClick -> Insert a DOM element at the end of said rowPopulate this DOM element with data obtained from data-* attributes (or a lookup in a variable via data-id?)Maintain state of what element is currently clickedFacepalm when we have to change the # of items per row

Page 16: React.js Basics - ConvergeSE 2015

"Verba movent, exempla trahunt." (Words move people, examples compel them.)

— Latin Proverb

Page 17: React.js Basics - ConvergeSE 2015

First the HowThen the WhyThen the Future

Page 18: React.js Basics - ConvergeSE 2015

How

Page 19: React.js Basics - ConvergeSE 2015

Thinking in Components

Page 20: React.js Basics - ConvergeSE 2015

Thinking in Components

Page 21: React.js Basics - ConvergeSE 2015

Thinking in Components

Page 22: React.js Basics - ConvergeSE 2015

Thinking in Components

Page 23: React.js Basics - ConvergeSE 2015

SpeakersRows of Speakers (grouped every 4)Info (conditionally added to a given Row)

Page 24: React.js Basics - ConvergeSE 2015

Component StructureSpeakers Row Speaker Speaker Speaker Speaker Row Speaker Speaker Speaker Speaker

Page 25: React.js Basics - ConvergeSE 2015

Component Structure (selected)Speakers Row Speaker Speaker Speaker Speaker Row Speaker Speaker (selected) Speaker Speaker Info

Page 26: React.js Basics - ConvergeSE 2015

What do we do first?Group speakers data in to rows of 4Map over each row itemMap over each speaker within a row

Page 27: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)# CoffeeScriptspeakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass

Page 28: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: ->

Page 29: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4)

Page 30: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers',

Page 31: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', rows.map (row) ->

Page 32: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', rows.map (row) -> row.map (speaker) ->

Page 33: React.js Basics - ConvergeSE 2015

Proof of Concept (try #1)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', rows.map (row) -> row.map (speaker) -> div className: 'speaker', speaker.name

Page 34: React.js Basics - ConvergeSE 2015
Page 35: React.js Basics - ConvergeSE 2015

Proof of Concept (try #2)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', rows.map (row) -> row.map (speaker) -> div className: 'speaker', speaker.name

Page 36: React.js Basics - ConvergeSE 2015

Proof of Concept (try #2)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', @_buildRows()

_buildRows: -> rows.map (row) -> row.map (speaker) -> div className: 'speaker', speaker.name

Page 37: React.js Basics - ConvergeSE 2015

Proof of Concept (try #2)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', @_buildRows()

_buildRows: -> rows.map(@_buildSpeakers)

_buildSpeakers: (row) -> row.map (speaker) -> div className: 'speaker', speaker.name

Page 38: React.js Basics - ConvergeSE 2015
Page 39: React.js Basics - ConvergeSE 2015

Proof of Concept (try #3)speakersData = [{ id: 1, name: 'Emily' }, { id: 2, name: 'Lucy' }]

Speakers = React.createClass render: -> rows = _.chunk(speakersData, 4) div className: 'speakers', rows.map (row) -> Row(row: row)

Row = React.createClass render: -> div null, # render must return a React.DOM element or null @props.row.map (speaker) -> Speaker(speaker: speaker)

Speaker = React.createClass render: -> div className: 'speaker', @props.speaker.name

Page 40: React.js Basics - ConvergeSE 2015

Nice work!

Take a breath.

Page 41: React.js Basics - ConvergeSE 2015

Asynchronous Data Fetching

Page 42: React.js Basics - ConvergeSE 2015

Speakers = React.createClass

render: -> # ...

Page 43: React.js Basics - ConvergeSE 2015

Speakers = React.createClass componentWillMount: -> @_fetchSpeakers()

render: -> # ...

Page 44: React.js Basics - ConvergeSE 2015

Speakers = React.createClass componentWillMount: -> @_fetchSpeakers()

render: -> # ...

_fetchSpeakers: -> SomeAjaxHelper .get('speakers.json') .then(@_onSuccess)

_onSuccess: (data) -> # triggers a re-render @setState(speakers: data)

Page 45: React.js Basics - ConvergeSE 2015

Speakers = React.createClass componentWillMount: -> @_fetchSpeakers()

render: -> # what do you think happens here? rows = _.chunk(@state.speakers, 4) div className: 'speakers', rows.map (row) -> Row(row: row)

_fetchSpeakers: -> # ...

_onSuccess: (data) -> @setState(speakers: data)

Page 46: React.js Basics - ConvergeSE 2015

React waits for no code.Speed is the name of the game.

React calls the render method unless specifically told not to.

When there is no initial state nor inital speakers property,@state (and thus, speakers) is undefined.

Page 47: React.js Basics - ConvergeSE 2015

We could do...Speakers = React.createClass componentWillMount: -> @_fetchSpeakers()

render: -> if @state and @state.speakers and @state.speakers.length > 0 rows = _.chunk(@state.speakers, 4) div className: 'speakers', rows.map (row) -> Row(row: row) else null

# ...

Page 48: React.js Basics - ConvergeSE 2015

or we could do...Speakers = React.createClass componentWillMount: -> @_fetchSpeakers()

render: -> if @state?.speakers?.length > 0 rows = _.chunk(@state.speakers, 4) div className: 'speakers', rows.map (row) -> Row(row: row) else null

# ...

Page 49: React.js Basics - ConvergeSE 2015

The Right Way™Speakers = React.createClass # With this we now have an initial bit of # data for our speakers state. getInitialState: -> speakers: []

componentWillMount: -> @_fetchSpeakers()

render: -> if @state.speakers.length > 0 rows = _.chunk(@state.speakers, 4) div className: 'speakers', rows.map (row) -> Row(row: row) else null # or a Loader

Page 50: React.js Basics - ConvergeSE 2015

Building the Child Components

Page 51: React.js Basics - ConvergeSE 2015

Row ComponentRow = React.createClass render: -> div null, @props.row.map (item) => Speaker(speaker: item)

Page 52: React.js Basics - ConvergeSE 2015

Speaker ComponentSpeaker = React.createClass render: -> div className: 'speaker', img className: 'speaker__image', src: @props.speaker.image div className: 'speaker__infoBox', div className: 'speaker__info', h3 className: 'speaker__name', @props.speaker.name span null, @props.speaker.work

Page 53: React.js Basics - ConvergeSE 2015

Adding info toggling functionality

Page 54: React.js Basics - ConvergeSE 2015

What do we need?Keep track of currently selected itemAbility to insert the info at the end of a groupClick event that toggles the info for an item

Page 55: React.js Basics - ConvergeSE 2015

Speakers = React.createClass getInitialState: -> speakers: [] selectedId: -1 # or null, if you prefer

render: -> # ... rows.map (row) -> Row( row: row selectedId: @state.selectedId updateSelectedId: @_updateSelectedId )

_updateSelectedId: (selectedId) -> @setState(selectedId: selectedId)

Page 56: React.js Basics - ConvergeSE 2015

Row = React.createClass render: -> div null, @_buildRow()

_buildRow: -> selectedItems = @_filterSelected() rendered = @props.row.map (item) => Speaker( isSelected: item.id is @props.selectedId speaker: item updateSelectedId: @props.updateSelectedId ) if selectedItems.length > 0 rendered.push(Info(speaker: selectedItems[0])) rendered

_filterSelected: -> @props.row.filter (item) => item.id is @props.selectedId

Page 57: React.js Basics - ConvergeSE 2015

Speaker = React.createClass render: -> div className: 'speaker', onClick: @_handleToggleClick, img className: 'speaker__image', src: @props.speaker.image div className: 'speaker__infoBox', div className: 'speaker__info', h3 className: 'speaker__name', @props.speaker.name span null, @props.speaker.work

# Determines selectedId value and # triggers the callback function # passed down from Speakers _handleToggleClick: -> # b/c i don't have room for a proper if/else selectedId = @props.speaker.id selectedId = -1 if @props.isSelected # reset! @props.updateSelectedId(selectedId)

Page 58: React.js Basics - ConvergeSE 2015

Info = React.createClass render: -> div className: 'speakerInfo', h3 null, @props.speaker.name p null, div null, @props.speaker.work div null, @props.speaker.twitter p null, @props.speaker.bio

Page 59: React.js Basics - ConvergeSE 2015

Rendering to the DOM// index.js

var React = require('react');var Speakers = require('./Speakers.react');

var div = document.createElement('div');document.body.insertBefore(div, document.body.firstChild);

React.render(Speakers(), div); // | | // component container

Page 60: React.js Basics - ConvergeSE 2015

Ship It and Pray...

Page 61: React.js Basics - ConvergeSE 2015

We won't go in to this, but it now is easy to add additionalfunctionality, such as Search.

Speakers = React.createClass # ... render: -> div null, Search() @_renderRows()

and control the data via this main component.

Page 62: React.js Basics - ConvergeSE 2015

Take a breath.

Page 63: React.js Basics - ConvergeSE 2015

Why

Page 64: React.js Basics - ConvergeSE 2015

Truth

Page 65: React.js Basics - ConvergeSE 2015

Maintaining state in web applications. How...?

Page 66: React.js Basics - ConvergeSE 2015

In web applications, what has been the source of data truthsince day one?

The server.

Page 67: React.js Basics - ConvergeSE 2015
Page 68: React.js Basics - ConvergeSE 2015

Mutating the DOM never made sense to me.

The solution?

Page 69: React.js Basics - ConvergeSE 2015
Page 70: React.js Basics - ConvergeSE 2015

Isn't that expensive on browsers?

Nope.

Page 71: React.js Basics - ConvergeSE 2015

Virtual DOM

Page 72: React.js Basics - ConvergeSE 2015

Image credit: Steven Hollidge

Page 73: React.js Basics - ConvergeSE 2015

Re-rendering subset (good)

Image credit: Christopher Chedeau

Page 74: React.js Basics - ConvergeSE 2015

Re-rendering subset (good)

Image credit: Christopher Chedeau

Page 75: React.js Basics - ConvergeSE 2015

but...

Image credit: Alexander Early

Page 76: React.js Basics - ConvergeSE 2015

The previous image occurs when one component does notcontrol all of the data for an application, or at least for a set

of subcomponents.

Page 77: React.js Basics - ConvergeSE 2015

It's very tempting to want to manage state inside thecomponent that the state affects.

Page 78: React.js Basics - ConvergeSE 2015

Remember this?Speaker = React.createClass render: -> # ... Row(updateSelectedId: @_updateSelectedId)

_updateSelectedId: (selectedId) -> # ...

Row = React.createClass render: -> # ... Speaker(updateSelectedId: @_updateSelectedId)

Speaker = React.createClass render: -> # ... div className: 'speaker', onClick: @_handleClick # ...

_handleClick: -> # ... @props.updateSelectedId(selectedId)

Page 79: React.js Basics - ConvergeSE 2015

Utilizing EventsSpeaker = React.createClass componentDidMount: -> SpeakerEmitter.addSelectionChangeListener(@_updateSelectedId)

componentWillUnmount: -> SpeakerEmitter.removeSelectionChangeListener(@_updateSelectedId)

render: -> Row()

_updateSelectedId: (selectedId) -> # ...

Row = React.createClass render: -> Speaker()

Speaker = React.createClass render: -> div className: 'speaker', onClick: @_handleClick

_handleClick: -> SpeakerEmitter.emitSelectionChange(selectedId)

Page 80: React.js Basics - ConvergeSE 2015

Image credit: Alexander Early

Page 81: React.js Basics - ConvergeSE 2015

The Future

Page 82: React.js Basics - ConvergeSE 2015

It's quite popular

Page 83: React.js Basics - ConvergeSE 2015

Client Side & Server-sideYou can compile React on the server and use the same

templates for server-side rendering as you do for client-side.

Page 84: React.js Basics - ConvergeSE 2015

It's gone native

Page 85: React.js Basics - ConvergeSE 2015

They're Open Source Committed

Page 86: React.js Basics - ConvergeSE 2015

TL;DRIf your current JS framework/lib/stack makes you feel like

give React a shot and see if if makes you feel like