Code Contributing Guide
Code Reviewing
All code changes being contributed to the project need to be reviewed by peers before they are added to the project codebase. This helps ensure that the code is of high quality and meets project requirements.
To have your contributions reviewed, you must submit a Merge Request (MR). If you're not familiar with MRs, you can read more about them in the Gitlab documentation.
For a general overview of how we conduct code reviews across the Technology Department, you can read the Developer Code Reviews decision document. This document applies to all projects.
The information in this code contribution guideline is specific to the Native Apps project and supplements the Developer Code Reviews document.
Code Review Tips
Here are some additional tips to help you have the best possible experience reviewing and having your code reviewed:
- Focus on reviewing the code, not the person writing it. Be kind and constructive in your feedback.
- It is always harder to read code, than it is to write it. Write code that is easy to read and understand for humans, not just machines.
- Ask plenty of questions before passing judgment. The writer's intentions may not be immediately obvious, and asking questions can help clarify any confusion.
- Set aside time each day for reviewing MRs. Reviewing code is everyone's responsibility, and doing it regularly can help ensure that the project stays on track.
- When leaving comments on MRs, consider following the Conventional Comments guidelines to ensure clear and effective communication.
Code Ownership
Code Ownership is a concept we apply within the Native Apps project to determine who is responsible for maintaining and upholding the quality and maintainability of various parts of the project's codebase. Every part of the codebase, whether an existing file is being modified, new ones added, or old ones removed, will have one or more designated Code Owners.
Code Owners are responsible for maintaining the part of the project's codebase they own, reviewing changes made to it, and educating others on it due to their existing domain knowledge and experience. They are the go-to people for questions about that part of the codebase, and they have the final say on changes made to it. Code Owners are assigned based on their experience and knowledge of the part of the codebase in question, as well as their availability and willingness to take on the responsibility.
For an in-depth explanation on the concept of Code Ownership and how it applies within the Native Apps project, you can read the Code Ownership documentation page.
Code Owner Approvals
When submitting an MR for your code contribution to be reviewed, if the changes you are making affect a part of the codebase that has Code Owners assigned to it, those Code Owners will automatically be required to review and approve your changes before they can be merged.
These approvals will be required in addition to the standard approvals from "Any eligible approvers" and any other Merge Request Approval rules that may be in place for the project repository generally, or a branch specifically.
Code Owners approvals are determined automatically by Gitlab, by parsing the contents of the CODEOWNERS File, which lists out the various code paths for the project for which groups or individual users working on the project are assigned ownership, as Code Owners.
To read more about the CODEOWNERS file and how it works with code owners approvals, visit the CODEOWNERS documentation page.
Code Owners approvals are currently optional, and don't impact the ability for an MR to be merged.
These will become a requirement at a later date, once the Native Platform (AKA App Core) team has received more feedback on the introduction of Code Ownership in the project, and the various teams of developers working on the project familiarise themselves with the practice.
Code Commits
We adhere to the Conventional Commits specification when creating code commits.
This is to ensure that:
- The git history is easy to traverse.
- Each commit can be easily understood for the type of change, scope of the change, and summary of changes just from reading the commit message.
- CI tooling can be used to automate the creation of changelogs/release notes, to be added to releases.
Whilst working on a branch, locally, you are free to create however many WIP commits you feel the need to, with whatever formatting you want for the commit message.
However once you are ready to create a new Merge Request to get your code reviewed and merged with the rest of the codebase, your commit messages will need to adhere to a the Conventional Commits spec, and more specifically to our own config file based on it.
Note: It is fine to have more than one commit, what matters is that each commit is meaningful and describes a specific chunk of your work. Hypothetically, if you were to have implemented a feature that required creating a new API file, a component to consume that API, and the associated tests, your commits could look like:
feat(new-feature): implemented API for the fancy new feature
feat(new-feature): created components to consume API and display response
test(new-feature): wrote tests for fancy feature API and components
Linting Commits
The formatting rules come from @commitlint/config-conventional which implements the aforementioned Conventional Commits spec.
- The formatting rules can be extended or modified inside of the commitlint.config.js file at the root of the project.
Once you create a Merge Request, your branch's commit messages will be linted on the merge request CI pipeline by using the commitlint library, during the lint
stage of the pipeline.
- The commit linting check is run on every push to a new branch or merge request, with the explicit exception of the
master
branch as it historically contains a lot of old commits that will never pass this check.
Here's some examples of valid commits, based on our commit lint rules.
feat(group-mode): add emoji support
chore: correct typing in race card context
fix(smg): incorrect odds shown
Writing conventional commits using commitizen
Commitizen is a CLI utility that allows you to format your commit messages, according to our own commitlint config file, commitlint.config.js.
It allows you to interactively choose from existing preset commit types and scopes in the config file without having to remember them and type them in manually.
To use it, simply type git-cz
or cz
in your terminal, after you've staged whatever files you want to commit.
Writing conventional commits using @commitlint/prompt-cli
If you'd rather use something else, there is commitlint's own prompt-cli tool.
To use it, type yarn commit
in your terminal, after you've staged whatever files you want to commit.
It doesn't offer the ability to interactively pick from types and scopes, but it presents you with a schema for the commit and lets you fill out each section step by step. You will see in your terminal a pro-forma of the commit, and then you manually fill out each section one step at a time. However, it doesn't tell you what the enforced options (if any) are there (e.g. the commit types aren't shown like they are for commitizen).
Linting commits using an IDE/editor extension
There may be extensions available for your IDE or code editor.
If you use VS Code, you could try the Conventional Commits extension
Linting commits using a git hook and husky
If you don't want any tools to help you before you make the commit, and simply want to have the commit linted after you try to write it (which is probably the hardest and most annoying option)
You can do the following.
# Active hooks
npx husky install
# Add hook
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'
Once this is done, when you run git commit -m <your message here>
inside of the terminal, your commit message will be evaluated by commitlint and the commit will fail if it doesn't adhere to the format set in commitlint.config.js
. **You can skip the check by passing --no-verify
to the commit command.
Tool-agnostic approach to commit linting locally
Currently .husky
files are being ignored in git so that everyone can still commit manually if they wish to without the git hook executing on every single commit, and without having to pass --no-verify
on every commit. This is the same reason why @commitlint/prompt-cli
and commitizen
haven't been configured in package.json
or the rest of the project to run on git commit
.
Failed commit-lint pipeline stage
If your branch or MR has a failing Pipeline due to commits causing a commitlint
error, you will need to fix your commits before proceeding.
You can fix your commits locally by rebasing them. Check the Git Workflow section below for more details on that.
Version control and Branching strategy
For this project, we've opted to follow the Trunk Based Development (TBD) model for how we manage our file version control, and how we manage branches in our code repository.
To quote Paul Hammant, one of its main proponents, Trunk Based Development is
A source-control branching model, where developers collaborate on code in a single branch called ‘trunk’ , resist any pressure to create other long-lived development branches by employing documented techniques. They therefore avoid merge hell, do not break the build, and live happily ever after. __main or master, in Git nomenclature
You can find an extensive guide on what Trunk Based Development is, and additional topics surrounding it here.
Rebasing locally - Merging remotely
Alongside using TBD to streamline our branching strategy, we also employ rebasing to keep a tidy git history of our commits, eliminating a lot of noise in the repository history by squashing any work in progress or accident clean up commits.
If you have never used rebasing before, the rundown below take a step-by-step approach to showing you how we do it in our every day work.
For a breakdown of what Rebasing is, see Git's own article on the git rebase command itself.
For an explanation of the difference between Rebasing and Merging see this article over at Atlassian.
Rundown of the typical branching workflow for this project (Original version contributed by @Rhys.Geary)
- Checkout the
main
branch. - Pull down the latest changes for the
main
branch. - Create a new feature branch off of
main
branch for you to work on. - Make your changes on your new local feature branch.
- Stage/Add your changed files in git, and commit them, using Conventional Commits formatting for your commit messages.
- With a clean working branch (no staged or un-staged changes left), return to
main
branch by checking it out again - Pull the latest changes for that
main
branch - Checkout back to your feature branch
- Rebase off of the
main
branch so that your branch is up to date - At this point your commit history should be tidy, and you can push your work up to gitlab
- Open an MR against the
main
branch
Rebasing Example
Click to see a step-by-step example
Let's say you have just picked up the ticket NATIVE-4000: Change racing title to all uppercase
.
First thing to do is checkout locally to main
and ensure you are up to date:
git checkout main
git pull
Next create your feature branch ensuring the name used is logical and readable:
git checkout -b NATIVE-4000-change-racing-title-case
Make the changes required in the ticket and commit as needed. You may have created a lot of work in progress commits, or had to make some quick fixes to mistakes you discovered later with not so useful commit messages like "typo" or "fixes", so once you're done working on your branch, your commit history might look something like:
commit d0eb83fbbf2b805d19080e10c1504f30cbfbb53b (HEAD -> NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Wed May 26 12:57:09 2021 +1000
feat(racing): Updated racing title to uppercase
commit d98347gv9345vbg93480e10c74hg93j0cbfbb72a (NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Wed May 26 10:27:09 2021 +1000
made a few cheeky changes
commit d049578gv4039587h4353096gh09930948357hg6 (NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Tue May 25 16:43:22 2021 +1000
whoops messed up here
.....
We adhere to conventional commits, so while all your local commits do not need to follow that standard, the ones that end up in the repository's git history must stick to that format. As you can see there are a couple of dud commits here that we want to combine into the one or a few useful commit, with a message formatted to follow conventional commits.
The following steps assume you are working using git
in your terminal, and not your any code editor's integrated git
tools. These steps should act as a guide no matter what you use.
Start by getting the number of commits to the start of the branch:
git log
Or if you prefer a fancier layout showing with an illustrated git history:
git log --graph --decorate --pretty=oneline --abbrev-commit
After running git log
you should see an output like the example above
commit d0eb83fbbf2b805d19080e10c1504f30cbfbb53b (HEAD -> NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Wed May 26 12:57:09 2021 +1000
feat(racing): Updated racing title to uppercase
commit d98347gv9345vbg93480e10c74hg93j0cbfbb72a (NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Wed May 26 10:27:09 2021 +1000
made a few cheeky changes
commit d049578gv4039587h4353096gh09930948357hg6 (NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Tue May 25 16:43:22 2021 +1000
whoops messed up here
commit dha3803ha839fh30a8fh30a3fha30fh0a83fh0n0a (origin/main, main)
Author: your.colleague <[email protected]>
Date: Wed May 25 11:27:23 2021 +1000
feat(sports): added some way cool feature
.....
You will have to point git
to the commit just before the ones you want to change. In this case we can see that the commit right after ours is the 4th commit. This will be a lower or higher number depending on how many commits you have on your branch, but generally look for the commit just before you started making yours, and point the rebase to that commit.
Perform an interactive rebase, where [NUMBER] is the number of commits you counted back to the start of the branch history:
git rebase -i HEAD~[NUMBER]
In our example, this would be git rebase -i HEAD~3
.
This will launch the interactive git rebase CLI in your terminal. You will see that each line is on of the commits that we are editing with the rebase tool, and each has the word pick
in front of it. You have several options for what you can do with each commit.
You can move the commit to a different position, changing when those changes are applied, in what order, by simply cutting and pasting the commit to a different line.
You can also change the pick
word to several other options that will have a different effect:
pick
keeps that commit, leaving it as is.edit
allows you to make code changes to the commit as if though you were still working on it.reword
allows you to change the commit message, to fix it or make it better.squash
will merge the commit with the one above it, and keep this commits message, in the commit description of the commit that it is getting merged into.fixup
will merge the commit with the one above it, and forget this commits messagedrop
will delete the commit, like it never happened.
When wanting to combine commits, you will be choosing squash
or fixup
. You will need to move the commits you want merged, to be under the commit you want to keep. So the list of commits in the rebase CLI:
pick 8357hg6 whoops messed up here
pick bfbb72a made a few cheeky changes
pick bfbb53b feat(racing): Updated racing title to uppercase
# Rebase bfbb53b..8357hg6 onto bfbb53b (3 commands)
Would become this:
pick bfbb53b feat(racing): Updated racing title to uppercase <-- the commit we want to keep
fixup bfbb72a made a few cheeky changes
fixup 8357hg6 whoops messed up here
# Rebase bfbb53b..8357hg6 onto bfbb53b (3 commands)
Follow the hints in your terminal to complete the rebase and the changes you've specified should be executed one by one. If any problem arise, git
will flag them for you in the terminal, so be sure to read what it says carefully. In any case, if anything goes wrong or you want to exit out of the rebasing without making any changes, you can simply type git rebase --abort
and everything will revert back to before you started rebasing in the first place.
If you are done making changes to your commits on your local working branch - it's now time to push them up to the remote repository.
First we will want to update your local working branch with whatever changes might have happened on the main
branch while you were working. It is important that we take those changes from the remote main
branch, or we update our local main
branch first
With your working branch still checked out, you can do all of this easily with the following command:
git pull origin main --rebase
This will do two things for you:
git fetch
the latest changes fromorigin/main
- rebase your commits on top of the latest commits from the now up-to-date
main
branch
NOTE: If any merge conflicts exist between your commits and the fresh changes pulled in from the remote main
branch, whilst you are rebasing, the rebase tool will pause and ask you to resolve them. Resolve the conflict as you normally would, but DO NOT COMMIT the changes. Instead, just type git rebase --continue
as the CLI instruct (make sure you look at what the terminal says!) and it will do the committing for you automatically.
After this your git history should be up to date and contain the commits from master with your commit being the latest:
commit da8w8w7afw9f7wa98f7w9af7wa9f87af0w7f0a007 (HEAD -> NATIVE-4000-change-racing-title-case)
Author: example.you <[email protected]>
Date: Wed May 26 12:57:09 2021 +1000
feat(racing): Updated racing title to uppercase
commit dha3803ha839fh30a8fh30a3fha30fh0a83fh0n0a (origin/main, main)
Author: your.colleague <[email protected]>
Date: Wed May 25 11:27:23 2021 +1000
feat(sports): added some way cool feature
Once that is completed, changes are committed, latest version of the target branch is merged in and there is no conflicts, push your local branch up.
If you haven't yet pushed up your branch since creating it, you should be able to just enter the following command to add your branch to the remote repository:
git push --set-upstream origin NATIVE-4000-change-racing-title-case
Any further work you make on the branch will essentially follow all of the same steps as above, except that after rebasing the latest changes from the main
branch onto your local feature branch you're working on, or when rebasing to tidy up or edit your commits, will mean that you have to "force push" your changes, instead of just pushing them.
This is because of how rebasing works, it re-writes the history of your branch, replacing it. This will mean that your local and remote branch don't sync up anymore, and by force pushing your changes, you are replacing your remote branch with the edits you made locally.
To do this, all you have to do is add --force-with-lease
at the end of your push command, like so.
git push --force-with-lease
REMEMBER: Only rebase (and force push) changes to a branch that is not public, meaning other people aren't actively checking it out and making changes of their own. If you do rebase and force-push your changes onto a remote branch that someone else is working on, your re-written history will make it so git doesn't know how to compare their changes if they need to push their commits now. This is not catastrophic, just really annoying to recover from. This is why the golden rule of rebasing is rebase locally, merge remotely.
When you are done making changes and are ready to have your code reviewed and merged back into the main
branch, open a new Merge Request (MR).
The MR should be as descriptive as possible, this includes before and after screenshots/videos of the changes and any pertinent information other developers will need to understand the context and impact of these changes. For ease of use, you can use one of the pre-formatted templates from the Template drop-down menu on the MR page. The MR should be assigned to yourself.
Once it has enough approvals and the pipeline finishes successfully the MR can be merged.
Fixing and Hot-fixing
Please see this dedicated wiki page for how to do fixing and hot-fixing of defects found in release by QA.
Code Linting, Formatting and General Code Cleanliness
Linting
Catching bugs, and warning about bad code practices.
We use ESLint to lint and format our codebase, and have our own ESLint configuration which enforces our style of coding and ensures certain coding practices are followed. You can see all of the presets we employ and any overrides with notes on override reasoning and links to the relevant rules inside of the eslintrc.js
file at the root of the project.
Formatting
Ensuring our code has one standardised format that everyone can get used to reading, whilst allowing anyone to write the code however they wish, by automatically formatting the code prior to merging with the rest of the codebase.
We use a .editorconfig
file to enforce basic code formatting guidelines for whatever code editor or IDE you use. You can learn more about Editorconfig here.
To automatically format our code across the entire project, we use Prettier, a code formatting tool that will change how your code is formatted (indentation, spacing, etc.) automatically once you save the file in which you're writing or changing some code, ensuring that it matches the format that everyone else is used to reading.
We have only a couple of overrides for the default Prettier rule set, and you can see them in the .prettierrc.js config file. If you need to have Prettier ignore a certain file's formatting, then you should add it to .prettierignore.
General Code Cleanliness
While we aim to automate away as much of this as possible, there is still work that needs to be done by developers to ensure their code is clean and readable. It's important to remember that at any time someone completely foreign to your code might have to open it, reason about it, and understand it to the point of being able to work on it. If your code is poorly laid out and difficult to read it makes it harder for anyone else to work on it, or even you to come back to it after time away.
The Clean Code JS repo is an adaptation of the popular book book Clean Code and great resource for writing clean, readable code. It contains basically everything you need to know and we highly suggest all devs read it and strive to integrate it into their work.
These are some of the more common issues seen in code that are easily avoided:
Lack of whitespace
const Component = (props: Props) => {
const { id } = props;
const [data, setData] = useState(undefined);
const [loading, setLoading] = useState(false);
const fetchRaces = async () => {
setLoading(true);
const data = await fetchRaces(id);
setData(data);
setLoading(false);
};
useEffect(() => {
fetchRaces();
}, []);
const renderRaces = () => {
return data.map((race) => (
<View>
<Text>{race.name}</Text>
<Text>{race.startTime}</Text>
</View>
));
};
return <View>{renderRaces()}</View>;
};
In this example all the declarations and functions are bunched together making it hard to reason about the component. It's also more difficult to see where the component starts and ends, as well as jump to different parts of the component. Whitespace is free, and should be utilised to make your code easy and pleasant to read.
While exactly how this is done is up to the dev's opinion, a good starting point is to bunch things by their repsonsibility and relationship to each other, and put space around function declarations. In this below example we have grouped our state declarations, then added space around the fetchRaces
and renderRaces
declaration, as well as the useEffect
. The result is much easier to read.
const Component = ({ id }: Props) => {
const [data, setData] = useState(undefined);
const [loading, setLoading] = useState(false);
const fetchRaces = async () => {
setLoading(true);
const data = await fetchRaces(id);
setData(data);
setLoading(false);
};
useEffect(() => {
fetchRaces();
}, []);
const renderRaces = () => {
return data.map((race) => (
<View>
<Text>{race.name}</Text>
<Text>{race.startTime}</Text>
</View>
));
};
return <View>{renderRaces()}</View>;
};
Single letter or unnecessarily short variable names
This one is fairly self explanatory:
// don't do this
const races = data.map((r) => <Race {...r} />);
const legCat = getLegacyCategory(category.id);
const keyEx = (item) => item.id;
// do this
const races = data.map((race) => <Race {...race} />);
const legacyCategory = getLegacyCategory(category.id);
const keyExtractor = (item) => item.id;
Lack of comments
Code should be mostly self-documenting, but without context and explanation it's impossible for a dev foreign to the code to easily understand it. Comments should be used to explain the why of the code, not the what.
In this example someone new to the code would have a hard time understanding what is being achieved.
const futuresData = useMemo(
() =>
(data?.futures?.nodes ?? []).reduce<
Array<{
title: string;
data: SportFuture[];
}>
>((acc, future) => {
if (!future || !future?.competition?.name) return acc;
const compIndex = acc.findIndex(
(comp) => comp.title === future?.competition?.name
);
if (compIndex === -1) {
acc.push({
title: future.competition.name,
data: [future as SportFuture],
});
} else {
acc[compIndex]?.data.push(future as SportFuture);
}
return acc;
}, []),
[data?.futures.nodes]
);
Easily fixed by adding comments
// Group the data by competition name
const futuresData = useMemo(
() =>
(data?.futures?.nodes ?? []).reduce<
Array<{
title: string; // Competition name
data: SportFuture[];
}>
>((acc, future) => {
// Skip the future if it doesn't have a competition name
if (!future || !future?.competition?.name) return acc;
// Find the index of the competition in the accumulator
const compIndex = acc.findIndex(
(comp) => comp.title === future?.competition?.name
);
// If the competition doesn't exist in the accumulator, add it
if (compIndex === -1) {
acc.push({
title: future.competition.name,
data: [future as SportFuture],
});
} else {
// Otherwise add the future to the existing competition
acc[compIndex]?.data.push(future as SportFuture);
}
return acc;
}, []),
[data?.futures.nodes]
);
Importing and exporting files
In order to take advantage of our feature based project structure, we use "barrel files" throughout features to export code. This allows us to treat a feature more like a package:
import { SocialProfilesAvatar } from "@feature/social-profiles"
Barrel files are just index.ts
files within a folder that directly export from other files within that folder, e.g.:
// inside /app/features/group-mode/components/index.ts
export * from "./shared/BlurView";
export * from "./shared/GroupModeSubtleButton";
export * from "./user/UserAvatar";
export * from "./user/UserIcon";
export * from "./GroupConfigPage";
export * from "./GroupEnterInviteCodePage";
Then each feature has an entry point, which is another index.ts
folder that directly exports from each of these barrel files. By doing this it's easier to enforce encapsulation to a feature, and makes code discovery and traversing the project much easier.
It's important to correctly import files in order to prevent circular dependencies.
If you are working inside a feature folder, and want to import something else from that feature, you should use relative imports. Everything else can use absolute imports via our module resolvers. In the below example we are working on code within the group-mode
feature folder.
// in the file app/features/group-mode/components/GroupModeMembersPage.tsx
// this is in a non-feature folder
import { Box } from "@app/components/core/Box";
// these imports are from outside the `features/group-mode` directory, in different features
import { useBetslipDrawerOpen } from "@feature/betslip";
import { RaceEntrant } from "@feature/racing";
// these imports are from inside the `features/group-mode` directory
import { GroupIcon } from "./GroupIcon";
import { useMessageStyles } from "../hooks/useBetMessageStyles.ts";
Components, functions and modules that won't be consumed outside the feature often don't require barrel files. Because you will be importing feature code via relative imports when inside that feature adding them to barrel files won't have any benefit. This also applies to feature entry files, which should only expose to the broader codebase things that will be consumed by the broader codebase.
If you are unsure, reach out to the #chapter-native-apps channel or your team lead.
Styling UI components
When creating or modifying any React Native Components in the project, be mindful to reduce or eliminate the use of color literals (e.g. colors="#FFF"
), as we have an app Theme object that stores all of the colors and in a lot of cases other UI properties (e.g. heights, widths, margins) for various components, so that they can be easily reused, modified from a centralised location and not nested too deeply in the code.
Even single instances of a color should be added to the theme object, lest they change in the future and then you have to trawl through the code base to find them.
If you are unsure about a color that is coming from a design document for a component you're creating or modifying, contact the relevant designer and confirm the color or other properties that are in or should be added to the theme object.
Currently the colours in the theme object are not optimally named (e.g. greyDark, greyDarker, greyDarkest). The design team is working on a proper color system (e.g. grey100, grey200, grey300) that will be less offensive to one's sensibilities. I hope you're ready for the fun of finding all of the "one-off" instances of color variables and having to put them back into the theme object, like you should have, in the first place.
How to use the theme object
HOC method: Wrap your components with
withTheme
to get access to thetheme
object as a prop within your componentHooks method: Get the theme object using the
useTheme
hook like this:const theme = useTheme();
PLEASE NOTE that you are not importing the useTheme
hook from react-native
, but from the app/hooks/misc.ts
file instead.
Theme guidelines
When modifying the theme object i.e. adding/removing/updating be sure to update the relevant interfaces first:
styled.d.ts
,theme.d.ts
and ensure that after updating the actual theme files that they do not have any type errors.When adding new properties into
theme/components.ts
esp. brand specific ones, prefer to use optional keys where possible and have appropriate fallbacks within the component. This will ensure that our theme files don't get too bloated.When adding domain specific properties to
theme/components.ts
(e.g. racing/sports related and not core) ensure they are put into the relevant sections instead of appending them onto the root level of thetheme/components.ts
object.All brand specific required values for
theme/component.ts
must also have appropriate fallbacks defined inapp/theme/components.ts
which is deep merged withbrand/theme/components.ts
Reusing text and copy within code
I18N - Abstraction layer for various text variables in the app
We use i18n
in the app to leverage various existing sets of common text copy that are already in use on the website, so they can be reused in the app easily - and therefore also automatically changed by just changing the i18n files instead of hard-coding the text.
To use it, initialize the useI18N
hook in your functional component:
const t = useI18n();
And use like this:
t("SGM_NOTIFICATION");
t("BLUESHYFT_CASH_IN.FAQS.WHATS_IT");
// Live {categoryName} -> "Live sports"
t("LIVE_CATEGORY", { values: { categoryName: "sports" } });
// no fallback -> "no_existant_key"
t("no_existant_key");
// fallback -> "whoops"
t("no_existant_key", { fallback: "whoops" });
Platform specific code
Some code will need different UX, markup, style or functionality depending on which device it is being run.
For such cases, we utilise the Platform API in React Native, whereby you can programmatically return a different value based on the platform the code is running on, acting as a switch.
Here is an example:
const textColor = Platform.select({
ios: 'blue',
android: 'red',
default: 'black'
})
<Text color={ textColor }>
This text color will change depending on what platform it runs on
</Text>
See the guide on platform specific code from the React Native team at Facebook.
Brand specific code
Sometimes you may wish to write some logic, or show different UI elements depending on which Brand the code is targetting. To make this easier, we have a Brand
utility module, which exposes a Brand.select()
function, that allows you to pass an object of brands (e.g. neds
, ladbrokes
), and a fallback default
that will execute depending on which brand app the code is being run on.
Here is an example:
const buttonColor = Brand.select({
neds: theme.colors.white,
ladbrokes: theme.colors.grey100,
default: theme.colors.grey100,
});
Note: It is advisable to always give a default
key and value as a fallback.
This is an internal utility we created, based on the Platform.select()
utility available by default in React Native.
Screen width specific code
We have our own utilities to deal with different screen widths on different mobile devices.
It allows for executing different code logic, such as which style props to pass set, depending on the width of the screen that the code is running on, without needing to cater for specific device models through the DeviceInfo
module.
See the ScreenSize
function and other utilities and comments inside the app/utils/layout.ts
file for an explanation and examples of how to use it.