K O logo
Web Development

Project WebApp — Taking control over state-changes

Author

Kalle

Date Published

This AI-generated image portrays an ultra-modern, futuristic control room or workspace, heavily themed around React.js and advanced programming concepts. The central focus is a large, curved desk filled with multiple screens, all displaying complex code, diagrams, and digital interfaces. Above the desk, an enormous React logo emits a bright, glowing light, with streams of data flowing from it, symbolizing state management or data flow. The walls are lined with large monitors showcasing various coding snippets, keywords like "Memoize," "State," "Middleware," and "Custom Updates," all related to React and JavaScript development. The room is bathed in a mix of cool blue and warm sunset lighting, creating a high-tech yet serene atmosphere. A few playful elements, such as a rubber duck on the desk, add a touch of whimsy to the otherwise sophisticated and immersive environment, blending creativity with cutting-edge technology.

As already said in last session, besides handling the state itself it is very important to take care of the state changes. An extremely powerful tool for this is RXJS. It works with the concept of observables, which are sources of data you can subscribe on.

The rxjs lib provides many defaults to create useful observables for example the interval function creates an observable triggered every time the given amount of milliseconds passes.

1import { interval } from "rxjs";
2
3// Create an Observable that will publish a value on an interval
4const secondsCounter = interval(1000);
5// Subscribe to begin publishing values
6secondsCounter.subscribe((n) =>
7 console.log(`It's been ${n} seconds since subscribing!`),
8);

Important to know here is, that the observable is only active as long as there is an active subscription. So you can declare the complete dataflow and trigger it by subscribing. This is also meaningful for ajax requests, the request is only triggered when there is someone who listens for the result.

1import { ajax } from "rxjs/ajax";
2
3const requestLuke = ajax("https://swapi.co/api/people/1");
4// nothing happens here
1import { ajax } from "rxjs/ajax";
2
3const requestLuke = ajax("https://swapi.co/api/people/1");
4
5const sub = requestLuke.subscribe((res) => {
6 console.log(res.status, res.response);
7});
8// now the actual api call is fired

The real power of rxjs lies in its operators which can transform the data coming from an observable and build a new observable providing the transformed data. The most important one is the map-operator, which returns a new value for each value passing through. In general, it can be seen as a stream of information passing through a pipe with all sorts of valves/filters/transformers... and so the function to work with the operators has the fitting name pipe. A simple map example is to transform the ajax-response object to the actual response object.

1import { ajax } from "rxjs/ajax";
2import { map } from "rxjs/operators";
3
4const requestLuke = ajax("https://swapi.co/api/people/1").pipe(
5 map((res) => res.response),
6);
7
8const sub = requestLuke.subscribe((response) => {
9 console.log(response);
10});

And to catch possible errors (handling with external services can always cause errors) use the catchError operator.

1import { ajax } from "rxjs/ajax";
2import { catchError, map } from "rxjs/operators";
3import { empty } from "rxjs";
4
5const requestLuke = ajax("https://swapi.co/api/people/1").pipe(
6 catchError((e) => {
7 console.error(e);
8 // simple fallback of returning an empty observable
9 // (swallows error -> bad error catching)
10 return empty();
11 }),
12 map((res) => res.response),
13);
14
15const sub = requestLuke.subscribe((response) => {
16 console.log(response);
17});

A list of all operators and observable factories can be found on the official docs site reactivex.io/rxjs.

Besides the given Observable factories there are also "Subjects", a subject in the rxjs context is an observable, with the addition of the .next() method. By calling next with a value you can push this value in the observable pipeline. But remember, the provided values are only digested when there is a subscriber. All calls to next before there is an active subscription are simply forgotten.

1import { Subject } from "rxjs";
2const eventsProvider = new Subject();
3eventsProvider.next("first event"); // this is forgotten
4
5const sub = eventsProvider.subscribe((response) => {
6 console.log(response);
7});
8eventsProvider.next("second event"); // gets logged to console

For the chat app from the last session this means you can declare all changes to the state with observables and user inputs with subjects.

1import { Subject, from } from "rxjs";
2import { Subject, merge, scan } from "rxjs/operators";
3
4const messageInputSubject = new Subject();
5const initialMessages = from(messagesJson);
6
7const messageStream = messageInputSubject.pipe(merge(messagesJson));
8
9// observable providing an array of all messages
10const messageState = messageStream.pipe(
11 // scan acts like reduce, but over time.
12 // it always provides the last calculated value
13 scan(
14 (state, message) => {
15 return state.concat(message);
16 },
17 // empty array as initial value
18 [],
19 ),
20);

To connect this state with the react world a subscription on the relevant data is needed in one component which transfers the rxjs-state into the react-world. When building a subscription it is important to unsubscribe, when it's not used anymore to prevent memory-leaks. You can either use a direct .unsubscribe() call on the subscription object, or use the takeUntil(someObservable) operator. The subscribe call should be in the componentDidMount react lifecycle method, and end of the subscription should be in the componentWillUnmount lifecycle method. More information about the react-component lifecycle can be found here.

1export class App extends React.Component {
2 // initial state
3 state = {
4 /* ✂️ */
5 };
6
7 // subject which emits on componentWillUnmount
8 unMounted = new Subject();
9
10 // react lifecycle method called when this
11 // component got mounted into the DOM
12 componentDidMount() {
13 stateObservable
14 .pipe(
15 /* other transforamtions ✂️ */
16 takeUntil(this.unMounted),
17 )
18 .subscribe((stateUpdate) => {
19 this.setState(stateUpdate);
20 });
21 }
22
23 // react lifecycle method called when this
24 // component will be removed from the DOM
25 componentWillUnmount() {
26 this.unMounted.next();
27 }
28
29 render() {
30 /* ✂️ */
31 }
32}

Tasks

  • clone the chat-app-2 repository
  • install the rxjs dependency npm install rxjs
  • create a folder "state" in the project to separate state-handling code
  • create the message- and user-observables and subjects needed to manage all data hold in the state
  • connect the app with the newly create state-logic
  • extend the state with an observable to calculate the message count
  • (bonus) add second input field to select a user and filter the messages to show only messages from this user
 This AI-generated image presents a futuristic, high-tech workspace focused on web development, particularly using Firebase and React.js. The room is dominated by large, holographic displays on the walls, showcasing various data visualizations, code snippets, and icons related to Firebase services like Real-Time Database and Firestore. The central display features a glowing React logo hovering over an image of Earth, symbolizing global connectivity. Several workstations with multiple monitors are arranged around the room, each displaying lines of code. The workspace is bathed in warm, golden light, creating a sophisticated yet dynamic atmosphere. Additional details include a coffee mug with the React logo, a touch-sensitive table with holographic controls, and a few potted plants, adding a touch of greenery to the sleek, modern environment. The scene highlights the integration of cutting-edge technology and web development tools in a highly organized and visually immersive setting.

Project WebApp — Get firebase to sync your data

Learn how to sync data in your web app using Firebase's realtime database. This guide covers setting up Firebase, configuring anonymous authentication, and using Firebase to manage and sync data efficiently across clients.

Mailing List

If you want to receive updates on new posts, leave your email below.