Hello, I'm Mateusz Roth. Versatile Software Engineer from 🇵🇱🇪🇺, specialized in JavaScript, TypeScript, Node.js, React, React Native.
Open-minded, friendly person that loves learning.

Frontend - printing documents

Print iframe

JS:

1
2
window.frames["printf"].focus();
window.frames["printf"].print();

HTML:

1
<iframe id="printf" name="printf"></iframe>

JS:

1
document.getElementById("printf").contentWindow.print()​​​​​​;

More: https://stackoverflow.com/questions/9616426/javascript-print-iframe-contents-only

Print div

1
2
3
4
5
6
7
8
9
10
function printDiv(divName) {
var printContents = document.getElementById(divName).innerHTML;
var originalContents = document.body.innerHTML;

document.body.innerHTML = printContents;

window.print();

document.body.innerHTML = originalContents;
}

jspdf nie nadaje się do robienia plików HTML

More: https://cdn.rawgit.com/MrRio/jsPDF/master/examples/html2pdf/showcase_supported_html.html

My implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import replace from "lodash/replace";
import { getDataBase64String, MIME_HTML } from "../constants/mimeTypes";

export const REPLACE_SCRIPT = `
<script>
window.loadPrint = function loadPrint() {
window.print();
setTimeout(function () { window.close(); document.clear(); document.close(); }, 100);
}

window.onload = function() { window.loadPrint(); }
</script>
`;
const HTML_TAG = "</html>";
const HTML_TAG_CAPITALIZED = "</HTML>";

function printHtmlDocument(dataBase64Source, iframeElement) {
const dataBase64Prefix = getDataBase64String(MIME_HTML);
const base64Source = dataBase64Source.slice(dataBase64Prefix.length);
const source = atob(base64Source);

const hasHtmlTag = source.indexOf(HTML_TAG) > -1;
const hasCapitalizedHtmlTag = source.indexOf(HTML_TAG_CAPITALIZED) > -1;

let processedHtml;

if (hasHtmlTag && !hasCapitalizedHtmlTag) {
processedHtml = replace(source, HTML_TAG, REPLACE_SCRIPT + HTML_TAG);
} else if (!hasHtmlTag && hasCapitalizedHtmlTag) {
processedHtml = replace(
source,
HTML_TAG_CAPITALIZED,
REPLACE_SCRIPT + HTML_TAG_CAPITALIZED
);
}

if (!processedHtml) {
processedHtml = source + REPLACE_SCRIPT;
}

const processedBase64Source = btoa(processedHtml);
const processedDataBase64Source = dataBase64Prefix + processedBase64Source;

const iframe = iframeElement || document.createElement("iframe");
iframe.src = processedDataBase64Source;
iframe.height = "0";
iframe.width = "0";
iframe.title = "";
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 1000);

return {
html: processedHtml,
dataBase64: processedDataBase64Source,
iframe: iframe,
};
}

export default printHtmlDocument;

Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import printHtmlDocument, { REPLACE_SCRIPT } from "./printHtmlDocument";
import { DATA_MIME_HTML_BASE64 } from "../constants/mimeTypes";

describe("printHtmlDocument", () => {
const HTML = "<html><body>Hello</body></html>";
const HTML_EXPECTED = `<html><body>Hello</body>${REPLACE_SCRIPT}</html>`;
const SOURCE = DATA_MIME_HTML_BASE64 + btoa(HTML);
const SOURCE_EXPECTED = DATA_MIME_HTML_BASE64 + btoa(HTML_EXPECTED);

test("it should return properly modified HTML with included REPLACE_SCRIPT script", () => {
expect(printHtmlDocument(SOURCE).html).toEqual(HTML_EXPECTED);
});

test("it should return properly modified BASE64 source", () => {
expect(printHtmlDocument(SOURCE).dataBase64).toEqual(SOURCE_EXPECTED);
});

test("it should create iframe element with proper attributes", () => {
const result = printHtmlDocument(SOURCE);
expect(result.iframe.src).toEqual(SOURCE_EXPECTED);
expect(result.iframe.height).toEqual("0");
expect(result.iframe.width).toEqual("0");
expect(result.iframe.title).toEqual("");
});
});

Elm programming language attributes

  • functional reactive programming language
  • compiles to JS
  • the compiler catches most errors immediately and provides a clear and understandable error message
  • purely functional
  • statically typed
  • looks like Haskell
  • time-travelling debugger
  • uses type inference
  • has its own virtual DOM implementation
  • very fast DOM diff implementation
  • your CSS and HTML are written in code :( - looks declarative (YAML-ish), breaks the separation principle
  • a steep learning curve for JS devs
  • helps dramatically, as the reactive paradigm is part of the language’s architecture

Objective C programming language attributes

Wypisywanie w konsoli:

1
2
NSLog(@"%@", strValue);
NSLog(flag ? @"Yes" : @"No");
  • For Strings you use %@
  • For int you use %i
  • For float and double you use %f
  • For bool you use %d

Consty i zmienne:

1
2
const char NEWLINE = '\n';
int area;

Tworzenie klas i obiektów:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
NSLog(@"Hello, World! \n");
}
@end

int main() {
/* my first program in Objective-C */
SampleClass *sampleClass = [[SampleClass alloc]init];
[sampleClass sampleMethod];
return 0;
}

Tworzenie struct:

1
2
3
4
5
6
struct Books {
NSString *title;
NSString *author;
NSString *subject;
int book_id;
};

Manipulowanie struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface BookLogger:NSObject
/* function declaration */
- (void) printBook:(struct Books*) book;
@end

@implementation BookLogger
- (void) printBook:(struct Books*) book {
NSLog(@"Book title: %@\n", book->title);
NSLog(@"Book author: %@\n", book->author);
NSLog(@"Book subject: %@\n", book->subject);
NSLog(@"Book book_id: %d\n", book->book_id);
}
@end

int main() {
struct Books Book1; /* Declare Book1 of type Book */
struct Books Book2; /* Declare Book2 of type Book */

BookLogger *bookLogger = [[BookLogger alloc]init];
/* print Book1 info */
[sampleClass bookLogger: Book1];
}

Tworzenie bloków funkcji:

1
2
3
4
5
- (return_type) method_name:(argumentType1)argumentName1 
joiningArgument2:(argumentType2)argumentName2 ...
joiningArgumentn:(argumentTypen)argumentNamen {
body of the function
}

i.e.:

1
2
3
4
5
6
7
double (^multiplyTwoValues)(double, double) = 
^(double firstValue, double secondValue) {
return firstValue * secondValue;
};

double result = multiplyTwoValues(2,4);
NSLog(@"The result is %f", result);

Struktury danych:

  • NSDictionary could be accessed like dict[@"key"] or [dict objectForKey:@"key"]
  • NSArray is accessible via indexes: 0, 1, 2 etc:

Inicjacja NSArray:

1
2
NSArray* array = @[@"One", @"Two", @"Three"];
NSArray* array = @[Object1, Object2]

Iterowanie po NSDictionary:

1
2
3
4
5
for (NSString* key in yourDict) {
NSLog(@"%@", yourDict[key]);
//or
NSLog(@"%@", [yourDict objectForKey:key]);
}

Inicjalizacja NSDictionary:

1
NSDictionary* dict = @{ key : value, key2 : value2};

Dodawanie do NSDictionary wartości niebędającej obiektem wymaga jej rzutowania na odpowiedni obiekt Objective C:

1
2
@"facebookId": [NSNumber numberWithInt:[fbId intValue]];
@"facebookId": @(fbId) // for non pointer datatype value store in Dictionary

@property:

atomic, nonatomic, etc.

@synthesize:

@synthesize

Reason programming language attributes

How to start React project in Reason:

Variants

Variants in Reason are data types and structures. It can be used to define sets of symbols and data structures.
type animal = Cat(string) | Dog(string);

Functions

Functions in Reason are declared with an arrow and a return expression.

1
2
3
4
5
let speak = (animal) =>
switch (animal) {
| Cat(name) => name ++ " says: meow"
| Dog(name) => name ++ " says: woof"
};

React

A React component can either be stateless or stateful. In ReasonReact, there are different ways of defining them:

  • Stateless components: defined as ReasonReact.statelessComponent("componentName")
  • Stateful components: defined as ReasonReact.reducerComponent("componentName")

let make = (~message, _children) => {
The first parameter has a symbol ~ indicating that it was passed into the App component as a props and the second parameter has _, this is a more explicit way of showing that the parameter isn’t used and ignored.

GraphQL Apollo links

  • self-documentation - you build your types
  • types information available to the client coder through introspection with tools like GraphiQL
  • specification which forms a standardization
  • make everything go through an API gateway which would usually do the auth resolution for all microservices

GraphCMS:
https://docs.graphcms.com/
https://github.com/GraphCMS/graphcms-examples/blob/master/current/next-apollo-blog/lib/initApollo.js

GraphQL example:
https://nec.is/writing/graphql-with-next-js-and-apollo/

Next, Apollo, Postgraphile:
https://github.com/graphile/postgraphile-apollo-server

Next, Apollo, Graphcool:
https://github.com/adamsoffer/next-apollo-example
https://github.com/zeit/next.js/tree/master/examples/with-apollo

Next & Apollo:
https://github.com/lfades/next-with-apollo
https://github.com/adamsoffer/next-apollo

Next, Express, Apollo:
https://github.com/ooade/next-apollo-auth

Hasura and Apollo:
https://medium.com/@f71uday/building-production-ready-application-with-hasura-and-apollo-476b18ccb44

Hasura vs Postgraphile:
https://news.ycombinator.com/item?id=17540866

Postgraphile:
https://www.graphile.org/postgraphile/

Hasura - GraphQL on Postgres:
https://hasura.io/

Graphcool - GraphQL BaaS:
https://www.graph.cool/

Prisma - GrapQL on any database:
https://www.prisma.io/with-graphql/

Nextjs, Apollo, Prisma:
https://github.com/mbaranovski/nextjs-apollo-oauth-prisma

RAN! React . GraphQL . Next.js Toolkit - bez backendu, przykład z Graphcool:
https://github.com/Sly777/ran
https://github.com/mshameer/ran-with-prisma

Vulcan - React, GraphQL, Meteor:
http://docs.vulcanjs.org/

Design Patterns

Design Patterns

List of design patterns
Source #1 - Common Design Patterns for Android with Kotlin

Creational patterns

They define how you create objects.

  • Builder
  • Dependency Injection - explained below
  • Singleton

Structural patterns

They define how you compose objects.

  • Adapter - this pattern lets two incompatible classes work together by converting the interface of a class into another interface the client expect
  • Facade - The Facade pattern provides a higher-level interface that makes a set of other interfaces easier to use -> for example a repository in Android project can be a facade
  • DAO - a design pattern for writing objects that access data
    • You should use DAOs. The pattern lends itself to modularized code. You keep all your persistence logic in one place (separation of concerns, fight leaky abstractions). You allow yourself to test data access separately from the rest of the application. And you allow yourself to test the rest of the application isolated from data access (i.e. you can mock your DAOs).

Behavioral patterns

They define how you coordinate object interactions.

  • Command - Similarly, the Command pattern lets you issue requests without knowing the receiver. You encapsulate a request as an object and send it off; deciding how to complete the request is an entirely separate mechanism.
  • Observer - The Observer pattern defines a one-to-many dependency between objects. When one object changes state, all of its dependents are notified and updated automatically.
  • Model View Controller
  • Model View ViewModel - The ViewModel object is the “glue” between the model and view layers, but operates differently than the Controller component. Instead, it exposes commands for the view and binds the view to the model. When the model updates, the corresponding views update as well via the data binding. Similarly, as the user interacts with the view, the bindings work in the opposite direction to automatically update the model. This reactive pattern removes a lot of glue code.

Dependency Injection & Dependency Inversion principle explained

1
2
3
class Parent {
private val child = Child()
}

A Parent instance creates its child field when it’s instantiated. The Parent instance is dependent on the concrete implementation of Child and on configuring the child field to use it.

This presents a coupling or dependency of the Parent class on the Child class. If the setup of a Child object is complex, all that complexity will be reflected within the Parent class as well. You will need to edit Parent to configure a Child object.

If the Child class itself depends on a class C, which in turn depends on class D, then all that complexity will propagate throughout the code base and hence result in a tight coupling between the components of the application.

Dependency Injection is the term used to describe the technique of loosening the coupling just described. In the simple example above, only one tiny change is needed:

1
class Parent(private val child: Child)

Voilà — that’s dependency injection at its core!

There is also Dependency Inversion principle. The gist of the Dependency Inversion principle is that it is important to depend on abstractions rather than concrete implementations. In the simple example above, this means changing Child to a Kotlin interface rather than a Kotlin class. With this change, many different types of concrete Child type objects that adhere to the Child interface can be passed into the Parent constructor.

Source - Dependency Injection in Android with Dagger 2 and Kotlin

Software Engineering - various terms

Terms

function declaration vs definition, parameter type, return type

Empty variables interpretation

  • null means nothing - we want no value
  • undefined declared but not defined - we want default value

coupling (miara współzależności)

In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa. Low coupling is often a sign of a well-structured computer system and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability.

cohesion (miara spójności)

In computer programming, cohesion refers to the degree to which the elements inside a module belong together. In one sense, it is a measure of the strength of relationship between the methods and data of a class and some unifying purpose or concept served by that class. In another sense, it is a measure of the strength of relationship between the class’s methods and data themselves.

Cohesion is an ordinal type of measurement and is usually described as “high cohesion” or “low cohesion”. Modules with high cohesion tend to be preferable, because high cohesion is associated with several desirable traits of software including robustness, reliability, reusability, and understandability. In contrast, low cohesion is associated with undesirable traits such as being difficult to maintain, test, reuse, or even understand.

loose coupling

In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Subareas include the coupling of classes, interfaces, data, and services. Loose coupling is the opposite of tight coupling.

big ball of mud

A big ball of mud is a software system that lacks a perceivable architecture. Although undesirable from a software engineering point of view, such systems are common in practice due to business pressures, developer turnover and code entropy. They are a type of design anti-pattern.

software entropy

The second law of thermodynamics, in principle, states that a closed system’s disorder cannot be reduced, it can only remain unchanged or increase. A measure of this disorder is entropy. This law also seems plausible for software systems; as a system is modified, its disorder, or entropy, tends to increase. This is known as software entropy.

The process of code refactoring can result in stepwise reductions in software entropy.

Software rot, also known as code rot, software erosion, software decay or software entropy is either a slow deterioration of software performance over time or its diminishing responsiveness that will eventually lead to software becoming faulty, unusable, or in need of upgrade.

feature creep

Feature creep is the excessive ongoing expansion or addition of new features in a product,[1] especially in computer software, videogames and consumer and business electronics. These extra features go beyond the basic function of the product and can result in software bloat and over-complication, rather than simple design.

software brittleness

In computer programming and software engineering, software brittleness is the increased difficulty in fixing older software that may appear reliable, but fails badly when presented with unusual data or altered in a seemingly minor way.

first-class functions

In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.[1] Some programming language theorists require support for anonymous functions (function literals) as well.[2] In languages with first-class functions, the names of functions do not have any special status; they are treated like ordinary variables with a function type.[3]
First-class functions are a necessity for the functional programming style, in which the use of higher-order functions is a standard practice. A simple example of a higher-ordered function is the map function, which takes, as its arguments, a function and a list, and returns the list formed by applying the function to each member of the list. For a language to support map, it must support passing a function as an argument.
Source https://en.m.wikipedia.org/wiki/First-class_function

higher order function

a function taking another function as argument is called a higher-order function

Authorization vs Authentication

Authentication describes the process of claiming an identity. That’s what you do when you log in to a service with a username and password, you authenticate yourself.
Authorization on the other hand describes permission rules that specify the access rights of individual users and user groups to certain parts of the system.

Monkey patching

https://en.wikipedia.org/wiki/Monkey_patch
https://www.audero.it/blog/2016/12/05/monkey-patching-javascript/

Domain-driven design (skrót DDD)

  • podejście do tworzenia oprogramowania kładące nacisk na takie definiowanie obiektów i komponentów systemu oraz ich zachowań, aby wiernie odzwierciedlały rzeczywistość. Dopiero po utworzeniu takiego modelu należy rozważyć zagadnienia związane z techniczną realizacją. Podejście to umożliwia modelowanie systemów informatycznych przez ekspertów, którzy znają specyfikę problemu, lecz nie muszą znać się na projektowaniu architektury systemów informatycznych.

Command–query separation (CQS)

  • is a principle of imperative computer programming. It was devised by Bertrand Meyer as part of his pioneering work on the Eiffel programming language. It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, Asking a question should not change the answer.[1] More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.

TypeScript with React

Common Operators and Signatures

Type intersection operator (&)

1
class WithLoading extends React.Component<P & WithLoadingProps> { ... }

Generic function

1
const funcComponent = <P extends object>(Component: React.ComponentType<P>): ... => { ... }

Type cast

A type cast (props as P) is required when passing props forward from TypeScript v3.2 onwards, due to a likely bug in TypeScript.

1
return loading ? <LoadingSpinner /> : <Component {...props as P} />;

Examples

Worth to read:

Higher Order Components

Higher Order Component in JS

1
2
3
4
5
6
7
const withLoading = Component =>
class WithLoading extends React.Component {
render() {
const { loading, ...props } = this.props;
return loading ? <LoadingSpinner /> : <Component {...props} />;
}
};

Higher Order Component in TS

1
2
3
4
5
6
7
8
9
10
11
interface WithLoadingProps {
loading: boolean;
}

const withLoading = <P extends object>(Component: React.ComponentType<P>) =>
class WithLoading extends React.Component<P & WithLoadingProps> {
render() {
const { loading, ...props } = this.props;
return loading ? <LoadingSpinner /> : <Component {...props as P} />;
}
};

Function Higher Order Component in TS

1
2
3
4
5
6
7
const withLoading = <P extends object>(
Component: React.ComponentType<P>
): React.FC<P & WithLoadingProps> => ({
loading,
...props
}: WithLoadingProps) =>
loading ? <LoadingSpinner /> : <Component {...props as P} />;

Render Prop Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
interface InjectedCounterProps {
value: number;
onIncrement(): void;
onDecrement(): void;
}

interface MakeCounterProps {
minValue?: number;
maxValue?: number;
children(props: InjectedCounterProps): JSX.Element;
}

interface MakeCounterState {
value: number;
}

class MakeCounter extends React.Component<MakeCounterProps, MakeCounterState> {
state: MakeCounterState = {
value: 0,
};

increment = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.maxValue
? prevState.value
: prevState.value + 1,
}));
};

decrement = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.minValue
? prevState.value
: prevState.value - 1,
}));
};

render() {
return this.props.children({
value: this.state.value,
onIncrement: this.increment,
onDecrement: this.decrement,
});
}
}

make-counter-render-prop.tsx

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface CounterProps extends InjectedCounterProps {
style: React.CSSProperties;
}

const Counter = (props: CounterProps) => (
<div style={props.style}>
<button onClick={props.onDecrement}> - </button>
{props.value}
<button onClick={props.onIncrement}> + </button>
</div>
);

interface WrappedCounterProps extends CounterProps {
minValue?: number;
maxValue?: number;
}

const WrappedCounter = ({
minValue,
maxValue,
...props
}: WrappedCounterProps) => (
<MakeCounter minValue={minValue} maxValue={maxValue}>
{injectedProps => <Counter {...props} {...injectedProps} />}
</MakeCounter>
);

wrapped-counter.tsx

Wrapping Render Prop as HOC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Subtract, Omit } from 'utility-types';
import MakeCounter, { MakeCounterProps, InjectedCounterProps } from './MakeCounter';

type MakeCounterHocProps = Omit<MakeCounterProps, 'children'>;

const makeCounter = <P extends InjectedCounterProps>(
Component: React.ComponentType<P>
): React.SFC<Subtract<P, InjectedCounterProps> & MakeCounterHocProps> => ({
minValue,
maxValue,
...props
}: MakeCounterHocProps) => (
<MakeCounter minValue={minValue} maxValue={maxValue}>
{injectedProps => <Component {...props as P} {...injectedProps} />}
</MakeCounter>
);

Source: https://medium.com/@jrwebdev/react-render-props-in-typescript-b561b00bc67c

React Hooks

Function Components with Hooks in TS example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// import useState next to FunctionComponent
import React, { FunctionComponent, useState } from "react";

// our components props accept a number for the initial value
const Counter: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
// since we pass a number here, clicks is going to be a number.
// setClicks is a function that accepts either a number or a function returning
// a number
const [clicks, setClicks] = useState(initial);
return (
<>
<p>Clicks: {clicks}</p>
<button onClick={() => setClicks(clicks + 1)}>+</button>
<button onClick={() => setClicks(clicks - 1)}>-</button>
</>
);
};

Details about React Hooks with TypeScript

1
2
3
const Counter:FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
...
}

with useState

1
2
3
4
5
6
7
8
9
// explicitly setting the types
const [value, setValue] = (useState < number) | (undefined > undefined);
const [value, setValue] = useState < Array < number >> [];

interface MyObject {
foo: string;
bar?: number;
}
const [value, setValue] = useState <MyObject> { foo: "hello" };

with useRef

1
const inputEl = useRef <HTMLInputElement> null;

with useContext

1
2
type Theme = "light" | "dark";
const ThemeContext = createContext <Theme> "dark";

with useReducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface State {
value: number;
}

type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "incrementAmount", amount: number };

const counterReducer = (state: State, action: Action) => {
switch (action.type) {
case "increment":
return { value: state.value + 1 };
case "decrement":
return { value: state.value - 1 };
case "incrementAmount":
return { value: state.value + action.amount };
default:
throw new Error();
}
};

const [state, dispatch] = useReducer(counterReducer, { value: 0 });

dispatch({ type: "increment" });
dispatch({ type: "decrement" });
dispatch({ type: "incrementAmount", amount: 10 });

// TypeScript compilation error
dispatch({ type: "invalidActionType" });

TypeScript basics

! operator

1
if (a!.b!.c) { }

compiles to JS:

1
2
if (a.b.c) {
}

? operator

1
let x = foo?.bar.baz();

compiles to JS:

1
let x = foo === null || foo === undefined ? undefined : foo.bar.baz();

And:

1
2
3
if (someObj?.bar) {
// ...
}

is equivalent in JS to:

1
2
3
if (someObj &amp;&amp; someObj.someProperty) {
// ...
}

TS:

1
2
3
4
5
6
7
8
9
10
interface Content {
getUrl?: () => string;
url?: string;
}
interface Data {
content?: Content;
}
let data: Data | undefined;
const url: string | undefined = data?.content?.getUrl?.();
const url2: string | undefined = data?.content?.url;

React Hooks

  • Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
  • Hooks allow you to reuse stateful logic without changing your component hierarchy.
  • Without hooks mutually related code that changes together gets split apart different lifecycle methods. Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.

React Hooks advantages

  • hooks are easier to test (as separated functions) and make the code look cleaner/easier to read (i.e. less LOCs)
  • code that uses hooks is more readable and have less LOC (https://m.habr.com/en/post/443500/)
  • hooks make code more reusable / composable (also they don’t create another element in DOM like HOCs do)
  • you can define several seperate lifecycle methods instead of having all in one method
  • hooks are going to work better with future React optimizations (like ahead of time compilation and components folding) - components folding in future (https://github.com/facebook/react/issues/7323) - what means dead code elimination at compile time (less JS code to download, less to execute)
  • hooks show real nature of React which is functional, using classes make developer easier to do mistakes and use React antipatterns
  • hooks are very convenient to re-use stateful logic, this is one of their selling point. But this not applicable when app is built using some state management library and stateful logic doesn’t live in React components. So for desktop in its current state hooks are mostly for readability and to make it future-proof.
  • With HOCs we are separating unrelated state logic into different functions and injecting them into the main component as props, although with Hooks, we can solve this just like HOCs but without a wrapper hell (https://cdn-images-1.medium.com/max/2000/1*t4NuFEZWHcfPHV_f487GRA.png)

React Hooks vs HOCs and render props

Hooks list

  • basic hooks: useState, useEffect, useContext
  • additional hooks: useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValue

Hook setState

  • if you call useState many times, you do it in the same order during every render
    • React relies on the order in which Hooks are called
    • React remembers initial order of calling hooks so we can’t conditionally add or remove any new hook
  • React will remember its current value between re-renders, and provide the most recent one to our function.
    1
    2
    3
    4
    5
    import React, { useState } from 'react';

    function Example() {
    // Declare a new state variable, which we'll call "count"
    const [count, setCount] = useState(0);
  • unlike the setState method found in class components, useState does not automatically merge update objects, so we have to manually set state for previous state values that we’re not intend to modify:
1
2
3
4
setState(prevState => {
// Object.assign would also work
return { ...prevState, ...updatedValues };
});
  • alternative to the setState hook is the useReducer hook, which is more suited for managing state objects that contain multiple sub-values and not simple primitives like numbers, strings.
  • setState - if the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:
1
2
3
4
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});

Hook useEffect

  • it serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes
  • React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates
  • Hooks let you organize side effects in a component by what pieces are related (such as adding and removing a subscription), rather than forcing a split based on lifecycle methods
  • you should call hooks at the top level of the render function, this means no conditional hooks:
1
2
3
4
5
6
7
8
9
10
11
12
13
// BAD:
if (user.isAdmin) {
useEffect(() => {
...
});
};

// GOOD:
useEffect(() => {
if (user.isAdmin) {
...
};
})
  • the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale. Every time we re-render, we schedule a different effect, replacing the previous one
  • Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen

    Cleaning subscriptions

  • we might want to set up a subscription to some external data source
  • In a React class, you would typically set up a subscription in componentDidMount, and clean it up in componentWillUnmount
  • that function what we return from our effect is the optional cleanup mechanism for effects
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import React, { useState, useEffect } from 'react';

    function FriendStatus(props) {
    const [isOnline, setIsOnline] = useState(null);

    useEffect(() => {
    function handleStatusChange(status) {
    setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
    });

    if (isOnline === null) {
    return 'Loading...';
    }
    return isOnline ? 'Online' : 'Offline';
    }
  • React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We will discuss optimalization in next section.

Second parameter

  • a second argument to useEffect that is the array of values that the effect depends on, so in below example the effect will be executed only when props.source changes:
1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
}, [props.source]);
  • this way you can tell React to skip applying an effect if certain values haven’t changed between re-renders
  • this also works for effects that have a cleanup phase
  • If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument

Hook useContext

Hook useCallback

  • returns a memoized callback
  • will return a memoized version of the callback that only changes if one of the dependencies has changed
1
2
3
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

Hook useMemo

  • If you’re doing expensive calculations while rendering, you can optimize them with useMemo:
1
2
3
4
5
6
const [c1, setC1] = useState(0);
const [c2, setC2] = useState(0);

// This value will not be recomputed between re-renders
// unless the value of c1 changes
const sinOfC1: number = useMemo(() => Math.sin(c1), [c1]);
  • useMemo is generalized version of useCallback hook. useMemo is primarily used for caching values but can also be used for caching functions as useCallback is used for because useCallback(fn, deps) is equivalent to useMemo(() => fn, deps):
1
2
3
4
5
6
// Some function ...
const f = () => { ... }

// The following are functionally equivalent
const callbackF = useCallback(f, [])
const callbackF = useMemo(() => f, [])

Extracting custom hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useState, useEffect } from 'react';

function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mport { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});

return isOnline;
}
1
2
3
4
5
6
7
8
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);

if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

Sources