Skip to main content

Betslip

Betslip is a self contained component accessed via the right hand side App Drawer used for placing bets. All UI components are located within this repo but most of the bet placement / state transition logics are located in a separate Betslip Repo. That repo is essentially an exported partial redux store + some relevant util methods that is consumed by the App.

Components Structure

This sections details the basic component heirarchy for the betslip. The Levels indicate the component depth from the outer screen component (which is Level 1)

Root Screen (Level 1)


Betslip.tsx

Contains:

Header (Level 2)


When logged in BetslipLoggedInHeaderRight.tsx - contains balance info and button to link you to your pending bets.

Betslip Groups (Level 2)


Contains 6 groups - BetslipGroup.tsx

Where each group contains a list of relevant individual bet items.

DescriptionContained Bet Types
Finalised Groupmixed
Processing Groupmixed
Singles GroupSingle
Exotics GroupExotic, Compound
Same Race Multis GroupSameRaceMulti
Multis GroupMulti, MultiAny
Same Game Multis GroupSameGameMulti

BetslipFooter.tsx

Contains:

Bet Item Components (Level 3)


BetslipSinglesItem.tsx

The regular ol' single bet, includes: sports, pyoo, mbo etc.

BetslipSinglesItem

BetslipExoticsBetContainer.tsx => BetslipExoticsItem.tsx

Exotics & compounds bets share mostly the same layout which is why we have a shared container component

BetslipExoticsItem

BetslipExoticsBetContainer.tsx => BetslipCompoundItem.tsx

Exists in exotics betslip group

BetslipCompoundItem

BetslipSameRaceMultisItem.tsx

BetslipSameRaceMultisItem

BetslipMultis.tsx => BetslipMultisItem.tsx

BetslipMultis

BetslipMultisAny.tsx => BetslipMultisAnyItem.tsx

BetslipMultiAnys

BetslipSameGameMultisItem.tsx - Only certain sports events have this available

BetslipSameGameMultisItem

Performance Considerations

Most of the high level performance considerations addressed here will apply to the rest of the repo as well. This section will focus more on the specifics.

Reducing Unnecessary Re-renders


This section will focus specifically on betslip structure, the usual code practices of reducing unnecessary re-renders still applies.

Background


The current betslip redux state structure stores all bet objects (as in all different bet types) in a single array state.betslip.bets which acts as the single source of truth.

This choice was made (in contrast to betslip v1 which stored bets in separate normalised objects based on bet type) to simplify the overall state for the betslip by treating all bet types as a child of "BaseBet" since a lot of the logic (i.e. adding/removing/placing bets etc) & properties (i.e. state, stake, transactionId etc.) are essentially the same.

This also greatly reduces the duplicate code.

For example, to update the stake of a bet:

Betslip v2

  • dispatch action updateStake(betId, newStake)

Betslip v1

  • dispatch one of the actions based on bet type
    • updateSingleBetStakeById
    • updateExoticBetStakeById
    • updateSameRaceMultiBetStakeById
    • updateMultiBetStakeById
    • updateSameGameMultiBetStakeById
    • etc.

There is a catch though when storing all bets in a single array instead of separate normalised objects; and that is if you don't structure your components properly you will end up with a situation where adding/removing/updating a bet will cause the entire bets array to regenerate a new value (as objects/arrays are not mutated) thereby potentially causing your entire betslip and also each unrelated bet item to re-render.

Imagine the scenario where you have 10 bets in your betslip and you want to set the stake of one of the bets to 100000. That will involve dispatching 6 updateStake actions and potentially causing all 9 unrelated bets to re-render 6 times.

Ensuring only the relevant bet is re-rendered


Take BetslipSinglesItem.tsx as an example. You have 2 single bets in your betslip and you are updating "Bet 1" (e.g. toggling price boost or editting the stake),

Approach 1: Bad - all bet items will re-render as bet is being passed down as an object which will have a different reference value when the redux bets state changes

/** Betslip group */
const BetslipGroup = () => {
return bets.map((bet: ISingleBet) => <BetslipSinglesItem bet={ bet }/>);
}

...

/** A single bet item */
const BetslipSinglesItem = ({ bet }) => {
/** Render the bet item based on bet object */
return ...;
}

Approach 2: Good - only the bet item being updated will re-render as betId is a string and strings are compared by value not reference.

/** Betslip group */
const BetslipGroup = () => {
const betIds = useSelector((state) => state.betslip.bets.filter(...).map((bet) => bet.id), shallowEqual);

return betIds.map((betId: string) => <BetslipSinglesItem betId={ betId }/>);
}
...

/** A single bet item */
const BetslipSinglesItem = ({ betId }) => {
const bet: ISingleBet = useSelector(getBetById(betId));
/** Render the bet item based on bet object */
return ...;
}

IMPORTANT CONCLUSION

So that was one example, but the rule of thumb is: never call a selector that touches the state.betslip.bets array on a component that isn't directly related to that list of bets or a single specific bet. E.g. if you end up calling useSelector(betslipSelectors.getMultiBet) (which constructs a "feature complete" multi bet object from the state.betslip.bets array) inside of Betslip.tsx you will cause the entire Betslip and all of it's unmemoised children to re-render everytime any bet related action happens (e.g. adding an unrelated SRM bet, updating the stake on an unrelated exotic bet).

Ensuring that createSelector from reselect is properly memoised


Because the betslip redux state is pretty massive & somewhat convoluted, if you ever find yourself having to create some new selectors (for specific pieces of data that may require non-trivial calculations/transforms on fast changing state values) either in RN repo or the betslip repo make sure to make use of createSelector.

If you are unsure of how to properly memoise selectors using createSelector, please read this:

Cache/pause betslip ui updates where possible when the betslip is not visible


The betslip is quite heavy both on UI and calculations and often you will have a scenario where you may have 20 bets in your betslip while you are doing other stuff in the app e.g. browsing events and once in a while you may be adding a bet. This whole time though the betslip could be closed. But just because the betslip it's not visible it still renders 20 bet items off screen (consuming memory) and those items could also be re-rendering depending on what you are doing.

The approach to fixing this issue is to cache or downright not render as much of the UI as possible while the betslip is closed and also making sure to not compromising the UX. I.e. you could just render null for Betslip.tsx when the betslip is closed but this will lead to a bad user experience as the content flashes in from a blank screen when the betslip is opened. Also, if the user is super tech savvy user is using the swipe gesture to open the betslip it will look like they are dragging a blank white rectangle across the screen.

How it is currently done (not perfect and definitely room for improvements): we cache the first 4 bets of each betslip group using useRef (which is non reactive and won't cause any re-renders when adding/removing bets from race card etc.). The 4 cached bet ids ensure that the user for the most part sees a screen that represents their expected betslip state during the opening transitions (instead of an empty screen).

Linking Betslip repo for local development

As Betslip uses npm and our RN repo uses yarn we need to use yalc to get the betslip repo to work locally.

Please see Linking Local Modules for the steps needed.

Remember! RN has it's own view for Betslip under app/features/betslip, but uses the business logic from the Betslip repo.