Skip to content

Commit

Permalink
add notes about why singletones are better than context, update swatc…
Browse files Browse the repository at this point in the history
…h.tsx to use an effect when calling onColorChange
  • Loading branch information
kevinbarabash committed Apr 3, 2024
1 parent 97722f9 commit 6ae00a3
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import {EventEmitter} from "eventemitter3";
export class ColorPickerEventEmitter extends EventEmitter {
selectedColor: string | undefined;

public onColorChange(color: string, callback: (state: boolean) => void) {
public onColorChange(
color: string,
callback: (state: boolean) => void,
): () => void {
this.on(color, callback);
return () => this.off(color, callback);
}

public onSelectedColorChange(callback: (selectedColor: string) => void) {
Expand Down
9 changes: 7 additions & 2 deletions src/react-render-perf/lesson-02/solution/swatch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useContext, useState} from "react";
import {useContext, useEffect, useState} from "react";

import {Color, toCssColor} from "../../shared/color";
import {ColorPickerContext} from "./color-picker-context";
Expand All @@ -12,7 +12,12 @@ export function Swatch({color, size}: Props) {
const colorPickerEventEmitter = useContext(ColorPickerContext)!;
const [isSelected, setIsSelected] = useState(false);

colorPickerEventEmitter.onColorChange(toCssColor(color), setIsSelected);
useEffect(() => {
return colorPickerEventEmitter.onColorChange(
toCssColor(color),
setIsSelected,
);
}, [color, colorPickerEventEmitter]);

return (
<div
Expand Down
13 changes: 13 additions & 0 deletions src/react-render-perf/lesson-03/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ If your context contains an event emitter (or other class instance) and it's onl
used once in your application, you can simplify things even further by using a
singleton instance of it.

While singletons are often frowned upon, their use in this scenario offers several
benefits over React Context:

- By default Context will often result in unnecessary re-renders while using a
singleton will not.
- Context is only available in React so if other code needs access to that data
we need to figure out some way to pipe that data to that code whereas with a
singleton, that code can just import it.
- Testing components that use React Context require wrapper the component in an
appropriate provider which isn't that bad on its own but often times you'll need
to update the value in the Context. Updates are easier with a singleton. The
only thing we need is a way to reset the singleton before each test.

## Example

Using our example from [Lesson 2](../lesson-02/README.md) we can replace the context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import {EventEmitter} from "eventemitter3";
export class ColorPickerEventEmitter extends EventEmitter {
selectedColor: string | undefined;

public onColorChange(color: string, callback: (state: boolean) => void) {
public onColorChange(
color: string,
callback: (state: boolean) => void,
): () => void {
this.on(color, callback);
return () => this.off(color, callback);
}

public onSelectedColorChange(callback: (selectedColor: string) => void) {
Expand Down
9 changes: 7 additions & 2 deletions src/react-render-perf/lesson-03/exercise/swatch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useContext, useState} from "react";
import {useContext, useEffect, useState} from "react";

import {Color, toCssColor} from "../../shared/color";
import {ColorPickerContext} from "./color-picker-context";
Expand All @@ -12,7 +12,12 @@ export function Swatch({color, size}: Props) {
const colorPickerEventEmitter = useContext(ColorPickerContext)!;
const [isSelected, setIsSelected] = useState(false);

colorPickerEventEmitter.onColorChange(toCssColor(color), setIsSelected);
useEffect(() => {
return colorPickerEventEmitter.onColorChange(
toCssColor(color),
setIsSelected,
);
}, [color, colorPickerEventEmitter]);

return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import {EventEmitter} from "eventemitter3";
class ColorPickerEventEmitter extends EventEmitter {
selectedColor: string | undefined;

public onColorChange(color: string, callback: (state: boolean) => void) {
public onColorChange(
color: string,
callback: (state: boolean) => void,
): () => void {
this.on(color, callback);
return () => this.off(color, callback);
}

public onSelectedColorChange(callback: (selectedColor: string) => void) {
Expand Down
9 changes: 7 additions & 2 deletions src/react-render-perf/lesson-03/solution/swatch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useState} from "react";
import {useEffect, useState} from "react";

import {Color, toCssColor} from "../../shared/color";
import {colorPickerEventEmitter} from "./color-picker-event-emitter";
Expand All @@ -11,7 +11,12 @@ type Props = {
export function Swatch({color, size}: Props) {
const [isSelected, setIsSelected] = useState(false);

colorPickerEventEmitter.onColorChange(toCssColor(color), setIsSelected);
useEffect(() => {
return colorPickerEventEmitter.onColorChange(
toCssColor(color),
setIsSelected,
);
}, [color]);

return (
<div
Expand Down

0 comments on commit 6ae00a3

Please sign in to comment.