Introduction
to
React and Redux
Amanpreet Singh
@apsdehal
Agenda
- Intro to JS Frameworks and their architectures
- React Philosophy and Virtual DOM
- Two way and One way data bindings
- Demo and Code explaining data flow in React
- Redux motivation and architecture principles
- Code changes for Redux
What is JS Framework?
A set of opinionated guidelines and code to model a client side application
Why use a JS Framework?
-
Organization and structure
-
Separation of concerns
-
DRY
-
Performance
-
Security and best practices
Remember
jQuery is not a framework
A library for creating user interfaces
General JS Framework Architecture
Model
Controller
View
Data
Stitching Logic
Template
MVC / MVVM / MV*
Controller, View and Model are often coupled.
When you change one, you often have to change other
React Philosophy
-
Build UI components which are cohesive units
-
UI logic and UI presentation are tightly coupled and should be together
-
Render all UI through JavaScript
Performance Best Practices
-
Avoid expensive DOM operations
-
Don't access DOM unnecessarily
-
Minimize DOM rerendering (tweaks)
-
All updated elements should be inserted into DOM together
More information on my other slide
React
Rerender everything on every update
But won't that be expensive
Enter Virtual DOM
Rerender everything on every update:
-
Generate light description of Component's current state
-
Diff it with old one
-
Compute the minimum changes to be applied to the DOM
-
Apply all the changes in batch updates
-
Efficient update of subtree only
Virtual DOM
Virtual DOM
Data Binding
React has one way data binding
One-way Data Binding
Title text field
let title = "";
Event
Watcher
Two-way Data Binding
Title text field
let title = "";
Watcher
Watcher
Data Binding
React does one job and does it perfectly
JSX
-
JavaScript syntax that looks like XML
-
Helps in writing HTML embedded inreact components
// Input JS
var component = <Viewer backgroundColor="#fff"/>
// Output JS
var component = React.createElement(Viewer, {backgroundColor: '#fff'});
Demos & Code
A simple search application
Init
Use create-react-app to init most projects
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
render() {
return <div>Hello</div>
}
}
Create Components
- Extend React.Component class to create components
- Implement render function and return UI description from it
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
render() {
return <div>Hello</div>
}
}
ReactDOM.render(
<App/>,
document.getElementById("app")
);
Create Components
- Root level component will be attached to DOM element
Component Composition
-
Use components in one another
-
Supports reusability and consistency in UI
Component Composition
class App extends React.Component {
render() {
return (<div>
<SearchBar/>
<Results/>
</div>);
}
}
Component Composition
/* Start SearchBar.js */
class SearchBar extends React.Component {
render() {
return <input/>
}
}
export default SearchBar;
/* End SearchBar.js */
/* Results.js */
class Results extends React.Component {
render() {
return <div>{heroes[0]}</div>
}
}
export default Results;
/* End Results.js */
Let's create some component first
Component Composition
import React from 'react';
import ReactDOM from 'react-dom';
import SearchBar from './Searchbar';
import Results from './Results';
class App extends React.Component {
render() {
return (<div>
<SearchBar/>
<Results/>
</div>);
}
}
Import now
Data Flow
From parent to child. Data flows down in react.
- Passed through props, which can be access using {this.props} in the child
- Props are immutable
Data Flow
/* Child */
class Name extends React.Component {
render() {
return <div>{this.props.firstname} {this.props.lastname}</div>;
}
}
/* Parent */
class Parent extends React.Component {
render() {
return <div>
<Name firstname="Amanpreet" lastname="Singh"/>
</div>
}
|
Reverse Data Flow
From child to parent
- Event handler is passed to child through props by parent
- Child calls the event handler passed on occurence of the respective event
Reverse Data Flow
/* Child */
class Name extends React.Component {
handleClick(e) {
this.props.onClickHandler(e, this.props.firstname + " " + this.props.lastname);
|
render() {
return <div onClick={this.handleClick}>{this.props.firstname} {this.props.lastname}</div>;
}
}
/* Parent */
class Parent extends React.Component {
onClickHandler(e, val) {
console.log(val)
}
render() {
return <div>
<Name
firstname="Amanpreet"
lastname="Singh"
onClickHandler={this.onClickHandler}/>
</div>
}
|
State
-
Similar to props, but private and controlled by component
-
Initialized in constructor, rerenders the component whenever the state changes
-
Props are fixed for lifetime, use state for data that is going to change
-
Remember the watcher we talked about in one way binding?
State
Adding state to parent
class App extends React.Component {
state = {
selectedHeroes: []
};
handleSearch(e) {
let searchValue = e.target.value;
if (searchValue.length === 0) {
this.setState({selectedHeroes: []})
return;
}
let filteredHeroes = heroes.filter((hero) => {
if (hero.indexOf(searchValue) > -1) {
return true;
} else {
return false;
}
});
this.setState({selectedHeroes: filteredHeroes});
}
render() {
return (<div>
<SearchBar handleSearch={this.handleSearch.bind(this)}/>
<Results selectedHeroes={this.state.selectedHeroes}/>
</div>);
}
}
State
SearchBar
class SearchBar extends React.Component {
handleChange(event) {
this.props.handleSearch(event);
}
render() {
return <input type="search" onChange={this.handleChange.bind(this)}/>
}
}
State
Pass state to Results
class Results extends React.Component {
render() {
let results = this.props.selectedHeroes.map((hero, index) => {
return <div key={index}>{hero}</div>;
});
return (
<div>{results}</div>
);
}
}
Presentational and Container Components
-
Better separation of concerns
- Skinny vs Fat controllers
- Eventually, we end up we too much props passing
- This helps with that
- Read more at https://goo.gl/ypJ1TP
Presentational and Container Components
const SearchBar = (props) => {
return <input type="search" onChange={(e) => {props.handleSearch(e)}}/>
}
const Results = (props) => {
let results = props.selectedHeroes.map((hero, index) => {
return <div key={index}>{hero}</div>;
});
return (
<div>{results}</div>
);
}
Redux
Predictable State Container for JavaScript Apps
Motivation
-
As applications become complex, managing state becomes pain
-
Libraries like react remove asynchronicity and direct DOM manipulation but you still need to maintain state
-
State bloats with every increasing demands of a UI platform
Three Principles
- Single source of truth
- State is read only, it can be only changed through actions
- Changes are made through pure functions called reducers
Single Source of Truth
The state of your whole application is stored in an object tree within a single store.
State is read-only
The only way to change the state is to emit an action, an object describing what happened.
Changes are made with pure functions
To specify how the state tree is transformed by actions, you write pure reducers.
These reducers take in old state and an action and return back new state
Basic Principle
(previousState, action) => newState
Data Flow Diagram
Example
Changes
const { combineReducers, createStore, dispatch } = Redux;
const { connect, Provider } = ReactRedux;
Add imports
Add reducers
const selectedHeroesReducer = (state = [], action) => {
switch(action.type) {
case 'UPDATE_RESULTS': {
let searchValue = action.event.target.value;
if (searchValue.length === 0) {
return []
}
let filteredHeroes = heroes.filter((hero) => {
if (hero.indexOf(searchValue) > -1) {
return true;
} else {
return false;
}
});
return filteredHeroes
}
default: {
return state;
}
}
}
const reducers = combineReducers({
selectedHeroes: selectedHeroesReducer
});
Add actions
const actions = {
updateResults: (e) => {
return {
type: 'UPDATE_RESULTS',
event: e
}
}
};
Create Store
let store = createStore(reducers);
Update Presentational Components
const SearchBar = (props) => {
return <input type="search" onChange={(e) => {props.handleSearch(e)}}/>
};
const mapDispatchToPropsForSearchBar = (dispatch) => {
return {
handleSearch: (e) => dispatch(actions.updateResults(e))
}
};
const VisibleSearchBar = connect(null, mapDispatchToPropsForSearchBar)(SearchBar);
const Results = (props) => {
let results = props.selectedHeroes.map((hero, index) => {
return <div key={index}>{hero}</div>;
});
return (
<div>{results}</div>
);
};
const mapStateToProps = (state) => {
return {
selectedHeroes: state.selectedHeroes
}
};
const VisibleResults = connect(mapStateToProps)(Results);
Update Container Components
class App extends React.Component {
render() {
return (<div>
<VisibleSearchBar/>
<VisibleResults/>
</div>);
}
}
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById("app")
);
Thanks for your attention!
Questions?
Introduction to React and Redux
By Amanpreet Singh
Introduction to React and Redux
- 902