Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Redux with TypeScript

Introduction to Redux with TypeScript

Elegance always matters - especially when crafting software - and patterns exist precisely as inspiring principles that lead us to methodically tackling complexity and creating clean architectures.

State management is one of the most essential and nuanced problems in the IT domain - and Redux, inspired by Elm and Flux, elegantly mixes beautiful concepts such as events and reducers to provide a simple and effective solution via a functional approach.

Gianluca Costa

February 14, 2023
Tweet

More Decks by Gianluca Costa

Other Decks in Programming

Transcript

  1. Gianluca Costa ️
    Introduction to Redux
    with TypeScript
    Latest update: 2023-02-14

    View Slide

  2. Foreword
    Elegance always matters - especially when crafting software - and patterns exist
    precisely as inspiring principles that lead us to methodically tackling complexity and
    creating clean architectures.
    State management is one of the most essential and nuanced problems in the IT
    domain - and Redux, inspired by Elm and Flux, elegantly mixes beautiful concepts
    such as events and reducers to provide a simple and effective solution via a functional
    approach.
    2

    View Slide

  3. About this presentation
    Our journey stems from my passion for the concepts at the heart of Redux: we are
    going to discover, with no claim of completeness, how Redux can be a brilliant tool for
    state management - especially when combined with the static type checking provided
    by TypeScript.
    I have also created a few minimalist code projects - available in the companion
    GitHub repository - to showcase different aspects of the Redux ecosystem.
    This work is designed to be a concise reference: should you need a more gradual
    approach, live coding sessions or real-life projects, please consider online courses.
    For the latest and most complete version of the documentation, please refer to
    Redux's official website.
    3

    View Slide

  4. Overview
    The main parts of this presentation can be briefly summarized as follows:
    1. Enter Redux: introductory considerations before sailing
    2. Essential building blocks: core aspects of Redux, packaged as redux on NPM
    3. Redux Toolkit: simplified development with the @reduxjs/toolkit package
    4. React Redux: to elegantly combine React and Redux
    5. Conclusion: parting thoughts
    4

    View Slide

  5. Part 1
    Enter Redux

    View Slide

  6. What is Redux?
    « Redux is a pattern and library for managing and updating
    application state »
    Minimalist library providing just a core implementation
    Redux does not depend on a specific major library or framework, such as React or
    Angular: it can be referenced by any JavaScript project, including backend projects
    and potentially even command-line applications
    The ecosystem provides a wide variety of extensions and developer tools
    6

    View Slide

  7. Global state in one store
    The core idea is a single, centralized store containing the global state:
    Any modification to the state can only be performed indirectly, by sending
    messages to the store
    Each message is processed according to specific business rules and creates a
    new state for the store
    7

    View Slide

  8. Why use Redux?
    Single source of truth - the store
    Trackable modifications
    Expressive code
    Scalable, message-based architecture
    Simplified data access in complex UIs
    Time-travel debugging
    8

    View Slide

  9. Time-travel debugging
    9

    View Slide

  10. Vibrant ecosystem
    The very heart of Redux consists of the redux package, designed to be
    extensible
    Redux Toolkit, packaged as @reduxjs/toolkit , exports sensible defaults and
    minimalist constructs
    React Redux provides seamless integration with React
    Redux DevTools: time-travelling debugger and other tools
    ...and much more! Redux is designed to be extensible!
    10

    View Slide

  11. Not every app needs Redux
    Every architectural choice has drawbacks; in the case of Redux:
    boilerplate code to support the architecture - especially without Redux Toolkit
    additional layers of indirection
    developers must choose what part of the state is global - typically in React apps
    the learning curve, which could be initially demanding
    11

    View Slide

  12. Best scenarios for Redux
    The more of the following factors are present in your application, the more effective
    Redux will be:
    Multiple, heterogeneous sources triggering state updates
    Chaotic state fragmentation at different levels
    Complex, strongly domain-related update logic
    Message-/Event-driven architecture
    Vast, shared codebase
    12

    View Slide

  13. Part 2
    Essential building blocks

    View Slide

  14. The architecture at a glance
    14

    View Slide

  15. Section 2.1
    Store, actions, reducers

    View Slide

  16. The store
    « The store is an object that holds the application's state tree.
    There should only be one single store in a Redux app »
    The store provides just a basic set of methods - in particular:
    getState() : returns the current state
    dispatch(action) : sends an action (a message) to the store, always triggering
    the computation of the next state. There's no other way to change the state in the
    store
    subscribe(listener) : adds a listener that gets notified right after the computed
    next state becomes the store state. Returns a function to cancel the subscription
    16

    View Slide

  17. The state must be serializable
    The state contained in the store can range from a primitive value up to a complex
    object graph having arbitrary depth.
    One should store only serializable objects as well as primitive values - to ensure
    portability in a variety of situations such as persistence and debugging. In particular, the
    state should definitely not contain:
    class instances
    functions
    promises
    It is usually recommended that entities - i.e., domain objects having an ID, be stored in
    a dedicated state branch and referenced by ID from within other branches
    17

    View Slide

  18. Store creators
    « A store creator is a function that creates a Redux store »
    The essential store creator is createStore() , provided by Redux and supporting 3
    args:
    reducer : the reducer (also known as root reducer) used by the store - a function
    returning the new state whenever an action is dispatched
    initial_state (optional): if missing or set to undefined, the reducer will use the
    default value of its state parameter
    enhancer (optional): higher-order function that takes a store creator and returns a
    modified store creator; the most common enhancer is applyMiddleware() , to
    apply middleware extensions 18

    View Slide

  19. Deprecation warning for createStore()
    createStore() is a basic store creator - effective for introductory examples.
    It is nowadays deprecated, as the recommended store creator has become
    configureStore() - provided by Redux Toolkit and described in the related section
    of this presentation.
    Existing code should still compile until future major versions - but it is definitely
    advisable to start adopting the new approach
    19

    View Slide

  20. Just a single store?
    « It is possible to create multiple distinct Redux stores, but the
    intended pattern is to have only a single store »
    Considering that the Redux ecosystem revolves around the one-store principle, it is
    generally advisable to adhere as much as possible to this guideline
    20

    View Slide

  21. Actions
    « An action is a plain object that represents an intention to change the state »
    Actions can be arbitrary JavaScript objects, with just one requirement: they must have a
    type field, that should be a specific string (not a symbol), such as in:
    const ADD_BEAR = "bears/add";
    interface AddBearAction {
    type: typeof ADD_BEAR;
    name: string;
    }
    As shown above, type constants often has / characters to conventionally organize the
    actions into logical trees; however, Redux code is usually based on mere string equality
    21

    View Slide

  22. Action creators
    « An action creator is a function creating a specific action type »
    An action creator is conventionally the only way to create an action - as it ensures that
    the action's fields are correctly initialized.
    This is a typical pattern for creating the action seen in the previous slide:
    export function addBear(name: string): AddBearAction {
    return {
    type: ADD_BEAR,
    name
    };
    }
    22

    View Slide

  23. Feature-related action type
    It is recommended to define a dedicated type unifying all the interfaces related to the
    same set of actions in the domain; for example:
    type BearAction = AddBearAction | ClearBearsAction;
    23

    View Slide

  24. Reducers
    « A reducer is a pure function that receives the current store
    state and an action, and must return the new state »
    Reducers are pure functions - they cannot have side effects. In particular:
    reducers cannot modify their arguments - especially the state
    reducers cannot depend on the context - e.g, reading/writing global variables
    reducers cannot perform side-effects (deleting files, calling impure functions, ...)
    reducers cannot contain async logic - such as REST API calls
    reducers must be deterministic they cannot rely on any external environment or
    even random number generators or Date.now() - use action creators instead
    24

    View Slide

  25. Writing a basic reducer
    export function bearReducer(
    state: ReadonlyArray = [], //The default arg is the initial state
    action: BearAction //This is why we have defined the BearAction type
    ): ReadonlyArray {
    //A switch is frequent, but not mandatory
    switch (action.type) {
    case ADD_BEAR:
    return [...state, { name: action.name }];
    case CLEAR_BEARS:
    return [];
    default:
    //You must return the current state by default
    return state;
    }
    }
    25

    View Slide

  26. The principle of state immutability
    « Redux expects that all state updates are done immutably »
    Never perform - in plain Redux and plain TS - something like this within a reducer:
    state.myField = 90;
    Instead, to change one or more fields, you must create a new object holding the
    state - for example, via the spread syntax
    State immutability provides a wide range of benefits, such as:
    code safety: no unexpected state modifications
    performances: Redux is based on shallow equality checking
    26

    View Slide

  27. Errors in reducers
    « By default, errors thrown within a reducer bubble up the call
    stack, thus terminating the computation induced by
    dispatch() »
    Should reducers contain try/catch ? Most probably no, because:
    syntax errors - especially in vanilla JS/ES - should emerge as quickly as possible
    domain errors should be expressed via dedicated state fields in lieu of throwing
    Finally, the default behaviour can be arbitrarily customized by introducing middleware -
    a topic we are going to discuss in the next section
    27

    View Slide

  28. Redux basics in action
    Please, refer to the GitHub project
    28

    View Slide

  29. Combining reducers
    The store demands exactly one reducer - named root reducer; however, dealing with a
    huge, monolithic state within a single function would be rather inconvenient
    « You split the root reducer into multiple, independent sub-
    reducers - each operating on different parts of the state object »
    This can be achieved in many ways - and mainly via the combineReducers() function:
    const rootReducer = combineReducers({
    bears: bearReducer,
    rangers: rangerReducer
    });
    29

    View Slide

  30. Understanding reducer combination
    Whenever a compound reducer is called, the following logical steps occur:
    1. it receives the current state and the dispatched action
    2. it creates a new object having the same fields as the object passed to
    combineReducers() :
    i. each sub-reducer is called with the value of the related field in the compound state - but
    also with the action passed to the compound reducer
    ii. the result of each sub-reducer is assigned to its related field in the new object
    3. The return value of the compound reducer will be:
    the new object - if at least one sub-reducer returns a different reference
    the current state otherwise 30

    View Slide

  31. One action, potentially multiple effects
    In the previous example:
    bearReducer() computes the value for the bears field of the state
    rangerReducer() computes the value for the rangers field of the state
    Despite this output differentiation, each reducer receives every single dispatched
    action; consequently:
    « Different reducers can react to the same action type »
    For example, different parts of the domain model could react differently to the same
    global event
    31

    View Slide

  32. Where should the business logic reside?
    In action creators:
    +: traditionally easier approach for beginners
    -: actions become thick objects with duplicated business logic
    -: the effects of action creators are not affected by time-travel debugging
    In reducers (recommended):
    +: single source of truth for computations
    +: pure functions foster code robustness
    32

    View Slide

  33. Setters and events
    In Redux, one dispatches actions - that are, technically speaking, messages.
    In semantic terms, however, messages can be interpreted as:
    setters - like bears/add - that describe commands to be executed by the system
    events - like bears/added - which denote something that already happened and
    that might trigger business logic within the system
    Redux is technically unopinionated, and different architectural styles might have
    different visions - not to mention a hybrid approach: what matters is to be aware of the
    semantic distinction
    33

    View Slide

  34. Selectors
    « Selectors are pure functions that know how to extract specific
    pieces of information from the store state »
    function getBearNames(state: ReadonlyArray): ReadonlyArray {
    return state.bears.map(bear => bear.name);
    }
    Selectors are essential in terms of minimalist design, as they:
    prevent duplicated domain logic in different parts of the system
    can express calculated properties, thus preventing redundant data in the store
    Memoization libraries like reselect can remarkably enhance selector performances
    34

    View Slide

  35. Section 2.2
    Middleware

    View Slide

  36. Middleware alters the dispatch process
    « Middleware provides a third-party extension point between
    dispatching an action, and the moment it reaches the reducer »
    In particular, middleware:
    exists for the lifetime of the store
    can see all dispatched actions and dispatch actions by itself
    can customize the dispatch process
    36

    View Slide

  37. Async actions
    Async actions are action instances that are not serializable - and would not be
    accepted by a reducer: for example, a function, or a Promise .
    « Async action can be dispatched - but only serializable actions
    should enter the reducer »
    More precisely, it is the middleware chain that detects async actions and dispatches
    actions accordingly
    37

    View Slide

  38. Middleware in detail
    « A middleware is a higher-order function that composes a
    dispatch function to return a new dispatch function »
    The redux package exports two fundamental type definitions:
    type MiddlewareAPI = { dispatch: Dispatch; getState: () => State };
    represents a minimalist view of the store - with just dispatch() and getState()
    type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch;
    Middleware needs to be plugged into the store: by design, you can only do so when
    creating the store - by passing the list of middlewares to applyMiddleware()
    38

    View Slide

  39. Applying middleware
    One of the first problems with Redux is logging: in particular, store subscribers can
    only access the store state, not the actions.
    The solution is to apply middleware when creating the store - e.g. via redux-logger :
    import { createStore, applyMiddleware } from "redux"
    import { createLogger } from "redux-logger"
    const logger = createLogger({...}) //One can pass a variety of options
    const reducer = ...
    export const createCustomStore = () => createStore(
    reducer,
    applyMiddleware(logger)
    )
    39

    View Slide

  40. Redux core - extended example
    Please, refer to the GitHub project
    40

    View Slide

  41. Middleware in the ecosystem
    Advanced reducers: reduce-reducers, redux-ignore, reduxr-scoped-reducer
    Sophisticated listening: redux-watch, redux-subscribe, ...
    Asynchronous actions: Redux Thunk, Redux-Saga, ...
    Action batching: redux-batched-actions, redux-batch
    Subscription: redux-batched-subscribe
    Logging: redux-logger, redux-log-slow-reducers
    Debugging: Redux DevTools
    41

    View Slide

  42. Creating middleware
    Creating one's custom middleware via a factory method is actually fairly straightforward
    - especially when using curried notation for lambdas:
    import { MiddlewareAPI, Dispatch, Action, Middleware } from "redux";
    export function createCustomMiddleware<
    TAction extends Action
    >(/*Custom params used to create the middleware*/): Middleware {
    //This series of chained lambdas represents the middleware
    return (store: MiddlewareAPI) =>
    (next: Dispatch) =>
    (action: TAction) => {
    //TODO: Add here the actual code of the middleware
    //Call next(action) to pass the action to the next mw in the chain.
    //You can also call store.dispatch() with any new action.
    };
    }
    42

    View Slide

  43. Custom middleware in action
    Please, refer to the GitHub project
    43

    View Slide

  44. Part 3
    Redux Toolkit

    View Slide

  45. Effective minimalism
    « Redux Toolkit makes it easier to write good Redux
    applications and speeds up development, by baking in our
    recommended best practices, providing good default behaviors,
    catching mistakes, and allowing you to write simpler code »
    It is available as a package via NPM: @reduxjs/toolkit
    45

    View Slide

  46. Section 3.1
    Enhanced store and middleware

    View Slide

  47. Battery-included store
    Redux Toolkit provides an enhanced store creator - configureStore() , taking an
    options object having the following core fields:
    reducer : can be either a reducer or the object that would be passed to
    combineReducers()
    preloadedState : optional initial state
    devTools - enables/disables/configures Redux DevTools in development only:
    if true (the default), Redux DevTools will have a predefined configuration
    if a DevToolsOptions instance, it will be used to configure Redux DevTools
    47

    View Slide

  48. Middleware in bundle
    Redux Toolkit's configureStore() automatically applies middleware to the new store
    - especially Redux Thunk; additionally, development and production have different
    default lists of applied middleware.
    Middleware can be customized by passing another field to configureStore() 's
    options - middleware - which can be:
    an array of middleware - in lieu of calling applyMiddleware()
    a higher-order function, taking the getDefaultMiddleware() function and
    returning, especially via the prepend() and concat() methods, an array of
    middleware instances - if you want to add middleware to the default list
    48

    View Slide

  49. Composable and efficient selectors
    Redux Toolkit exports createSelector() - actually provided by the reselect package.
    To create a selector, you can:
    just define it as a function (state) => T , with arbitrary T
    use createSelector() , which takes:
    an array of source selectors, defined in either way - ensuring composability
    a function whose arguments will be the actual return values of the sources and that must
    compute the selector's value
    This kind of compound selectors relies on memoization for efficiency: the value returned
    by a compound selector stays the same as long as so do the sources
    49

    View Slide

  50. Section 3.2
    Simplified actions and reducers

    View Slide

  51. Simplified action creators
    createAction(type: string, [prepare]) returns an action creator with:
    toString() : the action type - so, there is no more need for separate string
    constants
    match(action) : true if the action type matches - very useful in type guards
    Furthermore, this action creator returns a PayloadAction - an interface
    extending Action and having the additional payload field - standardizing action
    payloads all over Redux Toolkit
    51

    View Slide

  52. Generating a custom payload
    The prepare argument in createAction() is optional:
    if prepare is omitted, the action creator takes just one argument - defined by the
    TPayload type argument of createAction()
    if the type argument is omitted as well, it defaults to undefined - and the
    action creator will take no argument
    otherwise, prepare must be a function taking any argument list and returning an
    object of type {payload: TPayload} - where TPayload is arbitrary
    The actual creator will automatically add the type field
    52

    View Slide

  53. createAction() in action
    Please, refer to the GitHub project
    53

    View Slide

  54. Simplified and flexible reducers
    createReducer(initialState, (builder) => {}) simplifies the implementation of a
    reducer via a fluent builder in lieu of the traditional switch construct.
    A few aspects are definitely worth noting:
    the very action creators produced by createAction() should be the case
    argument of builder.addCase(case, (state[, action]) => state) : this
    ensures type checking for each function passed as the second argument
    Each subreducer can modify the state! Because createReducer() internally
    uses the immer library to actually manipulate a proxy wrapped around the state
    a default case can be specified via the builder, but it is not mandatory - the current
    state is returned by default
    54

    View Slide

  55. createReducer() in action
    Please, refer to the GitHub project
    55

    View Slide

  56. Section 3.3
    Slices

    View Slide

  57. Creating a slice
    createSlice() is the simplest and most elegant way to create both the actions and
    the reducer for a branch of the state - replacing also createAction() and
    createReducer() :
    const bearSlice = createSlice({
    name: "bears", //The prefix for all the related action types
    initialState: [{name: "Yogi"}],
    reducers: {
    //This creates both the action creator and the related reducer case
    bearAdded(state: Bear[], action: PayloadAction) { //The action is optional
    state.push({name: action.payload}) //Fake mutability via Immer
    }
    bearsCleared(state: Bear[]) {
    state.length = 0
    }
    }
    })
    57

    View Slide

  58. Exporting from a slice
    Once you have created a slice within a module, you can export both the action creators
    and the overall reducer:
    export const { addBear, clearBears } = bearSlice.actions;
    export const bearReducer = bearSlice.reducer;
    The action creators can be imported and called as usual
    The reducer takes into account the cases declared within the slice and can in turn
    become a sub-reducer passed to a combining function such as
    combineReducers()
    58

    View Slide

  59. Decoupled action creator and reducer in a slice
    You can actually decouple the action creator and the reducer in a slice as well:
    type AddBearPayload = {name: string, token: number}
    (...)
    reducers: {
    addBear: {
    //This works just like prepare in createAction()
    prepare: (name: string) => {
    //As usual, you definitely mustn't specify the "type" field here
    return { payload: { name, token: DateTime.now() } }
    },
    //This works just like a case in createReducer()
    //Static typing ensures that action is PayloadAction
    reducer: (state, action) => {
    state.push({name: action.name})
    }
    }
    59

    View Slide

  60. Extra reducers
    In slices, each reducer acts like a switch case applied to its specific action type,
    because of the coupling with action creators provided by createSlice() .
    However, a slice might need to logically handle actions whose creators are not defined
    in the slice itself.
    The options passed to createSlice() can have another, optional field,
    extraReducers : its value must be a lambda taking just a builder which works like in
    createReducer() .
    Ultimately, both reducers and extraReducers are combined into the slice's
    reducer
    60

    View Slide

  61. Slices in action
    Please, refer to the GitHub project
    61

    View Slide

  62. Section 3.4
    Redux Thunk via Toolkit

    View Slide

  63. Is Redux synchronous?
    By design, Redux is synchronous:
    you cannot add asynchronous logic to reducers, as they must be pure functions
    you could add async logic to action creators, but that is not recommended
    both the state and the action received by the reducer must be serializable - so
    promises and functions cannot be dispatched
    However, Redux Thunk - that configureStore() applies by default - alters the
    dispatch process to support asynchronous operations by dispatching thunks
    63

    View Slide

  64. Thunks
    « A thunk is a function that wraps an expression to delay its
    evaluation, such as () => 42 »
    In the case of Redux Thunk, a thunk is a higher-order function receiving two
    arguments, actually referencing the store methods:
    dispatch()
    getState()
    You usually return thunks from thunk creators - functions whose arguments customize
    the thunk behavior
    64

    View Slide

  65. Dispatching thunks
    Dispatching a thunk runs its code, which can include:
    reading the current state of the store
    dispatching actions or even other thunks
    performing one or more await , if the thunk is an async function
    The body of the thunk is arbitrary - and it is executed as soon as it is dispatched
    65

    View Slide

  66. Typical thunk workflow
    Most often, async operations follow a dedicated pattern:
    1. Dispatch an action notifying that the operation is pending: this usually alters
    the state so that the application's view displays some Waiting... component
    2. Perform arbitrary operations, that might also involve await on promises
    3. Finally, dispatch an action notifying:
    The success of the operations - with the related payload, which is usually the
    result of the computation process
    The failure of the operations - maybe with the related error
    In both cases, the UI is updated accordingly
    66

    View Slide

  67. Simplified thunk creation
    When TypeScript's static type checking is enforced, even the implementation of a basic
    pattern for asynchronous operations via Redux Thunk can become exponentially
    cumbersome.
    Consequently, Redux Toolkit exports the createAsyncThunk() function to define a
    thunk creator, which in turn creates a thunk that:
    executes an arbitrary block of code - the actual logic, encapsulated within a
    Promise
    keeps track of the async action lifecycle by dispatching the related actions -
    pending , fulfilled and rejected - all sharing the same prefix
    67

    View Slide

  68. Using createAsyncThunk()
    createAsyncThunk() , in a fairly basic form, takes the following arguments:
    the type prefix shared by the 3 workflow actions
    a factory function - whose optional arguments are:
    arg : the argument that could be passed to the thunk creator
    thunkAPI - object with essential APIs such as dispatch() and getState()
    The thunk creator returned by the function has 3 properties - pending , fulfilled
    and rejected - that reference the action creators, as returned by createAction() , of
    the lifecycle actions - so they can be used in reducer cases
    68

    View Slide

  69. Thunks in action
    Please, refer to the GitHub project
    69

    View Slide

  70. The return value of dispatch()
    The dispatch() method provided by the store dispatches actions to the middleware
    chain and ultimately to the reducer - but its return value has interesting properties:
    by default, dispatch() returns the dispatched action
    however, if middleware is applied to the store, dispatch() returns the value
    returned by the middleware
    in particular, when using Redux Thunk combined with createAsyncThunk() ,
    dispatch() returns the Promise returned by the thunk - consequently,
    one can use await on its return value, within an async function
    70

    View Slide

  71. Part 4
    React Redux

    View Slide

  72. One-way data flow
    One-way data flow is a paradigm adopted by both React and Redux:
    in Redux, via the immutability of the current state, plus the dispatch() mechanism
    in React, information flows via properties from a component to its sub-components
    React's waterfall approach actually works - but it has a major drawback: if a node holds
    data that are required in a remote descendant, such data must be passed to all the
    intermediary node as well.
    This is an inelegant but fairly infrequent scenario, although there may be more compelling
    reasons to adopt Redux, so:
    « Don't use Redux until you have problems with vanilla React »
    72

    View Slide

  73. Introducing React Redux
    « React Redux is the official Redux UI binding library for React »
    The very essence of React Redux is:
    subscribing to the store created by Redux
    updating the UI:
    only when needed - that is, if the state actually changes
    only where needed - to minimize DOM modifications
    73

    View Slide

  74. Where should the state reside?
    When Redux is added to a React app, there are at least 3 possible locations where
    each piece of the app state can reside:
    global state - provided by the Redux store:
    it can be modified only by dispatching actions to the store
    data can be extracted from it via selectors
    As the name implies, it is best suited for app-wide global information
    component state - living within the single React component and provided, for
    example, by the useState() hook. Typically recommended for form controls
    context state - an intermediate state container provided by React
    74

    View Slide

  75. Components: connected or presentational?
    « When a React component interacts with the Redux store, it
    becomes a connected component »
    Not all components need to be connected: on the contrary, most React components
    should be presentational - that is, relying just on their properties and maybe their
    internal state.
    Consequently, in React Redux it is common to see connected components that fetch
    data from the Redux store and propagate such data to their sub-tree of presentational
    components
    75

    View Slide

  76. Plugging the Redux store into React
    React Redux is available via NPM as react-redux .
    After the installation, it takes just a few steps:
    1. Create the store, as you would in vanilla Redux - for example, you can export
    either the store instance or a store creator from a dedicated module
    2. In JSX, plug the Provider tag - usually as the parent of the application's root
    component tag passed to React's render() function - assigning the store
    instance to its store property:



    76

    View Slide

  77. Connecting components to the store
    To create a connected component:
    if the component is implemented as a class - which is less and less frequent, the
    connect() function is required - as well as the related boilerplate code
    in modern, functional React components, everything can be achieved via hooks:
    useSelector(selector) : returns the value it calculates from the current state
    within the store
    useDispatch() returns a reference to the store's dispatch() method
    77

    View Slide

  78. Performance optimizations
    Calling useSelector() many times with different selectors should be preferred
    over calling useSelector() once with an aggregating selector
    real performance gains can be achieved via createSelector() - which is
    provided by reselect as well as Redux Toolkit
    General advice also apply - for example, in reducers it is more efficient to return the
    very same state instance rather than a deep copy
    78

    View Slide

  79. Part 5
    Conclusion

    View Slide

  80. Global takeaways
    Do you really need Redux? Just like any other software component, you should
    adopt it in scenarios where the benefits outweigh the drawbacks
    Use createSlice() to create the action creators and the reducers for each slice
    of the domain: this will make you especially rely on PayloadAction
    Call configureStore() to create the store with default middleware and DevTools
    Invoke the store methods - especially dispatch() and getState()
    Rely on createAsyncThunk() for asynchronous operations
    Call useSelector() and useDispatch() in React Redux for elegant code
    80

    View Slide

  81. Redux projects - directory structure
    Redux is not opinionated in terms of directory layout, thus different choices are viable:
    group by feature: the recommended style, based on Domain-Driven Design. It
    seems wise to have a bears.ts module containing everything about bears:
    action type constants and action interfaces
    action creators (exported)
    reducer (exported)
    group by construct: à la Ruby on Rails - with one folder per construct
    ( actions/ , reducers/ , ...), each containing a module per feature ( bears.ts ,
    rangers.ts , ...)
    81

    View Slide

  82. Further references
    Redux - Official website
    Redux Toolkit
    Redux Thunk
    Redux Saga - a generator-based approach to async operations
    React Redux
    Redux - Style guide
    Middleware evolution - step-by-step architectural explanation
    React Redux sample app
    Elm - «A delightful language for reliable webapps»
    82

    View Slide

  83. Thank you! ^__^

    View Slide