React
History
- jQuery
- Application lives in DOM
- Update state -> traverse DOM to find node to update, update it
- shared mutable state was a bad idea
- Spaghetti of mutations which were hard to predict and keep track of
- Backbone.js
- MVC
- State lived inside models
- When a model changed all views using it would re-render
- Angular.js
- Two way data binding
- Has to constantly scan app looking for changes in order to re-render
- Implicit state changes hard to follow and debug
- Data filters
- Routing
- Controllers
- Dependency injection
- Templates
- React
- View is a function of the application's state
- v = f(s)
- Separation of concerns
- Historically HTML is separate from CSS which is separate from JS
- React: they're just different technologies, not concerns
- JSX combines HTML and JS
- Tradeoffs
- Large bundle sizes
- Whenever state is needed in multiple components have to
- Raise it
- Use contexts
- Use library like Redux
- Was treated more as a UI primitive than an application platform
- Next.js
Npm
Webpack
- Module bundler
- Looks at all modules, creates dependency graph, put them into bundles that
index.html
can reference
- Installing
npm install webpack webpack-cli --save-dev
-
webpack.config.js
- Exports an object with settings
-
module.exports = {};
- Needs
- Entry point
- Transformations to make
- Location to put bundles
- Entry
- Transformations with loaders
- Out of the box webpack only knows how to process js, json
- Want to import css, svg, etc...
module.exports = {
...
module {
rules: []
}
}
- Rule: type of file to run on, loader to use
npm install svg-inline-loader --save-dev
{test: /\.svg$/, use: "svg-inline-loader" }
npm install css-loader --save-dev
{test: /\.css$/, use: "css-loader"}
- want to import css and have webpack put it into a style tag
npm install style-loader --save-dev
{test: /\.css$/, use: ["style-loader", "css-loader"]}
- Note:
style-loader
before css-loader
- Newer javascript for older browsers
npm install babel-loader --save-dev
{test: /\.(js)$/, use: "babel-loader"}
- Transformations list
- Output
output: {
path: path.resolve(__dirname, "dist"),
filename: "index_bundle.js",
}
- Grabs entry point
- Examines import and require statements to create dependency graph
- Starts creating bundle, if a path has a loader it will be run
- Takes final bundle and outputs it to
dist/index_bundle.js
- Plugins
- Mode
- Development, production
-
mode: "production"
- Can remove EnvironmentPlugin, Webpack will automatically set that env
- Running
- Production vs deployment
- Webpack DevServer
JSX tips and gotchas
- Variables
<h1>Hello, {name}</h1>
- Rendering nothing
- if/else
if(firstLogin === true) return <h1>Welcome</h1>
else return <h1> Welcome back </h1>
- Ternary operator
return firstLogin ? <h1>Welcome</h1> : <h1>Welcome back</h1>
{showWarning() == true ? <Warning/> : null}
- Logical
&&
{showWarning() && <Warning />}
- React fragments
- Capitalization
- Components are capitalised to distinguish from tags
Elements vs Components
- Element
- Describes what you see on the screen
- Object representation of a DOM node
const element = React.createElement("div", {id: "login-btn"}, "Login");
- Arguments: tag string, attributes, children
- Returns an object like
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
- Renders to a dom node like
<div id='login-btn'>Login</div>
- Component
- Function or class which optionally accepts input and returns a React element
function Button({onLogin}) {
return React.createElement("div", {id: "login-btn", onClick: onLogin}, "Login");
}
- Button component accepts and onLogin input and returns a React element
- onLogin is a prop
- pass along as 2nd arg to createElement
- If react sees a class or function as 1st arg it will check to see what it renders give the props
const element = React.createEement(User, {name: "Tyler"}, null);
- Creates a full representation of the dom like above
- Called reconciliation
- Triggered on every state change
Props
Lists
<ul id="tweets">
{tweets.map((tweet) => (
<li>{tweet.text}</li>
))}
</ul>
Managing state in React
- Adding state
- Updating state
Function Components
Prop types
Component lifecycle
- Components
- Manage their own state
- Receive data via prop
- Describe their UI
- But end up having more responsibilities
- Ajax requests
- Setting and removing listeners
- Reacting to new props
- Lifecycle
- Added to DOM (mounting)
- Updates its state or receives new data via props (updating)
- Gets removed from DOM (unmounting)
- Mounting (in order they occur)
- Set the components initial state
- Render a DOM node
- Render method
- Pure function, looks at state,props and returns a description of the UI
- Make an Ajax request
-
componentDidMount
- Invoked once when first mounted
- Set up listeners (e.g. websockets)
- Updating
- Re-render UI with updated props, state
- Render also invoked when state changes via
setState
or when it receives new updated props
- Re-fetch data
componentDidUpdate
method
- Invoked when state changes or receives new props
- But not on initial render
- Passed previous props and previous state
- Can compare to decide if need to do anything
- Re-set a listener
- Unmounting
- Remove listeners
-
componentWillUnmount
- Other methods
getDerivedStateFromProps
, shouldComponentUpdate
, getSnapshotBeforeUpdate
Controlled vs Uncontrolled Components
- React: state lives inside components, not dom
- Traditional forms: state lives in DOM (input fields)
- Controlled component: the 'react way', state lives inside components state, value of input is what's in component
- Uncontrolled: no components state, lives inside DOM
- Controlled
handleChange
on input, to update value in react
- value on input set to state
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
email: e.target.value,
});
}
handleSubmit() {
alert("The email is " + this.state.email);
}
render() {
return (
<div>
<pre>The email is {this.state.email}</pre>
<br />
<input
type="text"
placeholder="Email"
value={this.state.email}
onChange={this.handleChange}
/>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
- Uncontrolled
React children
- Can pass data to a html tag within opening closing tags as well as props
- Child tags in react:
props.children
Default props
- Static property
defaultProps
- Object: keys are props being passed, values default values
- ES6
- Default function parameters
- Use with destructuring to set default props for function components
Loading component
- Change text every x ms, to change number of dots
- Loading, Loading., Loading.., Loading...
- On
comopnentDidMount
- Register handler
window.setInterval
with closure which sets the state
- On
componentWillUnmount
- Need to deregister window handler
- Better to not show loading indicator right away
- Will flash indicator if content loads fast
- Delayed component which decides whether to show or hide - toggles on window.setTimeout
Higher order components
Render props
- May have wrapper components which handle some logic and render other components which they pass data
- Can pass a function that returns UI as a prop and call from render
- Can use this.props.children as a function
- Function inside component tags
- No inversion of control
- We render the components in the render function passed in
class Hover extends React.Component {
constructor(props) {
super(props)
this.state = {
hovering: false
}
this.mouseOver = this.mouseOver.bind(this)
this.mouseOut = this.mouseOut.bind(this)
}
mouseOver() {
this.setState({ hovering: true })
}
mouseOut() {
this.setState({ hovering: false })
}
render() {
return (
<div
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
>
{this.props.children(this.state.hovering)}
</div>
)
}
}
<Hover> {(hovering) => {return <Info anyNameWeWant={hovering} />;}} </Hover>
React context
- State shared between components at multiple levels of the tree
- Recommended solution
- Move state up to nearest common parent and pass it down via props
- Can become unmanageable
- Context
- Declare data that needs to be available throughout the component tree
- Need any component to be able to subscribe to it
const MyContext = React.createContext()
<MyContext.Provider value=...> </MyContext.Provider>
- Value prop with the data to be made available to children
<MyContext.Consumer> {(data) => {return (...); }} </MyContext.Consumer>
- Changing context state
- Can put a function on the value object which references this to change state
- Performance
- If we assign the value object every render() every component using the context will be redrawn since the component has changed
- Wrap in a component and use the component's state as the value, and set state to change
- Can put the change function on the state to be referenced by other components
- Object isn't being recreated so fixes the problem
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
locale: 'en'
}
}
render() {
return (
<LocaleContext.Provider
value={{
locale: this.state.locale,
toggleLocale: : () => {
this.setState(({ locale }) => ({
locale: locale === "en" ? "es" : "en"
}));
}
}}>
<Home />
</LocaleContext.Provider>
)
}
}
- Default value
- Provider gets value from the nearest Provider components of the context
- If there isn't a parent it uses the first argument to createContext
React router
- BrowserRouter component
- Keeps track of application's browsing history
- Makes it available with contexts
- Probably put it at the root
- Also MemoryRouter and StaticRouter
- Route component
- Routes component
- Looks at every child Route and renders the best match
- Should always wrap
Route
components with Routes
- Link component
<Link to="/">Home</Link>
Query strings
Class fields
- Adds instance properties directly on class without using constructor
- Adds static fields, so propTypes and defaultProps can go in the class definition
- Arrow functions don't have their own
this
, so can use those for methods instead of binding
- NOTE: function object will be created per instance rather than per class
Code splitting
Building react apps for production
- Need to tell webpack to run in production mode
- Minify code
- Set process.env.NODE_ENV to production
-
package.json
"scripts": {
"dev": "webpack serve",
"build": "NODE_ENV='production' webpack"
}
-
webpack.config.js
mode: process.env.NODE_ENV === 'production'
? 'production'
: 'development'