React workshop
Learn front-end web development with React 📷🎶 💻 🤓
React workshop
Learn front-end web development with React 📷🎶 💻 🤓
Preview
We need:
For Windows users:
Check out this doc if having problems with Node, npm installation
Git setup Also in Slack #resources
$ git clone https://github.com/<username>/react_workshop.git
or use SSH if you’d like
$ cd react_workshop
$ git checkout dev
Git: exploring workshop code
Also in Slack #resources
Comparing branches
Or use Github compare tool:
Checkout code at any step
$ git stash // (and/or git clean -fd) (or commit + push in a new branch) $ git checkout <chapter> // e.g. git checkout c4.1 $ npm install // install deps to make code work // if needed, restart terminal processes for npm run dev |
Introduction
Let’s make this better!
After the workshop/before you leave
Please answer a short survey about the workshop
Please go to the code repo, and give it a star
(if it deserves one, of course!)
https://github.com/lenmorld/node_workshop
Any mistakes or improvements, please submit an issue
or even better, contribute to open-source! (ping me in Slack for details)
React and webpack setup
Our React boilerplate
$ git checkout dev $ npm install # if nodemon fails $ npm install -g nodemon $ npm run dev |
Our React boilerplate
Our first React component
// app/index.jsx import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { render() { return ( <div>React: Hello World!</div> ); } }; ReactDOM.render(<App />, document.getElementById('app')); |
1 React Basics
Codesandbox: Component Hierarchy
Codesandbox:
https://codesandbox.io/u/lenmorld/sandboxes/
sandbox name:
2A_components
or use direct link: https://codesandbox.io/s/p791r850r0
c1.1 UIManager.jsx
// app/UIManager.jsx import React from 'react'; import data from './data'; console.log(data); class UIManager extends React.Component { render() { return( <div>List goes here...</div> ); } } export default UIManager; |
// app/index.jsx import React from 'react'; import ReactDOM from 'react-dom'; import UIManager from './UIManager'; ...� render() {� return (� <UIManager />� );�... |
c1.2 Header.jsx
// app/UIManager.jsx import Header from './Header'; ... return( <div> <Header /> <div>List goes here...</div> </div> ); ... |
Different ways to style your components:
c1.3 List.jsx and Item.jsx
Component tree
App
UIManager
Header
List
Item
...
// app/Item.jsx import React from "react"; class Item extends React.Component { render() { return <div>...item...</div>; } } export default Item; // app/List.jsx import React from "react"; import Item from "./Item"; class List extends React.Component { render() { return ( <div><Item /></div> ); } } export default List; // app/UIManager.jsx import List from './List'; ... <Header /> <List /> |
Props
What?
Props is a data object passed from a parent component to a child component. It uses attribute syntax like in HTML
How?
Inside the JSX of a parent component, include the child component, passing the data in this format:
<Child propname=propvalue>
e.g. <Person name="Lenny">
as an example
Codesandbox: Props
Codesandbox:
https://codesandbox.io/u/lenmorld/sandboxes/
sandbox name:
2B_react_props
or use direct link: https://codesandbox.io/s/7joom0lp6x
State
aka Data
state change → re-render UI
What and why?
Implementing State
How?
Inside a component declaration:
To read data, use this.state.<obj>
class App extends React.Component { constructor() { super(); this.state = { name: "Concordia" }; } render() { return ( <div> {/* get from state */} <School name={this.state.name} /> ... |
Codesandbox: State
Codesandbox:
https://codesandbox.io/u/lenmorld/sandboxes/
sandbox name:
2C_react_state
or use direct link: https://codesandbox.io/s/0x3y5vj61l
State and Props gotchas! 😱
Props
State
State and Props analogy
Component_tree:
Boss
Employee
Props passing:
Boss → $100 → Employee
👨💼👩💼👨💼
c1.4 Add state to UIManager, pass list object as props to List
// app/UIManager.jsx ... class UIManager extends React.Component {�� constructor() { super(); this.state = { listName: "The list", list: data.music } }�� render() {� ...�<List name={this.state.listName} list={this.state.list} /> � ...� |
// app/List.jsx ... class List extends React.Component { render() { return ( <div> <h3>{this.props.name}</h3> <div>List: {this.props.list}</div> <hr/> <Item /> </div> ...� |
c1.5 Using map() to render list
// app/List.jsx render() { var list = this.props.list; return ( <div> <h3>{this.props.name}</h3> <hr /> { list.map(function (item) { return ( <Item item={item} key={item.id} /> ) }) } </div> ); } |
📝 NOTE: always include a key to help React optimize rendering
Map, filter, reduce
Codesandbox:
https://codesandbox.io/u/lenmorld/sandboxes/
sandbox name: js_map_filter_reduce
or use direct link: https://codesandbox.io/s/4z684jjzxw
c1.6 rendering Item
Copy from Slack #react_snippets: Item.jsx
// app/Item.jsx ... class Item extends React.Component { render() { var item = this.props.item; ... <div className="right"> <div className="title">{item.title}</div> <div className="artist">{item.artist}</div> <div className="album">{item.album}</div> </div> |
Presentational components
c1.7 Stateless/functional components
// CLASS Component import React from "react"; class Item extends React.Component { render() { var item = this.props.item; return ( <div> <div className="title">{item.title}</div> <div className="artist">{item.artist}</div> <div className="album">{item.album}</div> </div> ); } } export default Item; |
// STATELESS (functional) Component const Item = (props) => ( <div> <div className="title">{props.item.title}</div> <div className="artist">{props.item.artist}</div> <div className="album">{props.item.album}</div> </div> ); export default Item; |
*required when using state, constructor(), and other lifecycle methods
succinct, slight increase in performance
2 Forms and event handlers
Searching
Events and event handlers
HTML elements (e.g. inputs, buttons) generate events (onClick, onChange, etc), which is processed by an event handler function. This is where we can define what to do when the event happens
Event handler
c2.1 event handler
// app/UIManager.jsx ... searchList(event) { var search_term = event.target.value; console.log(search_term); } render() { return( <div> <Header /> <div className="options"> <input type="text" placeholder="Filter..." onChange={this.searchList} /> </div> <List list={this.state.list}/> </div> ); } ...� |
*event parameter is automatically passed here as the default argument of a HTML element event handler
c2.2 setState() to modify state
For us to filter the list based on current input:
class UIManager extends React.Component { ... this.state = { listName: "The list", list: data.music, searchTerm: '', } } searchList(event) { var searchTerm = event.target.value; console.log(`${this.state.searchTerm} -> ${searchTerm}`); this.setState({ searchTerm: searchTerm }); |
But we’re getting an error!
c2.3 using an arrow function to bind this
Codesandbox: 3A_binding_event_handlers_this https://codesandbox.io/s/3x3z1rm365
Explore the issue and solutions here
Alternatives:
// app/UIManager.jsx ... <div className="options"> <input type="text" placeholder="Filter..." onChange={ (event) => this.searchList(event)} /> </div> ... |
c2.4 filtering list based on state.search_term
render() { // filter list based on current user input -> searchTerm var list = this.state.list; var searchTerm = this.state.searchTerm; var filteredList; if (!searchTerm) { filteredList = list; } else { filteredList = list.filter(function (item) { return item.title.toLowerCase().includes(searchTerm.toLowerCase()); }); } return ( ... <List name={this.state.listName} list={filteredList} /> |
Create / Add new Item
c2.5 ItemForm.jsx
We’ll create a component that has an HTML form
NOTES: 📝
searchTerm: '', formFields: { id: '', title: '', artist: '', album: '', }, |
** from this chapter onwards,
see full code change on Github
Function props
Remember that Employee (child) component cannot modify Boss (parent) state directly.
However, Boss can pass a function to Employee that it can call whenever it wants to change Bosss state. e.g:
In Boss render():
<Employee performWell={(money) => this.increaseMoney(money)}>
In Employee render():
<button onClick={() => this.props.performWell(100)}>
Codesandbox: 4B_state_props_analogy https://codesandbox.io/s/6jormxo65n
👨💼👩💼👨💼
c2.6 Function props
UIManager.jsx
UIManager renders ItemForm, passing a function prop
that allows ItemForm to change data (state) in UIManager
<UIManager>
<ItemForm>
Props passing:
<UIManager> ---- onChangeFormInput(event) ---> <ItemForm>
class UIManager extends React.Component { ... onChangeFormInput(event) { console.log("input changed"); } ... <ItemForm item={this.state.formFields} onChangeFormInput={(event) => this.onChangeFormInput(event)} /> |
c2.6 Function props
ItemForm.jsx
ItemForm then calls the function prop given by UIManager,
whenever there is a change in input (onChange event).
We do this for the 4 inputs (id, item, artist, album)
<UIManager>
<ItemForm>
Props passing:
<UIManager> ---- onChangeFormInput(event) ---> <ItemForm>
class ItemForm extends React.Component { ... <p> <label>ID:</label> <input name="id" onChange={(event) => this.props.onChangeFormInput(event)} value={item.id} /> |
c2.7 Other event handlers of ItemForm
ItemForm.jsx
class ItemForm extends React.Component { hideForm() { console.log("hide form"); } onSubmitForm(event) { event.preventDefault(); // prevent reload of page console.log("form submitted"); } ... <div className="close_form"> <span onClick={this.hideForm}>[🗙]</span> </div> ... <div className="create"> <button onClick={(event) => this.onSubmitForm(event) }> CREATE </button> |
c2.8 onChangeFormInput
Once UIManager receives the event from ItemForm (through function prop)
class UIManager extends React.Component { ... onChangeFormInput(event) { // copy values (not reference) using ES6 spread var currentListFields = { ...this.state.formFields }; // e.g. current_list_fields['artist'] = 'Artist1' currentListFields[event.target.name] = event.target.value; // apply new value to state this.setState({ formFields: currentListFields }); } |
c2.9 createItem()
In UIManager
<UIManager>
<ItemForm>
Props passing:
<UIManager> → createItem(item) → <ItemForm>
<ItemForm item={this.state.formFields} onChangeFormInput={(event) => this.onChangeFormInput(event)} createItem={() => this.createItem() } /> |
class UIManager extends React.Component { ... // data API - CRUD methods createItem() { // get Item data from state var item = this.state.formFields; // copy list values, not reference, using ES6 spread operator var currentListItems = [...this.state.list]; // add new item currentListItems.push(item); // apply change to state this.setState({ list: currentListItems }); // empty fields for next round this.setState({ formFields: { id: '', title: '', artist: '', album: '' } }); } |
c2.9 createItem()
ItemForm
class ItemForm extends React.Component { ... onSubmitForm(event) { event.preventDefault(); // prevent reload of page // console.log("form submitted"); this.props.createItem(); } |
c2.10 show and hide ItemForm
UIManager:
Add [+] button beside search box, add onClick and set to showForm()
showForm()
Invoke when clicking [+] -> set style to ‘block’
ItemForm:
Set CSS class to “modal”
hideForm()
Invoke when clicking [X] on modal -> set style to ‘hidden’
Optional: invoke after adding a new item
View code changes:
https://github.com/lenmorld/react_workshop/compare/c2.9...c2.10
TEST IT!
Add a song or two to your playlist
(Not a lot since it will be gone on page refresh!)
Recap of React forms
we covered:
questions?
A form example
Codesandbox:
https://codesandbox.io/u/lenmorld/sandboxes/
sandbox name: 6_react_forms
or use direct link: https://codesandbox.io/s/k9v60wxp07
3 Forms CRUD
Delete and Update item
c3.1 Delete ❎ and Edit 📝
Delete and Update button icons will be inside Item as icons that appear on hover
NOTE: the use of arrow functions to be able to use this
<UIManager>
<List>
<Item>
Props passing:
<UIManager> -- deleteItem() ---> <List> -- deleteItem() ---> <Item>
editItem() editItem()
c3.2 implementing deleteItem()
NOTE: notice the current pattern in CRUD methods
1 ) Copy list 2 ) do operation on copy 3 ) setState
❎
c3.3 editItem() - form mode
When user clicks edit, we have to show ItemForm, but let it know that we want to EDIT, not CREATE. We do this by adding a mode in state, and passing the item to be edited so the form fields would be populated.
UIManager.jsx
ItemForm.jsx
📝
c3.4 editItem() - show ItemForm
on edit mode
UIManager must get correct item to be edited, before passing to ItemForm
In editItem()
Now, we are getting the item values in the form fields
📝
c3.5 editItem() - saveUpdatedItem
When form is saved, we need a CRUD method that will apply changes to our data
UIManager saveUpdatedItem():
Pass saveUpdatedItem to ItemForm as function props
ItemForm:
📝
c3.6 set ItemForm fields on [+] too
🐞 BUG 🐞: Notice that after Edit, clicking Create [+] sets the item fields to previous item incorrectly in ItemForm
UIManager:
TEST IT!
Create, Edit, Delete, Filter (Search)
4 Integrating with Giphy API
Fetching data
Lifecycle methods
Refs
c4.1Component setup
Giphy.jsx
index.jsx
# axios is a promise-based HTTP library $ npm install axios |
how do we fetch data in JS?
common pattern in Async data fetch
componentDidMount, and other React lifecycle methods
c4.2 componentDidMount()
Fetch data from backend before component is rendered. How?
componentDidMount()
class Giphy extends React.Component { componentDidMount() { axios.get(`<REQUEST_URL>`) .then((res) => { console.log(res); }); } ... |
Using the Giphy API
NOTE: different APIs have varying degree
of authentication (no auth, API key, Oauth, etc)
Check out the docs of the API you need to use.
c4.3 setState and rendering
componentDidMount()
When response is received, set current state data to response data
render()
Render array into JSX items using map(), following API formats and desired styling, layout, etc
class ApiTest extends React.Component { componentDidMount() { ... .then((res) => { const memes = res.data.data; this.setState({ memes: memes, }) }); } render() { return ( <div> {this.state.memes.map((item) => <div key={item.id}> <iframe src={item.embed_url} ... /> </div> )} </div> ); } ... |
c4.4 Show loading when data not ready
Initialize state.list to [ ], to show a Loading page if results are not back yet
class ApiTest extends React.Component { ... render() { if (this.state.memes.length === 0) { return <div>Loading...</div>; } return ( <div> {this.state.memes.map((item) => ... |
Searching meme and adjusting count
Instead of hard-coding SEARCH_QUERY and RESULTS_LIMIT, we can use our knowledge of forms to include a search!
Similar to Chapters 2 and 3, we can setup searchQuery and resultsLimit in the state and wire and event handler to them (DO THIS AS A PRACTICE).
But since this is a smaller example, we’ll also explore a simpler way of using inputs in React, called refs.
Controlled vs Uncontrolled inputs
Controlled inputs:
Uncontrolled input:
c4.5 refs
class ApiTest extends React.Component { constructor() { ... // React refs for searchQuery and resultsLimit this.searchQueryInput = React.createRef(); this.resultsLimitInput = React.createRef(); } ... submitSearch(event) { const searchQuery = this.searchQueryInput.current.value; const resultsLimit = this.resultsLimitInput.current.value; // execute request axios.get() {...} ... render() { ... <form> <p>Search: <input type="text" ref={this.searchQueryInput} /></p> <p>Num. results: <input type="number" ref={this.resultsLimitInput}/></p> <button onClick={(event) => {this.submitSearch(event)}}>Search memes!</button> </form> |
Meme search using Giphy API and React refs
5 Frontend Routing
Routing
When doing a SPA (Single Page Application) in React, we need a routing library to:
We use react-router
# React routing library $ npm install react-router-dom |
c5.1 setup routes
// index.jsx import { HashRouter, Route, Link } from 'react-router-dom'; class App extends React.Component { render() { return <BrowserRouter> <div> {/* nav links to take us to the route */} <nav> <ul> <li><Link to="/">Songs</Link></li> <li><Link to="/memes">Memes</Link></li> </ul> </nav> {/* match route to React component */} <Route exact path="/" component={UIManager} /> <Route exact path="/memes" component={Giphy} /> </div> </HashRouter> ... |
c5.2 passing URL params to components
// index.jsx ... <li><Link to="/greeting/en">Greeting</Link></li> ... <Route exact path="/greeting/:lang" render={(props) => <Greeting text="Hello, " {...props} /> } |
// Greeting.jsx ... import qs from "qs"; ... class Greeting extends React.Component { render() { // debugger; const { text, location, match } = this.props; const lang = match.params.lang; const name = qs.parse(location.search, { ignoreQueryPrefix: true }).name; ... |
Simple routing with react-router
6 Integrating with Spotify API
Spotify search API
c6.0 Server setup
This branch contains code for:
$ git checkout c6.0 $ npm install qs # query parser $ npm run dev |
c6.1 Spotify.jsx
Create app/Spotify.jsx
Copy from Slack #react_snippets: Spotify.jsx
UIManager.jsx
c6.2 Controlled inputs
Setup Spotify.jsx form inputs as controlled inputs, similar to chapters 2 and 3,
by defining:
also import axios for the fetch request
import axios from 'axios'; class Spotify extends React.Component { constructor(props) { super(props); this.state = { searchTerm: '' } } trackSearchTerm(event) { var searchTerm = event.target.value; this.setState({searchTerm: searchTerm}); } searchSpotify() { axios.get(`/api/songs?search=${this.state.searchTerm}`) .then((res) => {console.log(res.data);}) .catch((err) => { console.log(`[Spotify.jsx] search error: ${err}`); }); } ... |
Search results in the debugger console
c6.3 transforming API’s response data into UI data
So far, this is the item format we have been using:
The object returned by Spotify is too big. We only need: id, artist, title, album.
res.data array can be mapped into a new array that contains only the track attributes we need.
{� "id": "0c4IEciLCDdXEhhKxj4ThA"� "artist": "Muse",� "title": "Madness",� "album": "The 2nd Law",� }
class Spotify extends React.Component { ... searchSpotify() { ... .then((res) => { const searchResults = res.data; const squashedResults = searchResults.map((track) => { return { id: track.id, artist: track.artists[0].name, album: track.album.name, title: track.name }; }); console.log(squashedResults); |
That’s more like it! Now we have to put it in our UI
c6.4 We need a List and Item component...
But wait! We already have one! 🙌
Hooray for reusable components! 🎉
Only thing we have to do here is import and use them in Spotify.jsx
*** Now we have two instances of List, one is Spotify’s List and the old one is UIManager’s List
import List from './List'; class Spotify extends React.Component { ... this.state = { searchTerm: '', searchResults: [], } } ... searchSpotify() { ... // update state with search results this.setState({ searchResults: squashedResults }); }) ... render() { ... <List list={this.state.searchResults} /> |
Nice! Last thing is to add the controls in each Item to add them to our playlist
c6.5 [X] and [+] buttons on Item
To implement adding/removing items from Spotify List, we introduce the following props:
<UIManager>
<Spotify>
<List>
<Item>
Props passing:
<UIManager> toggleItemFromSpotify() ---> <Spotify> ---display_type---> <List> ---display_type---> <Item>
toggleItem() toggleItem()
display_type - allows us to customize Item to have [+] instead of [X] and [Edit] (home list)
toggleItem… - when Item’s [+] is clicked, item object will be passed upwards all the way to UIManager, who can add the item to our state
c6.6 implement toggleItemFromSpotify()
When an Item is clicked, UIManager can either add the item if it doesn’t exist yet, or delete it if it exists already.
UIManager.jsx
c6.7 Denote Item X instead of + if already in playlist
<UIManager>
<Spotify>
<List>
<Item>
Props passing:
<UIManager> --isInStateList()---> <Spotify> --isInStateList()---> <List> --isInStateList---> <Item>
UIManager.jsx
isInStateList() - checks if passed item_id is already in UIManager’s this.state.list
Implementation using some(), similar to logic in toggleItemFromSpotify()
Item.jsx
display [X] instead of [+]
if isInStateList() returns true
...
<div className="add_remove">
<span onClick={() => this.props.toggleItem(item)}>
{ this.props.isInStateList(item.id) ? '❎' : '➕' }
</span>
</div>
...
Some final testing:
Test [+]. Here’s a few sample songs
4uLU6hMCjMI75M1A2tKUQC
7GhIk7Il098yCjg4BQjzvb
0FutrWIUM5Mg3434asiwkp
Test [+ from Spotify].
Try searching and adding; removing already added items in Spotify List
We did it! 😃😃🏆🏆👏👏
We finished the app!
🙌🙌🙌 🎉🎉🎉
Questions?
Need help?
Coming up… FAQ and Advanced Stuff
Let’s make this better!
After the workshop/before you leave
Please answer a short survey about the workshop
Please go to the code repo, and give it a star
(if it deserves one, of course!)
https://github.com/lenmorld/react_workshop
Any mistakes or improvements, please submit an issue
or even better, contribute to open-source! (ping me in Slack for details)
Appendix A: FAQs and tips
Final tip: Beef up your CV with different technologies and projects. You’ll learn a lot and it would help you get you an internship/job!
Appendix B: Intro to Advanced React Concepts
Server stuff
Lifecyle Methods
Children props
Context
Render props
HoC (Higher Order Components)
Hooks
Surprise
Portals
Server stuff
...To learn more about backend/server/API, attend my Node workshop !..
Server setup used:
SSR (Server-side Rendering)
This is our current SPA (Single Page Application) setup:
Although SPA saves on network requests, you might want to consider SSR when
There are ways to combine SPA + SSR:
There are frameworks that provide SSR, routing, and so much more out of the box:
image from https://codeburst.io/using-google-analytics-with-angular-25c93bffaa18+
SSR:
SPA:
Lifecycle methods
Full reference here: https://reactjs.org/docs/react-component.html
Codesandbox: A_Lifecycle_1 https://codesandbox.io/s/5z1rk9776p
image from: https://rangle.github.io/react-training/react-lifecycles/
Prop-drilling and some alternatives
codesandbox: A_prop-drilling https://codesandbox.io/s/w2xyprw937
Prop-drilling - passing down data from ancestor to a child, when the middle ones don’t need it
There are alternatives but they add some complexity to a React app. Thus, complexity vs verbosity. These are ordered in least complex to the most.
Children props
codesandbox: A_children_props https://codesandbox.io/s/rllz7rx2n
Instead of using the <Component /> syntax, we can do
<Component> where <Stuff /> can be any valid JSX
<Stuff />
</Component>
then inside Component render(), we can access <Stuff /> via this.props.children
Perfect for “generic placeholders”, which could be dumb components that just renders whatever the parent passes
e.g. Nav, Body, Container, Sidebar, Card components
Context
codesandbox: A_context_1 https://codesandbox.io/s/q7o3096p5w
React Context API involves the use of a
Redux
HoC and render props
codesandbox: A_hoc-and-render-props https://codesandbox.io/s/0y09y8m14p
These two patterns emerged from the need to effectively reuse code and share data between reusable components.
HoC (Higher Order Components) - a function that takes a component and returns a new component
Render props - a function prop that a (reusable) child component uses to know what to render. The parent component expresses the how.
Portals
codesandbox: A_portals https://codesandbox.io/s/7ylx55z0zx
Allow a parent to render children outside its hierarchy,
i.e. into a DOM node that exists outside the DOM hierarchy of the parent component
Perfect for modals, tooltips, dialogs
Another example: rendering into a different browser window
https://hackernoon.com/using-a-react-16-portal-to-do-something-cool-2a2d627b0202
Suspense
For async fethc requests, we used to do:
if (this.state.memes.length === 0) {
return <div>Loading…</div>
}
But for more complex data fetching and more elegant UX, it is better to use React Suspense. It allows you to defer rendering part of your application tree until some condition is met (e.g. data from an endpoint or a resource is loaded)
An advanced example: https://codesandbox.io/s/k2m12p8nqr
Hooks
Hooks let you use state, lifecycle methods and other class features, with functional/stateless components, i.e. without writing a class
Really good intro here: https://scotch.io/tutorials/getting-started-with-react-hooks
Final words...
This is just the beginning of your React learning…
A few tips:
Let’s make this better!
After the workshop/before you leave
Please answer a short survey about the workshop
Please go to the code repo, and give it a star
(if it deserves one, of course!)
https://github.com/lenmorld/react_workshop
Any mistakes or improvements, please submit an issue
or even better, contribute to open-source! (ping me in Slack for details)
Thank you!!!