SlideShare ist ein Scribd-Unternehmen logo
1 von 92
Managing State in React
Applications with RxJS
James Wright
Software Developer, YLD
Hello!
I’m James!
I’m a developer who works with various
technologies and languages, but primarily
JavaScript and C#
I’m a developer who works with various
technologies and languages, but primarily
JavaScript and C#
I have gained experience at the likes of Sky,
Channel 4, Trainline, News UK, and NET-A-
PORTER
I’m a developer who works with various
technologies and languages, but primarily
JavaScript and C#
I have gained experience at the likes of Sky,
Channel 4, Trainline, News UK, and NET-A-
PORTER
I love open-source software, writing, speaking,
and mentoring
I work at YLD
I work at YLD
We’re a software engineering
and design consultancy
I work at YLD
We’re a software engineering
and design consultancy
We specialise in JavaScript,
TypeScript, Node.js, React,
DevOps, and beyond!
Time for some background
Image: https://www.pexels.com/photo/background-blur-clean-clear-531880/
Recently, I have been contributing to a
web app.
On the client, it uses:
On the client, it uses:
● React for rendering the DOM
On the client, it uses:
● React for rendering the DOM
● Redux for state management
On the client, it uses:
● React for rendering the DOM
● Redux for state management
● RxJS for reactive data transformation
and aggregation
On the client, it uses:
● React for rendering the DOM
● Redux for state management
● RxJS for reactive data transformation
and aggregation
● Redux Observable for expressing
async Redux actions with RxJS
That sounds like a lot to juggle! How
does that even look?!
I’ll show you, but let’s firstly take a step
back.
What is RxJS?
“RxJS is a library for [functional], reactive
programming using Observables, to
make it easier to compose
asynchronous or callback-based code.” -
the RxJS team
Right. How does that translate to code?
import { webSocket } from 'rxjs/webSocket';
import { map, scan } from 'rxjs/operators';
interface StreamedMessage {
username: string;
message: string;
}
webSocket<StreamedMessage>('wss://localhost:8080/rt')
.pipe(
map(({ message }) => message),
scan<string>((messages, incomingMessage) => [
...messages,
...incomingMessage,
], []),
)
.subscribe(renderMessages);
import { webSocket } from 'rxjs/webSocket';
import { map, scan } from 'rxjs/operators';
interface StreamedMessage {
username: string;
message: string;
}
webSocket<StreamedMessage>('wss://localhost:8080/rt')
.pipe(
map(({ message }) => message),
scan<string>((messages, incomingMessage) => [
...messages,
...incomingMessage,
], []),
)
.subscribe(renderMessages);
Courtesy of rxviz.com
import { fromEvent, concat } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { switchMap, map } from 'rxjs/operators';
const submitButton = document.querySelector<HTMLButtonElement>('.button');
const loadStory = fromEvent<HTMLButtonElement>(submitButton, 'click');
loadStory.pipe(
switchMap(({ dataset: { id } }) =>
concat(
ajax(`/api/story/${id}`),
ajax(`/api/story/${id}/comments`),
),
),
map(({ response }) => response),
).subscribe(response =>
renderComponentFromData(response),
);
import { fromEvent, concat } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { switchMap, map } from 'rxjs/operators';
const submitButton = document.querySelector<HTMLButtonElement>('.button');
const loadStory = fromEvent<HTMLButtonElement>(submitButton, 'click');
loadStory.pipe(
switchMap(({ dataset: { id } }) =>
concat(
ajax(`/api/story/${id}`),
ajax(`/api/story/${id}/comments`),
),
),
map(({ response }) => response),
).subscribe(response =>
renderComponentFromData(response),
);
So I can generate, consume, and map
streams of data, without side effects,
using composable operators?
Yup! Pretty neat, right?
You can also write your own
observables!
import { Observable } from 'rxjs';
const createMessageSource = () =>
new Observable(observer => {
const socket = new WebSocket('wss://localhost:8081/messages'); // Producer
socket.onmessage = message => observer.next(message);
socket.onclose = () => observer.complete();
socket.onerror = e => observer.error(e);
// clean-up code ran when one unsubscribes from observable
observer.add(() => {
socket.onmessage = null;
socket.onclose = null;
socket.onerror = null;
socket.close();
});
});
import { Observable } from 'rxjs';
const createMessageSource = () =>
new Observable(observer => {
const socket = new WebSocket('wss://localhost:8081/messages'); // Producer
socket.onmessage = message => observer.next(message);
socket.onclose = () => observer.complete();
socket.onerror = e => observer.error(e);
// clean-up code ran when one unsubscribes from observable
observer.add(() => {
socket.onmessage = null;
socket.onclose = null;
socket.onerror = null;
socket.close();
});
});
(This is a cold observable)
const socket = new WebSocket('wss://localhost:8081/messages');
const createMessageSource = () =>
new Observable(observer => {
const onMessage = (message: MessageEvent) => observer.next(message);
const onClose = () => observer.complete();
const onError = (e: Event) => observer.error(e);
socket.addEventListener('message', onMessage);
socket.addEventListener('close', onClose);
socket.addEventListener('message', onError);
observer.add(() => {
socket.removeEventListener('message', onMessage);
socket.removeEventListener('close', onClose);
socket.removeEventListener('error', onError);
});
});
const socket = new WebSocket('wss://localhost:8081/messages');
const createMessageSource = () =>
new Observable(observer => {
const onMessage = (message: MessageEvent) => observer.next(message);
const onClose = () => observer.complete();
const onError = (e: Event) => observer.error(e);
socket.addEventListener('message', onMessage);
socket.addEventListener('close', onClose);
socket.addEventListener('message', onError);
observer.add(() => {
socket.removeEventListener('message', onMessage);
socket.removeEventListener('close', onClose);
socket.removeEventListener('error', onError);
});
});
(This is a hot observable)
Great, but what if I need to push data to
subscribers from elsewhere?
RxJS has subjects!
RxJS has subjects!
(It’s an observable + observer, all in one
lovely API surface)
import { Subject } from 'rxjs';
import { map, scan } from 'rxjs/operators';
const messageSource = new Subject<StreamedMessage>();
messageSource.pipe(
map(({ message }) => message),
scan<string>((messages, incomingMessage) => [
...messages,
incomingMessage,
], []),
).subscribe(renderMessages);
messageSource.next({ username: 'Bob', message: 'Hi!' });
messageSource.next({ username: 'Peter', message: 'Hey!' });
How can I consume observables in my
React app?
How About Redux Observable?
import { Action } from 'redux';
interface State {
messages: string[];
isLoading: boolean;
hasError: boolean;
}
interface MessageAction extends Action {
messages: string[];
}
import { Action } from 'redux';
const reducer = (state: State, action: Action): State => {
if (isActionOfType(action, 'REQUEST_MESSAGES')) {
return {
...state,
isLoading: true,
hasError: false,
};
}
if (isActionOfType<MessageAction>(action, 'RECEIVE_MESSAGES')) {
return {
...state,
isLoading: false,
hasError: false,
messages: action.messages,
};
}
return state;
};
import { Action } from 'redux';
import { Observable, of, concat } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { filter, catchError, switchMap } from 'rxjs/operators';
const fetchMessagesEpic = (
actionSource: Observable<Action>,
) =>
actionSource.pipe(
filter(action => action.type === 'FETCH_MESSAGES'),
switchMap(() => concat(
of({ type: 'REQUEST_MESSAGES' }),
ajax('/api/messages').pipe(
switchMap(({ response }) => response),
switchMap(messages => of({
type: 'RECEIVE_MESSAGES',
messages,
})),
),
)),
catchError(() => of({ type: 'FETCH_ERROR' })),
);
import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
const epicMiddleware = createEpicMiddleware();
const store = createStore(
reducer,
applyMiddleware(epicMiddleware),
);
epicMiddleware.run(fetchMessagesEpic);
store.dispatch({ type: 'FETCH_MESSAGES' });
Great, so I can use RxJS to control my
Redux state, which I can render with
React!
But...
...I have to use it alongside Redux and
Redux Observable.
What if I could just use RxJS for
everything?
Well, I tried just that. Let me show you.
Disclaimer: it’s a proof-of-concept.
Live demo!
import * as React from 'react';
import MessageList from './MessageList';
import MessageForm from './MessageForm';
import Status from './Status';
export default () => (
<>
<Status />
<MessageForm />
<MessageList />
</>
);
import * as React from 'react';
import { appState, defaultState, State } from '../state';
import connectToObservable from './connectToObservable';
export const MessageList = ({ messages }: Pick<State, 'messages'>) => (
<ul>
{messages.map((message, i) => <li key={i}>{message}</li>)}
</ul>
);
export default connectToObservable(appState, defaultState)(MessageList);
import * as React from 'react';
import { appState, defaultState, State } from '../state';
import connectToObservable from './connectToObservable';
export const MessageList = ({ messages }: Pick<State, 'messages'>) => (
<ul>
{messages.map((message, i) => <li key={i}>{message}</li>)}
</ul>
);
export default connectToObservable(appState, defaultState)(MessageList);
export interface State {
messages: string[];
isFormValid: boolean;
isLoadingQuote: boolean;
hasQuoteError: boolean;
}
export const defaultState: State = {
messages: [],
isFormValid: true,
isLoadingQuote: false,
hasQuoteError: false,
};
export const appState = new BehaviorSubject<State>(defaultState);
import * as React from 'react';
import { Observable } from 'rxjs';
const connectToObservable = <TState, TProps = {}>(
observable: Observable<TState>,
defaultState: TState,
) =>
(Component: React.ComponentType<TProps & TState>) =>
(props: TProps) => {
const [state, setState] = React.useState(defaultState);
React.useEffect(() => {
const subscription = observable.subscribe(setState);
return () => {
subscription.unsubscribe();
};
}, []);
return <Component {...props} {...state} />;
};
Reading state is cool, but how can we
update it?
const [message, setMessage] = useState('');
return (
<section>
<h2>Add a Message</h2>
<form
name="message-form"
onSubmit={e => {
e.preventDefault();
toNextState(addMessage(message));
}}
>
<input
className="form-input"
type="text"
name="message"
placeholder="Your comment"
onChange={e => setMessage(e.currentTarget.value)}
export type Action<TPayload> = (payload?: TPayload) => Reducer;
export type Reducer = (currentState: State) => Observable<State>;
export const withState = (reducer: Reducer) =>
appState
.pipe(
take(1),
switchMap(state => reducer(state)),
);
export const toNextState = (reducer: Reducer) => {
const sequence = withState(reducer);
sequence.subscribe(newState => appState.next(newState));
return sequence;
};
import { of } from 'rxjs';
import { State, withState } from './state';
export const addMessage = (message: string) =>
(currentState: State) =>
of({
...currentState,
isFormValid: !!message.length,
isLoadingQuote: false,
hasQuoteError: false,
messages: [
...(message.length ? [message] : []),
...currentState.messages,
],
});
Why do reducers have to return
observables?
So we can express asynchronous
effects!
placeholder="Your comment"
onChange={e => setMessage(e.currentTarget.value)}
/>
<input
className="form-input form-button"
type="submit"
value="Add"
/>
<button
className="form-input form-button"
type="button"
name="add-quote"
disabled={isLoadingQuote}
onClick={() => toNextState(addRonSwansonQuote())}
>
Add Ron Swanson quote
</button>
</form>
export const addRonSwansonQuote = () =>
() =>
concat(
withState(onQuoteLoading()),
ajax.getJSON<string[]>('https://ron-swanson-quotes.herokuapp.com/v2/quotes')
.pipe(
switchMap(([quote]) => withState(addMessage(quote))),
catchError(() => withState(onQuoteError())),
),
);
So we can use the same paradigms to:
So we can use the same paradigms to:
● create and query data sources
So we can use the same paradigms to:
● create and query data sources
● combine said sources
So we can use the same paradigms to:
● create and query data sources
● combine said sources
● reduce actions into a single source of
truth
So we can use the same paradigms to:
● create and query data sources
● combine said sources
● reduce actions into a single source of
truth
● consume this source of truth in React
But how does this perform compared to
Redux?
But how does this perform compared to
Redux? useReducer?
useReducer
useReducer
RxJS
RxJS
RxJS
RxJS
So we’re potentially hampering
performance.
Someone: “But I like the approach. How
do I go about ditching Redux?”
Well, you probably shouldn’t.
RxJS can be overkill for simpler
applications.
Plus Redux provides a solid, opinionated
architecture.
Someone else: “Isn’t MobX built around
observables? Why not just use that?”
It is, and it’s great, but its observables
are not compatible with RxJS.
Summary
Summary
● RxJS provides a functional, declarative means
of consuming and aggregating data sources
Summary
● RxJS provides a functional, declarative means
of consuming and aggregating data sources
● Redux Observable is nice, but we can cut out
the middle man using HOCs and Hooks
Summary
● RxJS provides a functional, declarative means
of consuming and aggregating data sources
● Redux Observable is nice, but we can cut out
the middle man using HOCs and Hooks
● However, Redux provides a (positively)
opinionated architecture
Summary
● RxJS provides a functional, declarative means
of consuming and aggregating data sources
● Redux Observable is nice, but we can cut out
the middle man using HOCs and Hooks
● However, Redux provides a (positively)
opinionated architecture
● How does this perform compared to Redux?
Thanks For Listening!
@jamesseanwright
@YLDio
jamesseanwright
jamesswright.co.uk
yld.io
Icons by Alfredo Hernandez (https://www.iconfinder.com/AlfredoHernandez)
Slides: https://bit.ly/reactrxslides
Repo: https://bit.ly/reactrxrepo

Weitere ähnliche Inhalte

Was ist angesagt?

Javascript: the important bits
Javascript: the important bitsJavascript: the important bits
Javascript: the important bits
Chris Saylor
 

Was ist angesagt? (20)

Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the masters
 
Workshop 5: JavaScript testing
Workshop 5: JavaScript testingWorkshop 5: JavaScript testing
Workshop 5: JavaScript testing
 
Why Redux-Observable?
Why Redux-Observable?Why Redux-Observable?
Why Redux-Observable?
 
Debugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionDebugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 Version
 
Javascript: the important bits
Javascript: the important bitsJavascript: the important bits
Javascript: the important bits
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
 
JavaScript Promise
JavaScript PromiseJavaScript Promise
JavaScript Promise
 
Teaching Your Machine To Find Fraudsters
Teaching Your Machine To Find FraudstersTeaching Your Machine To Find Fraudsters
Teaching Your Machine To Find Fraudsters
 
Workshop 1: Good practices in JavaScript
Workshop 1: Good practices in JavaScriptWorkshop 1: Good practices in JavaScript
Workshop 1: Good practices in JavaScript
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
 
Nubilus Perl
Nubilus PerlNubilus Perl
Nubilus Perl
 
ES6 is Nigh
ES6 is NighES6 is Nigh
ES6 is Nigh
 
Perl Web Client
Perl Web ClientPerl Web Client
Perl Web Client
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
$q and Promises in AngularJS
$q and Promises in AngularJS $q and Promises in AngularJS
$q and Promises in AngularJS
 
ES6, 잘 쓰고 계시죠?
ES6, 잘 쓰고 계시죠?ES6, 잘 쓰고 계시죠?
ES6, 잘 쓰고 계시죠?
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript
 
How to stand on the shoulders of giants
How to stand on the shoulders of giantsHow to stand on the shoulders of giants
How to stand on the shoulders of giants
 
Frontin like-a-backer
Frontin like-a-backerFrontin like-a-backer
Frontin like-a-backer
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 

Ähnlich wie Managing State in React Apps with RxJS by James Wright at FrontCon 2019

Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010
Ismael Celis
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
Tom Croucher
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Tatsuhiko Miyagawa
 
Creating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todayCreating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of today
gerbille
 
SockJS Intro
SockJS IntroSockJS Intro
SockJS Intro
Ngoc Dao
 

Ähnlich wie Managing State in React Apps with RxJS by James Wright at FrontCon 2019 (20)

Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010
 
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
 
Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016
 
NodeJS "Web en tiempo real"
NodeJS "Web en tiempo real"NodeJS "Web en tiempo real"
NodeJS "Web en tiempo real"
 
Node.js server-side rendering
Node.js server-side renderingNode.js server-side rendering
Node.js server-side rendering
 
RESTful API In Node Js using Express
RESTful API In Node Js using Express RESTful API In Node Js using Express
RESTful API In Node Js using Express
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
 
Server side data sync for mobile apps with silex
Server side data sync for mobile apps with silexServer side data sync for mobile apps with silex
Server side data sync for mobile apps with silex
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. REST
 
Speed up your Web applications with HTML5 WebSockets
Speed up your Web applications with HTML5 WebSocketsSpeed up your Web applications with HTML5 WebSockets
Speed up your Web applications with HTML5 WebSockets
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
 
Socket.IO
Socket.IOSocket.IO
Socket.IO
 
Creating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todayCreating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of today
 
SockJS Intro
SockJS IntroSockJS Intro
SockJS Intro
 
Event-driven IO server-side JavaScript environment based on V8 Engine
Event-driven IO server-side JavaScript environment based on V8 EngineEvent-driven IO server-side JavaScript environment based on V8 Engine
Event-driven IO server-side JavaScript environment based on V8 Engine
 

Mehr von DevClub_lv

Mehr von DevClub_lv (20)

Fine-tuning Large Language Models by Dmitry Balabka
Fine-tuning Large Language Models by Dmitry BalabkaFine-tuning Large Language Models by Dmitry Balabka
Fine-tuning Large Language Models by Dmitry Balabka
 
"Infrastructure and AWS at Scale: The story of Posti" by Goran Gjorgievski @ ...
"Infrastructure and AWS at Scale: The story of Posti" by Goran Gjorgievski @ ..."Infrastructure and AWS at Scale: The story of Posti" by Goran Gjorgievski @ ...
"Infrastructure and AWS at Scale: The story of Posti" by Goran Gjorgievski @ ...
 
From 50 to 500 product engineers – data-driven approach to building impactful...
From 50 to 500 product engineers – data-driven approach to building impactful...From 50 to 500 product engineers – data-driven approach to building impactful...
From 50 to 500 product engineers – data-driven approach to building impactful...
 
Why is it so complex to accept a payment? by Dmitry Buzdin from A-Heads Consu...
Why is it so complex to accept a payment? by Dmitry Buzdin from A-Heads Consu...Why is it so complex to accept a payment? by Dmitry Buzdin from A-Heads Consu...
Why is it so complex to accept a payment? by Dmitry Buzdin from A-Heads Consu...
 
Do we need DDD? by Jurijs Čudnovskis from “Craftsmans Passion” at Fintech foc...
Do we need DDD? by Jurijs Čudnovskis from “Craftsmans Passion” at Fintech foc...Do we need DDD? by Jurijs Čudnovskis from “Craftsmans Passion” at Fintech foc...
Do we need DDD? by Jurijs Čudnovskis from “Craftsmans Passion” at Fintech foc...
 
Network security with Azure PaaS services by Erwin Staal from 4DotNet at Azur...
Network security with Azure PaaS services by Erwin Staal from 4DotNet at Azur...Network security with Azure PaaS services by Erwin Staal from 4DotNet at Azur...
Network security with Azure PaaS services by Erwin Staal from 4DotNet at Azur...
 
Using Azure Managed Identities for your App Services by Jan de Vries from 4Do...
Using Azure Managed Identities for your App Services by Jan de Vries from 4Do...Using Azure Managed Identities for your App Services by Jan de Vries from 4Do...
Using Azure Managed Identities for your App Services by Jan de Vries from 4Do...
 
SRE (service reliability engineer) on big DevOps platform running on the clou...
SRE (service reliability engineer) on big DevOps platform running on the clou...SRE (service reliability engineer) on big DevOps platform running on the clou...
SRE (service reliability engineer) on big DevOps platform running on the clou...
 
Emergence of IOT & Cloud – Azure by Narendra Sharma at Cloud focused 76th Dev...
Emergence of IOT & Cloud – Azure by Narendra Sharma at Cloud focused 76th Dev...Emergence of IOT & Cloud – Azure by Narendra Sharma at Cloud focused 76th Dev...
Emergence of IOT & Cloud – Azure by Narendra Sharma at Cloud focused 76th Dev...
 
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
 
Building resilient frontend architecture by Monica Lent at FrontCon 2019
Building resilient frontend architecture by Monica Lent at FrontCon 2019Building resilient frontend architecture by Monica Lent at FrontCon 2019
Building resilient frontend architecture by Monica Lent at FrontCon 2019
 
Things that every JavaScript developer should know by Rachel Appel at FrontCo...
Things that every JavaScript developer should know by Rachel Appel at FrontCo...Things that every JavaScript developer should know by Rachel Appel at FrontCo...
Things that every JavaScript developer should know by Rachel Appel at FrontCo...
 
In the Trenches During a Software Supply Chain Attack by Mitch Denny at Front...
In the Trenches During a Software Supply Chain Attack by Mitch Denny at Front...In the Trenches During a Software Supply Chain Attack by Mitch Denny at Front...
In the Trenches During a Software Supply Chain Attack by Mitch Denny at Front...
 
Software Decision Making in Terms of Uncertainty by Ziv Levy at FrontCon 2019
Software Decision Making in Terms of Uncertainty by Ziv Levy at FrontCon 2019Software Decision Making in Terms of Uncertainty by Ziv Levy at FrontCon 2019
Software Decision Making in Terms of Uncertainty by Ziv Levy at FrontCon 2019
 
V8 by example: A journey through the compilation pipeline by Ujjwas Sharma at...
V8 by example: A journey through the compilation pipeline by Ujjwas Sharma at...V8 by example: A journey through the compilation pipeline by Ujjwas Sharma at...
V8 by example: A journey through the compilation pipeline by Ujjwas Sharma at...
 
Bridging the gap between UX and development - A Storybook by Marko Letic at F...
Bridging the gap between UX and development - A Storybook by Marko Letic at F...Bridging the gap between UX and development - A Storybook by Marko Letic at F...
Bridging the gap between UX and development - A Storybook by Marko Letic at F...
 
Case-study: Frontend in Cybersecurity by Ruslan Zavacky by FrontCon 2019
Case-study: Frontend in Cybersecurity by Ruslan Zavacky by FrontCon 2019Case-study: Frontend in Cybersecurity by Ruslan Zavacky by FrontCon 2019
Case-study: Frontend in Cybersecurity by Ruslan Zavacky by FrontCon 2019
 
Building next generation PWA e-commerce frontend by Raivis Dejus at FrontCon ...
Building next generation PWA e-commerce frontend by Raivis Dejus at FrontCon ...Building next generation PWA e-commerce frontend by Raivis Dejus at FrontCon ...
Building next generation PWA e-commerce frontend by Raivis Dejus at FrontCon ...
 
Parcel – your next web application bundler? by Janis Koselevs at FrontCon 2019
Parcel – your next web application bundler? by Janis Koselevs at FrontCon 2019Parcel – your next web application bundler? by Janis Koselevs at FrontCon 2019
Parcel – your next web application bundler? by Janis Koselevs at FrontCon 2019
 
AAA 3D GRAPHICS ON THE WEB WITH REACTJS + BABYLONJS + UNITY3D by Denis Radin ...
AAA 3D GRAPHICS ON THE WEB WITH REACTJS + BABYLONJS + UNITY3D by Denis Radin ...AAA 3D GRAPHICS ON THE WEB WITH REACTJS + BABYLONJS + UNITY3D by Denis Radin ...
AAA 3D GRAPHICS ON THE WEB WITH REACTJS + BABYLONJS + UNITY3D by Denis Radin ...
 

Kürzlich hochgeladen

Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 

Kürzlich hochgeladen (20)

[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontology
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 

Managing State in React Apps with RxJS by James Wright at FrontCon 2019

  • 1. Managing State in React Applications with RxJS James Wright Software Developer, YLD
  • 4. I’m a developer who works with various technologies and languages, but primarily JavaScript and C#
  • 5. I’m a developer who works with various technologies and languages, but primarily JavaScript and C# I have gained experience at the likes of Sky, Channel 4, Trainline, News UK, and NET-A- PORTER
  • 6. I’m a developer who works with various technologies and languages, but primarily JavaScript and C# I have gained experience at the likes of Sky, Channel 4, Trainline, News UK, and NET-A- PORTER I love open-source software, writing, speaking, and mentoring
  • 7. I work at YLD
  • 8. I work at YLD We’re a software engineering and design consultancy
  • 9. I work at YLD We’re a software engineering and design consultancy We specialise in JavaScript, TypeScript, Node.js, React, DevOps, and beyond!
  • 10.
  • 11.
  • 12. Time for some background Image: https://www.pexels.com/photo/background-blur-clean-clear-531880/
  • 13. Recently, I have been contributing to a web app.
  • 14. On the client, it uses:
  • 15. On the client, it uses: ● React for rendering the DOM
  • 16. On the client, it uses: ● React for rendering the DOM ● Redux for state management
  • 17. On the client, it uses: ● React for rendering the DOM ● Redux for state management ● RxJS for reactive data transformation and aggregation
  • 18. On the client, it uses: ● React for rendering the DOM ● Redux for state management ● RxJS for reactive data transformation and aggregation ● Redux Observable for expressing async Redux actions with RxJS
  • 19. That sounds like a lot to juggle! How does that even look?!
  • 20. I’ll show you, but let’s firstly take a step back.
  • 22. “RxJS is a library for [functional], reactive programming using Observables, to make it easier to compose asynchronous or callback-based code.” - the RxJS team
  • 23. Right. How does that translate to code?
  • 24. import { webSocket } from 'rxjs/webSocket'; import { map, scan } from 'rxjs/operators'; interface StreamedMessage { username: string; message: string; } webSocket<StreamedMessage>('wss://localhost:8080/rt') .pipe( map(({ message }) => message), scan<string>((messages, incomingMessage) => [ ...messages, ...incomingMessage, ], []), ) .subscribe(renderMessages);
  • 25. import { webSocket } from 'rxjs/webSocket'; import { map, scan } from 'rxjs/operators'; interface StreamedMessage { username: string; message: string; } webSocket<StreamedMessage>('wss://localhost:8080/rt') .pipe( map(({ message }) => message), scan<string>((messages, incomingMessage) => [ ...messages, ...incomingMessage, ], []), ) .subscribe(renderMessages); Courtesy of rxviz.com
  • 26. import { fromEvent, concat } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { switchMap, map } from 'rxjs/operators'; const submitButton = document.querySelector<HTMLButtonElement>('.button'); const loadStory = fromEvent<HTMLButtonElement>(submitButton, 'click'); loadStory.pipe( switchMap(({ dataset: { id } }) => concat( ajax(`/api/story/${id}`), ajax(`/api/story/${id}/comments`), ), ), map(({ response }) => response), ).subscribe(response => renderComponentFromData(response), );
  • 27. import { fromEvent, concat } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { switchMap, map } from 'rxjs/operators'; const submitButton = document.querySelector<HTMLButtonElement>('.button'); const loadStory = fromEvent<HTMLButtonElement>(submitButton, 'click'); loadStory.pipe( switchMap(({ dataset: { id } }) => concat( ajax(`/api/story/${id}`), ajax(`/api/story/${id}/comments`), ), ), map(({ response }) => response), ).subscribe(response => renderComponentFromData(response), );
  • 28. So I can generate, consume, and map streams of data, without side effects, using composable operators?
  • 30. You can also write your own observables!
  • 31. import { Observable } from 'rxjs'; const createMessageSource = () => new Observable(observer => { const socket = new WebSocket('wss://localhost:8081/messages'); // Producer socket.onmessage = message => observer.next(message); socket.onclose = () => observer.complete(); socket.onerror = e => observer.error(e); // clean-up code ran when one unsubscribes from observable observer.add(() => { socket.onmessage = null; socket.onclose = null; socket.onerror = null; socket.close(); }); });
  • 32. import { Observable } from 'rxjs'; const createMessageSource = () => new Observable(observer => { const socket = new WebSocket('wss://localhost:8081/messages'); // Producer socket.onmessage = message => observer.next(message); socket.onclose = () => observer.complete(); socket.onerror = e => observer.error(e); // clean-up code ran when one unsubscribes from observable observer.add(() => { socket.onmessage = null; socket.onclose = null; socket.onerror = null; socket.close(); }); }); (This is a cold observable)
  • 33. const socket = new WebSocket('wss://localhost:8081/messages'); const createMessageSource = () => new Observable(observer => { const onMessage = (message: MessageEvent) => observer.next(message); const onClose = () => observer.complete(); const onError = (e: Event) => observer.error(e); socket.addEventListener('message', onMessage); socket.addEventListener('close', onClose); socket.addEventListener('message', onError); observer.add(() => { socket.removeEventListener('message', onMessage); socket.removeEventListener('close', onClose); socket.removeEventListener('error', onError); }); });
  • 34. const socket = new WebSocket('wss://localhost:8081/messages'); const createMessageSource = () => new Observable(observer => { const onMessage = (message: MessageEvent) => observer.next(message); const onClose = () => observer.complete(); const onError = (e: Event) => observer.error(e); socket.addEventListener('message', onMessage); socket.addEventListener('close', onClose); socket.addEventListener('message', onError); observer.add(() => { socket.removeEventListener('message', onMessage); socket.removeEventListener('close', onClose); socket.removeEventListener('error', onError); }); }); (This is a hot observable)
  • 35. Great, but what if I need to push data to subscribers from elsewhere?
  • 37. RxJS has subjects! (It’s an observable + observer, all in one lovely API surface)
  • 38. import { Subject } from 'rxjs'; import { map, scan } from 'rxjs/operators'; const messageSource = new Subject<StreamedMessage>(); messageSource.pipe( map(({ message }) => message), scan<string>((messages, incomingMessage) => [ ...messages, incomingMessage, ], []), ).subscribe(renderMessages); messageSource.next({ username: 'Bob', message: 'Hi!' }); messageSource.next({ username: 'Peter', message: 'Hey!' });
  • 39. How can I consume observables in my React app?
  • 40. How About Redux Observable?
  • 41. import { Action } from 'redux'; interface State { messages: string[]; isLoading: boolean; hasError: boolean; } interface MessageAction extends Action { messages: string[]; }
  • 42. import { Action } from 'redux'; const reducer = (state: State, action: Action): State => { if (isActionOfType(action, 'REQUEST_MESSAGES')) { return { ...state, isLoading: true, hasError: false, }; } if (isActionOfType<MessageAction>(action, 'RECEIVE_MESSAGES')) { return { ...state, isLoading: false, hasError: false, messages: action.messages, }; } return state; };
  • 43. import { Action } from 'redux'; import { Observable, of, concat } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { filter, catchError, switchMap } from 'rxjs/operators'; const fetchMessagesEpic = ( actionSource: Observable<Action>, ) => actionSource.pipe( filter(action => action.type === 'FETCH_MESSAGES'), switchMap(() => concat( of({ type: 'REQUEST_MESSAGES' }), ajax('/api/messages').pipe( switchMap(({ response }) => response), switchMap(messages => of({ type: 'RECEIVE_MESSAGES', messages, })), ), )), catchError(() => of({ type: 'FETCH_ERROR' })), );
  • 44. import { createStore, applyMiddleware } from 'redux'; import { createEpicMiddleware } from 'redux-observable'; const epicMiddleware = createEpicMiddleware(); const store = createStore( reducer, applyMiddleware(epicMiddleware), ); epicMiddleware.run(fetchMessagesEpic); store.dispatch({ type: 'FETCH_MESSAGES' });
  • 45. Great, so I can use RxJS to control my Redux state, which I can render with React!
  • 47. ...I have to use it alongside Redux and Redux Observable.
  • 48. What if I could just use RxJS for everything?
  • 49. Well, I tried just that. Let me show you.
  • 50. Disclaimer: it’s a proof-of-concept.
  • 52. import * as React from 'react'; import MessageList from './MessageList'; import MessageForm from './MessageForm'; import Status from './Status'; export default () => ( <> <Status /> <MessageForm /> <MessageList /> </> );
  • 53. import * as React from 'react'; import { appState, defaultState, State } from '../state'; import connectToObservable from './connectToObservable'; export const MessageList = ({ messages }: Pick<State, 'messages'>) => ( <ul> {messages.map((message, i) => <li key={i}>{message}</li>)} </ul> ); export default connectToObservable(appState, defaultState)(MessageList);
  • 54. import * as React from 'react'; import { appState, defaultState, State } from '../state'; import connectToObservable from './connectToObservable'; export const MessageList = ({ messages }: Pick<State, 'messages'>) => ( <ul> {messages.map((message, i) => <li key={i}>{message}</li>)} </ul> ); export default connectToObservable(appState, defaultState)(MessageList);
  • 55. export interface State { messages: string[]; isFormValid: boolean; isLoadingQuote: boolean; hasQuoteError: boolean; } export const defaultState: State = { messages: [], isFormValid: true, isLoadingQuote: false, hasQuoteError: false, }; export const appState = new BehaviorSubject<State>(defaultState);
  • 56. import * as React from 'react'; import { Observable } from 'rxjs'; const connectToObservable = <TState, TProps = {}>( observable: Observable<TState>, defaultState: TState, ) => (Component: React.ComponentType<TProps & TState>) => (props: TProps) => { const [state, setState] = React.useState(defaultState); React.useEffect(() => { const subscription = observable.subscribe(setState); return () => { subscription.unsubscribe(); }; }, []); return <Component {...props} {...state} />; };
  • 57. Reading state is cool, but how can we update it?
  • 58. const [message, setMessage] = useState(''); return ( <section> <h2>Add a Message</h2> <form name="message-form" onSubmit={e => { e.preventDefault(); toNextState(addMessage(message)); }} > <input className="form-input" type="text" name="message" placeholder="Your comment" onChange={e => setMessage(e.currentTarget.value)}
  • 59. export type Action<TPayload> = (payload?: TPayload) => Reducer; export type Reducer = (currentState: State) => Observable<State>; export const withState = (reducer: Reducer) => appState .pipe( take(1), switchMap(state => reducer(state)), ); export const toNextState = (reducer: Reducer) => { const sequence = withState(reducer); sequence.subscribe(newState => appState.next(newState)); return sequence; };
  • 60. import { of } from 'rxjs'; import { State, withState } from './state'; export const addMessage = (message: string) => (currentState: State) => of({ ...currentState, isFormValid: !!message.length, isLoadingQuote: false, hasQuoteError: false, messages: [ ...(message.length ? [message] : []), ...currentState.messages, ], });
  • 61. Why do reducers have to return observables?
  • 62. So we can express asynchronous effects!
  • 63. placeholder="Your comment" onChange={e => setMessage(e.currentTarget.value)} /> <input className="form-input form-button" type="submit" value="Add" /> <button className="form-input form-button" type="button" name="add-quote" disabled={isLoadingQuote} onClick={() => toNextState(addRonSwansonQuote())} > Add Ron Swanson quote </button> </form>
  • 64. export const addRonSwansonQuote = () => () => concat( withState(onQuoteLoading()), ajax.getJSON<string[]>('https://ron-swanson-quotes.herokuapp.com/v2/quotes') .pipe( switchMap(([quote]) => withState(addMessage(quote))), catchError(() => withState(onQuoteError())), ), );
  • 65. So we can use the same paradigms to:
  • 66. So we can use the same paradigms to: ● create and query data sources
  • 67. So we can use the same paradigms to: ● create and query data sources ● combine said sources
  • 68. So we can use the same paradigms to: ● create and query data sources ● combine said sources ● reduce actions into a single source of truth
  • 69. So we can use the same paradigms to: ● create and query data sources ● combine said sources ● reduce actions into a single source of truth ● consume this source of truth in React
  • 70. But how does this perform compared to Redux?
  • 71. But how does this perform compared to Redux? useReducer?
  • 72.
  • 75. RxJS
  • 76. RxJS
  • 77. RxJS
  • 78. RxJS
  • 79. So we’re potentially hampering performance.
  • 80. Someone: “But I like the approach. How do I go about ditching Redux?”
  • 81. Well, you probably shouldn’t.
  • 82. RxJS can be overkill for simpler applications.
  • 83. Plus Redux provides a solid, opinionated architecture.
  • 84. Someone else: “Isn’t MobX built around observables? Why not just use that?”
  • 85. It is, and it’s great, but its observables are not compatible with RxJS.
  • 87. Summary ● RxJS provides a functional, declarative means of consuming and aggregating data sources
  • 88. Summary ● RxJS provides a functional, declarative means of consuming and aggregating data sources ● Redux Observable is nice, but we can cut out the middle man using HOCs and Hooks
  • 89. Summary ● RxJS provides a functional, declarative means of consuming and aggregating data sources ● Redux Observable is nice, but we can cut out the middle man using HOCs and Hooks ● However, Redux provides a (positively) opinionated architecture
  • 90. Summary ● RxJS provides a functional, declarative means of consuming and aggregating data sources ● Redux Observable is nice, but we can cut out the middle man using HOCs and Hooks ● However, Redux provides a (positively) opinionated architecture ● How does this perform compared to Redux?
  • 91.
  • 92. Thanks For Listening! @jamesseanwright @YLDio jamesseanwright jamesswright.co.uk yld.io Icons by Alfredo Hernandez (https://www.iconfinder.com/AlfredoHernandez) Slides: https://bit.ly/reactrxslides Repo: https://bit.ly/reactrxrepo

Hinweis der Redaktion

  1. Hey everyone! Welcome to my talk on Managing State in React Apps with RxJS
  2. Hello!
  3. I’m James!
  4. I’m a developer who works with various technologies and languages, but primarily JavaScript and C#
  5. I have gained experience at the likes of Sky, Channel 4, Trainline, News UK, and NET-A-PORTER
  6. I love open-source software, writing, speaking, and mentoring
  7. I work at YLD
  8. We’re a software engineering and design consultancy
  9. We specialise in JavaScript, TypeScript, Node.js, React, DevOps, and beyond!
  10. Just a couple more things before we jump in: firstly, I’ve been ill throughout this entire conference, so I apologise in advance if I start spontaneously coughing.
  11. Finally; a huge thanks to FrontCon for having me back and for organising yet another stellar event
  12. Time for some background (see what I did there?)
  13. Recently, I have been contributing to a web app.
  14. On the client, it uses:
  15. React for rendering the DOM
  16. Redux for state management
  17. RxJS for reactive data transformation and aggregation
  18. Redux Observable for expressing async Redux actions with RxJS
  19. That sounds like a lot to juggle! How does that even look?!
  20. I’ll show you, but let’s firstly take a step back.
  21. What is RxJS?
  22. “RxJS is a library for [functional], reactive programming using Observables, to make it easier to compose asynchronous or callback-based code.” - the RxJS team
  23. Right. How does that translate to code?
  24. Note that I’m using TypeScript here to clarify the shape of the object which should be emitted by the web socket observable. webSocket creator included in RxJS. pipe method on stream used to forward emissions to sequential operators. Scan is like Array.prototype.reduce. Observables are lazy and thus won’t be executed until one calls subscribe.
  25. loadStory uses fromEvent observable source creator. Concat => one emission for each inner observable, thus map and renderComponentFromData will be called twice
  26. So I can generate, consume, and map streams of data, without side effects, using composable operators?
  27. Yup! Pretty neat, right?
  28. You can also write your own observables!
  29. This is COLD/UNICAST
  30. This is COLD/UNICAST
  31. This is HOT/MULTICAST
  32. This is HOT/MULTICAST
  33. Great, but what if I need to push data to subscribers from elsewhere?
  34. RxJS has subjects!
  35. (It’s an observable + observer, all in one lovely API surface)
  36. I should mention there are special kinds of subjects which we will cover later
  37. How can I consume observables in my React app?
  38. How About Redux Observable?
  39. This is COLD
  40. N.B. isActionOfType is a TypeScript type guard, in case you’re wondering
  41. EPIC. Almost analogous to Redux Thunk. Actions in, actions out! Describe body!
  42. How do we integrate Redux Observable into Redux? With Middleware! createEpicMiddleware - I love that term
  43. Great, so I can use RxJS to control my Redux state, which I can render with React!
  44. But...
  45. ...I have to use it alongside Redux and Redux Observable.
  46. What if I could just use RxJS for everything?
  47. Well, I tried just that. Let me show you.
  48. Disclaimer: it’s a proof-of-concept. Don’t get the pitchforks out just yet.
  49. Ron Swanson Quote - never have truer words been spoken Show validation failure state
  50. Here’s our root App component
  51. Here’s what the message list looks like. It’s a stateless component that consumes messages via its props and renders them. But how do we connect them to the state?
  52. Notice this connectToObservable bad boy? Before diving into the implementation, here’s what our app’s state looks like
  53. Going back to those special kinds of subject I mentioned earlier: BehaviorSubject - sends the most recently broadcast value to new subscribers. How does connectToObservable subscribe to the appstate stream?
  54. connectToObservable is a higher-order component that takes in a component and returns a new one, holding its own state using React’s useState hook (which Manjula covered yesterday). There’s also a call to React.useEffect. This hook is used to manage side effects between prop changes, or in our case, when the component mounts and unmounts; this is determined by the empty array of side effects as the second param to useEffect. Thus, when the HOC mounts, it will subscribe to the observable, and will unsubscribe when unmounted.
  55. Reading state is cool, but how can we update it?
  56. Let’s take a look at our form component more closely. When submitted, we call addMessage; as we’ll see shortly, this returns a reducer function. Said function is passed to toNextState, you can think of this as Redux’s dispatch
  57. So how does toNextState work? It will take the latest app state via the appState stream and pass it into the reducer, whose emissions will then be pushed back to the app state. To clarify, let’s take a look at a reducer. Oh, and if you’re wondering why withState is exported, we’ll hit that shortly.
  58. addMessage is what I like to call an action-reducer. It’s a function that is invoked to trigger a side effect in the app’s state, returning a reducer function which takes in state, and produces an updated state as a result, wrapped in an observable (EXPLAIN OF)
  59. Why do reducers have to return observables? (Not a joke)
  60. So we can express asynchronous effects!
  61. Here’s what’s called when we click the button to add a Ron Swanson quote. Looks similar to addMessage, right?
  62. Well in this case, our action-reducer returns an observable created with concat, so we can emit respective loading, success, and error states. Now do you know why we export withState, so we can get the most recent state between broadcasts!
  63. So we can use the same paradigms to:
  64. create and query data sources
  65. combine said sources
  66. reduce actions into a single source of truth
  67. consume this source of truth in React
  68. But how does this perform compared to Redux?
  69. Well, the useReducer hook (LOL). I ported this app to use it in lieu of RxJS as a basis of comparison. Explain useReducer!!!
  70. Y axis is megabytes!
  71. MIGHT BE
  72. MIGHT BE
  73. Spend a little more time in the overall side effect (few extra MS?)
  74. Call stacks are crazy
  75. MIGHT BE
  76. MIGHT BE
  77. So we’re potentially hampering performance.
  78. Someone: “But I like the approach. How do I go about ditching Redux?”
  79. Well, you probably shouldn’t.
  80. RxJS can be overkill for simpler applications.
  81. Plus Redux provides a solid, opinionated architecture.
  82. Someone else: “Isn’t MobX built around observables? Why not just use that?”
  83. It is, and it’s great, but its observables are not compatible with RxJS (so bear that in mind)
  84. Summary
  85. RxJS provides a functional, declarative means of consuming and aggregating data sources
  86. Redux Observable is nice, but we can cut out the middle man using HOCs and Hooks
  87. However, Redux provides a (positively) opinionated architecture
  88. How does this perform compared to Redux?
  89. Final thought I think React’s ability to handle any input objects without wrapping them into observables/wrappers/trackers is significantly underappreciated. That’s why I think the connectToObservable HOC is a good approach for separating presentation components from stateful logic