CheatSheet for Several Popular React/React Native Libraries
Posted onWord count in article: 52kReading time ≈47 mins.
This article presented several popular React/React Native libraries.
It can be used as a cheatsheet for those libraries.
Moment.js
Parse, validate, manipulate, and display dates and times in
JavaScript. The Moment project is in maintenance mode. Thus no new
features will be added.
Installation
1 2 3 4 5
npm install moment --save # npm yarn add moment # Yarn Install-Package Moment.js # NuGet spm install moment --save # spm meteor add momentjs:moment # meteor
Format dates
1 2 3 4 5
moment().format('MMMM Do YYYY, h:mm:ss a'); // August 5th 2022, 8:54:30 pm moment().format('dddd'); // Friday moment().format("MMM Do YY"); // Aug 5th 22 moment().format('YYYY [escaped] YYYY'); // 2022 escaped 2022 moment().format(); // 2022-08-05T20:54:30+02:00
Relative time
1 2 3 4 5
moment("20111031", "YYYYMMDD").fromNow(); // 11 years ago moment("20120620", "YYYYMMDD").fromNow(); // 10 years ago moment().startOf('day').fromNow(); // 21 hours ago moment().endOf('day').fromNow(); // in 3 hours moment().startOf('hour').fromNow(); // an hour ago
Calendar time
1 2 3 4 5 6 7 8
moment().subtract(10, 'days').calendar(); // 07/26/2022 moment().subtract(6, 'days').calendar(); // Last Saturday at 8:55 PM moment().subtract(3, 'days').calendar(); // Last Tuesday at 8:55 PM moment().subtract(1, 'days').calendar(); // Yesterday at 8:55 PM moment().calendar(); // Today at 8:55 PM moment().add(1, 'days').calendar(); // Tomorrow at 8:55 PM moment().add(3, 'days').calendar(); // Monday at 8:55 PM moment().add(10, 'days').calendar(); // 08/15/2022
Multiple locale support
1 2 3 4 5 6 7 8 9 10 11
moment.locale(); // en moment().format('LT'); // 8:55 PM moment().format('LTS'); // 8:55:36 PM moment().format('L'); // 08/05/2022 moment().format('l'); // 8/5/2022 moment().format('LL'); // August 5, 2022 moment().format('ll'); // Aug 5, 2022 moment().format('LLL'); // August 5, 2022 8:55 PM moment().format('lll'); // Aug 5, 2022 8:55 PM moment().format('LLLL'); // Friday, August 5, 2022 8:55 PM moment().format('llll'); // Fri, Aug 5, 2022 8:55 PM
Notes and gotchas
1 2 3 4 5 6 7 8 9 10 11
moment("2010 13", "YYYY MM").isValid(); // false (not a real month) moment("2010 11 31", "YYYY MM DD").isValid(); // false (not a real day) moment("2010 2 29", "YYYY MM DD").isValid(); // false (not a leap year) moment("2010 notamonth 29", "YYYY MMM DD").isValid(); // false (not a real month name) moment('2012 juillet', 'YYYY MMM', 'fr'); moment('2012 July', 'YYYY MMM', 'en'); moment('2012 July', 'YYYY MMM', ['qj', 'en']); moment('It is 2012-05-25', 'YYYY-MM-DD').isValid(); // true moment('It is 2012-05-25', 'YYYY-MM-DD', true).isValid(); // false moment('2012-05-25', 'YYYY-MM-DD', true).isValid(); // true moment('2012.05.25', 'YYYY-MM-DD', true).isValid(); // false
Array
1 2 3 4
moment([2010, 1, 14, 15, 25, 50, 125]); // February 14th, 3:25:50.125 PM moment([2010]); // January 1st moment([2010, 6]); // July 1st moment([2010, 6, 10]); // July 10th
Add and subtract
1 2 3 4 5 6
moment().add(7, 'days').add(1, 'months'); // with chaining moment().add({days:7,months:1}); // with object literal moment().subtract('seconds', 1); // Deprecated in 2.8.0 moment().subtract(1, 'seconds'); moment().subtract(1.5, 'months') == moment().subtract(2, 'months') moment().subtract(.7, 'years') == moment().subtract(8, 'months') //.7*12 = 8.4, rounded to 8
if (enabled) { console.log('Authorization status:', authStatus); } }
Receiving messages
Common use-cases for handling messages could be: * Displaying a
notification * Syncing message data silently on the device * Updating
the application's UI
State
Description
Foreground
When the application is open and in view.
Background
When the application is open, however in the background
(minimised).
Quit
When the device is locked or application is not active or
running
Message handlers
In cases where the message is data-only and the device is in the
background or quit, both Android & iOS treat the message as low
priority and will ignore it. You can increase the priority by setting
the priority to high and
content-available to true (iOS)
On iOS in cases where the message is data-only and the device is in
the background or quit, the message will be delayed until the background
message handler is registered via
setBackgroundMessageHandler, signaling the application's
javascript is loaded and ready to run.
// Register background handler messaging().setBackgroundMessageHandler(async remoteMessage => { console.log('Message handled in the background!', remoteMessage); });
AppRegistry.registerComponent('app', () =>App);
The handler must return a promise once your logic has completed to
free up device resources. It must not attempt to update any UI (e.g. via
state) - you can however perform network requests, update local storage
etc.
The remoteMessage property still contains all of the
information about the meesage sent to the device. If the
RemoteMessage payload contains a notification
property when sent to the setBackgroundMessageHandler
handler, the device will have displayed a notification to the user.
Data-only message
When an incoming message is "data-only", both Android & iOS
regard it as low priority and will prevent the application from waking.
To allow data-only messages to trigger the background handler, you must
set the "priority" to "high" on Android, and enable the
content-available flag on iOS. For example, if using the
Node.js firebase-admin package to send a message:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
admin.messaging().sendToDevice( [], // device fcm tokens... { data: { owner: JSON.stringify(owner), user: JSON.stringify(user), picture: JSON.stringify(picture), }, }, { // Required for background/quit data-only messages on iOS contentAvailable: true, // Required for background/quit data-only messages on Android priority: 'high', }, );
Authentication
Installation
1
yarn add @react-native-firebase/auth
What does it do
Firebase authentication provides backend services & easy-to-use
SDKs to authenticate users to your app.
Ensure that the "Email/Password" sign-in provider is enabled on the
Firebase console.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
import auth from'@react-native-firebase/auth';
auth() .createUserWithEmailAndPassword('jane.doe@example.com', 'SuperSecretPassword!') .then(() => { console.log('User account created & signed in!'); }) .catch(error => { if (error.code === 'auth/email-already-in-use') { console.log('That email address is already in use!'); }
if (error.code === 'auth/invalid-email') { console.log('That email address is invalid!'); }
console.error(error); });
Cloud Firestore
Installation
1
yarn add @react-native-firebase/firestore
What does it do
Firestore is a flexible, scalable NoSQL cloud database to store and
sync data.
Usage
Collections & Documents
Cloud Firestore stores data within "documents", which are contained
within "collections", and documents can also contain collections. For
example, we could store a list of our users documents within a "Users"
collection. The collection method allows us to reference a collection
within our code:
// Stop listening for updates when no longer required return() =>subscriber(); }, [userId]); }
Snapshot
Once a query has returned a result, Firestore returns either a
QuerySnapshot (for collections queries) or a
DocumentSnapshot (for document queries). These snapshots
provide the ability to view the data, view query metadata, whether the
document exists or not.
QuerySnapshot
A QuerySnapshot returned from a collection query allows
you to inspect the collection, such as how many documents exist within
it, access the documents within the collection, any changes since the
last query, and more:
A DocumentSnapshot is returned from a query to a
specific document, or as part of the documents returned via a
QuerySnapshot. The snapshot provides the ability to view a
document data, metadata, and whether a document actually exists.
importBackendfrom'i18next-http-backend'; importLanguageDetectorfrom'i18next-browser-languagedetector'; // don't want to use this? // have a look at the Quick start guide // for passing in lng and translations on init
i18n // load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) // learn more: https://github.com/i18next/i18next-http-backend // want your translations to be loaded from a professional CDN? => https://github.com/locize/react-tutorial#step-2---use-the-locize-cdn .use(Backend) // detect user language // learn more: https://github.com/i18next/i18next-browser-languageDetector .use(LanguageDetector) // pass the i18n instance to react-i18next. .use(initReactI18next) // init i18next // for all options read: https://www.i18next.com/overview/configuration-options .init({ fallbackLng: 'en', debug: true,
interpolation: { escapeValue: false, // not needed for react as it escapes by default } });
// i18n translations might still be loaded by the http backend // use react's Suspense exportdefaultfunctionApp() { return ( <Suspensefallback="loading"> <MyComponent /> </Suspense> ); }
Translation files
Creata a new file
public/locales/<language_code>/translation.json with
the following sample content:
1 2 3 4 5 6 7
{ "title":"Welcome to react using react-i18next", "description":{ "part1":"To get started, edit <1>src/App.js</1> and save to reload.", "part2":"Switch language between english and german using buttons above." } }
Using the trans component
1 2 3 4 5 6 7 8 9
importReactfrom'react'; import { Trans } from'react-i18next';
exportdefaultfunctionMyComponent () { return<Trans>Welcome to <strong>React</strong></Trans> }
// the translation in this case should be "Welcome to <1>React</1>": "Welcome to <1>React and react-i18next</1>"
constSignupForm = () => { // Note that we have to initialize ALL of fields with values. These // could come from props, but since we don’t want to prefill this form, // we just use an empty string. If we don’t do this, React will yell // at us. const formik = useFormik({ initialValues: { firstName: '', lastName: '', email: '', }, onSubmit: values => { alert(JSON.stringify(values, null, 2)); }, }); return ( <formonSubmit={formik.handleSubmit}> <labelhtmlFor="firstName">First Name</label> <input id="firstName" name="firstName" type="text" onChange={formik.handleChange} value={formik.values.firstName} /> <labelhtmlFor="lastName">Last Name</label> <input id="lastName" name="lastName" type="text" onChange={formik.handleChange} value={formik.values.lastName} /> <labelhtmlFor="email">Email Address</label> <input id="email" name="email" type="email" onChange={formik.handleChange} value={formik.values.email} /> <buttontype="submit">Submit</button> </form> ); };
Notice some patterns and symmetry forming: 1. We reuse the same exact
change handler function handleChange for each HTML input.
2. We pass an id and name HTML attribute that
matches the property name we defined in initialValues. 3.
We access the field's value using the same name (email
-> formik.values.email)
// A custom validation function. This must return an object // which keys are symmetrical to our values/initialValues constvalidate = values => { const errors = {}; if (!values.firstName) { errors.firstName = 'Required'; } elseif (values.firstName.length > 15) { errors.firstName = 'Must be 15 characters or less'; }
if (!values.lastName) { errors.lastName = 'Required'; } elseif (values.lastName.length > 20) { errors.lastName = 'Must be 20 characters or less'; }
constSignupForm = () => { // Pass the useFormik() hook initial form values, a validate function that will be called when // form values change or fields are blurred, and a submit function that will // be called when the form is submitted const formik = useFormik({ initialValues: { firstName: '', lastName: '', email: '', }, validate, onSubmit: values => { alert(JSON.stringify(values, null, 2)); }, }); return ( <formonSubmit={formik.handleSubmit}> <labelhtmlFor="firstName">First Name</label> <input id="firstName" name="firstName" type="text" onChange={formik.handleChange} value={formik.values.firstName} /> {formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null} <labelhtmlFor="lastName">Last Name</label> <input id="lastName" name="lastName" type="text" onChange={formik.handleChange} value={formik.values.lastName} /> {formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null} <labelhtmlFor="email">Email Address</label> <input id="email" name="email" type="email" onChange={formik.handleChange} value={formik.values.email} /> {formik.errors.email ? <div>{formik.errors.email}</div> : null} <buttontype="submit">Submit</button> </form> ); };
Visited fields
Our validation function runs on each keystroke against the entire
form's values, our errors object contains all validation errors at any
given moment. This is not what we want.
Like errors and values, Formik keeps track
of which fields have been visited. It stores this information in an
object called touched that also mirrors the shape of
values/initialValues. The keys of
touched are the field names, and the values of
touched are booleans.
To take advantage of touched, we pass
formik.handleBlur to each input's onBlur prop.
This function works similarly to formik.handleChange in
that it uses the name attribute to figure out which fields
to update.
The code above is very explicit about exactly what Formik is doing.
onChange -> handleChange,
onBlur -> handleBlur, and so on. However,
to save your time, useFormik() returns a helper method
called formik.getFieldProps() to make it faster to wire up
inputs. Given some field-level info, it returns to you the exact group
of onChange, onBlur, value,
checked for given field. You can then spread that on an
input, select, or textarea.
To save you even more time, Formik comes with
React context-powered API/components to make life easier
and code less verbose: <Formik />,
<Form />, <Field />,
<ErrorMessage />.
Since these components use React Context, we need to render a React
Context Provider that holds our form state and helpers in our tree. If
you did this yourself, it would look like:
Here
we use Yup for validation. The <Field>
component by default will render an <input> component
that, given a name prop, will implicitly grab the
respective onChange, onBlur, and
value props and pass them to the element as well as any
props you pass to it. For example:
We can do better with an abstraction. With Formik, you can and should
build reusable input primitive components that you can shared around
your application. useField does the same thing like
<Field> render-prop component but via React Hooks:
constMyTextInput = ({ label, ...props }) => { // useField() returns [formik.getFieldProps(), formik.getFieldMeta()] // which we can spread on <input>. We can use field meta to show an error // message if the field is invalid and it has been touched (i.e. visited) const [field, meta] = useField(props); return ( <> <labelhtmlFor={props.id || props.name}>{label}</label> <inputclassName="text-input" {...field} {...props} /> {meta.touched && meta.error ? ( <divclassName="error">{meta.error}</div> ) : null} </> ); };
constMyCheckbox = ({ children, ...props }) => { // React treats radios and checkbox inputs differently other input types, select, and textarea. // Formik does this too! When you specify `type` to useField(), it will // return the correct bag of props for you -- a `checked` prop will be included // in `field` alongside `name`, `value`, `onChange`, and `onBlur` const [field, meta] = useField({ ...props, type: 'checkbox' }); return ( <div> <labelclassName="checkbox-input"> <inputtype="checkbox" {...field} {...props} /> {children} </label> {meta.touched && meta.error ? ( <divclassName="error">{meta.error}</div> ) : null} </div> ); };
Let's suppose that we acutally want to add another details screen:
1 2 3 4
<Button title="Go to Details... again" onPress={() => navigation.push('Details')} />
Going back
The header provided by the native stack navigator will automatically
include a back button when it is possible to go back from the active
screen. Sometimes you'll want to be able to programmatically trigger
this behavior, and for that you can use
navigation.goBack():
We can pass data to routes when we nagivate to them. There are two
pieces to this: 1. Pass params to a route by putting them in an object
as a second parameter to the navigation.navigate function.
2. Read the params in your screen component:
route.params.
Redux is a predictable state container for JavaScript apps. Redux
manages application's state with a single global object called Store.
More importantly, it gives you live code editing combined with a
time-travelling debugger. It is flexible to go with any view layer such
as React, Angular, Vue, etc.
Principle of Redux
Single source of truth: The state of your whole application is
stored in an object tree within a single store.
State is read-only: The only way to change the state is to emit an
action, an object describing what happened. Nobody can directly change
the state of your application.
Changes are made with pure functions: To specify how the state tree
is transformed by actions, you write pure reducers. A reducer is a
central place where state modification takes place. Reducer is a fnction
which takes state and action as arguments, and returns a newly updated
state.
Core concepts
Let us assume our application's state is described by a plain object
called initialState which is as follows:
Every piece of code in your application cannot change this state. To
change the state, you need to dispatch an action.
What is an action?
An action is a plain object that describes the intention to cause
change with a type property. It must have a type property which tells
what type of action is being performed.
1 2 3 4
return { type: 'ITEMS_REQUEST', // action type isLoading: true// payload information }
Actions and states are held together by a function called Reducer. An
action is dispatched with an intention to caues change. This change is
performed by the reducer. A reducer function that handles the
ITEMS_REQUEST action is as follows:
Dispatch Action <-------------------- View | ^ | | | | Subscribe | | v | Reducers -----------------> Store
Data follows
Redux follows the unidirectional data flow. It means that your
application data will follow in one-way binding data flow. * An action
is dispatched when a user interacts with the application. * The root
reducer function is called with the current state and the duspatched
action. The root reducer may divide the task among smaller reducer
functions, which ultimately returns a new state. * The store notifies
the view by executing their callback functions. * The view can retrieve
updated state and re-render again.
Store
A store is an immutable object tree in Redux. A store is a state
container which holds the application's state. Redux can have only a
single store in your application. Whenever a store is created in Redux,
you need to specify the reducer.
If helps you retrieve the current state of your Redux store:
1
store.getState()
dispatch
It allows you to dispatch an action to change a state in your
application.
1
store.dispatch({type: 'ITEMS_REQUEST'})
subscribe
It helps you register a callback that Redux store will call when an
action has been dispatched. As soon as the Redux state has been updated,
the view will re-render automatically.
Apart from the type attribute, the structure of an action object is
totally up to the developer. It is recommended to keep your action
object as light as possible and pass only the necessary information. To
cause any change in the store, you need to dispatch an action first by
using store.dispatch() function. The action object is as
follows:
Action creators are the functions that encapsulte the process of
creation of an action object. These functions simply return a plain JS
object which is an action. It promotes writing clean code and helps to
achieve reusability.
Reducers
The following few things should never be performed inside the
reducer: * Mutation of functions arguments. * API calls & routing
logic. * Calling non-pure function e.g. Math.random()
We can write our logic in reducer and can split it on the logical
data basis. Suppose, we want to design a web page where a user can
access product order status and see wishlist information. We can
separate the logic in different reducers files, and make them work
independently. Let us assume that GET_ORDER_STATUS action
is dispatched to get the status of order corresponding to some order id
and user id.
Now, we can combine both reducers by using Redux combineReducers
utility. The combineReducers generate a function which returns an object
whose values are different reducer functions. You can import all the
reducers in index reducer file and combine them together as an object
with their respective names.
Now you can pass this rootReducer to the
createStore method as follows:
1
const store = createStore(rootReducer);
Middleware
Redux itself is synchronous, so how the async operations like network
request work with Redux? Here middlewares come handy. Reducers are the
place where all the execution logic is written. Reducer has nothing to
do with who performs it, how much time it is taking or logging the state
of the app before and after the action is dispatched. In this case,
Redux middleware function provides a medium to interact with dispatched
action before they reach the reducer. Customized middleware functions
can be created by writing high order functions (a function that returns
another function), which wraps around some logic. Multiple middlewares
can be combined together to add new functionality, and each middleware
requires no knowledge of what came before and after. You can imagine
middlewares somewhere between action dispatched and reducer.
Commonly, middlewares are used to deal with asynchrnous actions in
your app. Redux provides with API called applyMiddleware
which allows us to use custom middleware as well as Redux middlewares
like Redux-thunk and Redux-promise. It applies middlewares to store:
Conditional dispatch can be written inside middleware. Each
middleware receives store's dispatch so that they can dispatch new
action, and getState functions as arguments so that they
can access the current state and return a function. Any return value
from an inner function will be available as the value of dispatch
function itself. The following is the syntax of a middleware:
1
({ getState, dispatch }) => next => action
The getState function is useful to decide whether new
data is to be fetched or cache result should get returned, depending
upon the current state.
For example, a custom middleware logger function. It simply logs the
action and new state:
const store = createStore( userLogin, initialState = [], applyMiddleware(loaderHandler) );
This middleware shows the loader when you are requesting any resource
and hides it when resource request has been completed.
Redux Devtools
Redux-Devtools provide us debugging platform for Redux apps. * It
lets you inspect every state and action payload. * It lets you go back
in time by "cancelling" actions. * If you change the reducer code, each
"staged" action will be re-evaluated. * If the reducers throw, we can
identify the error and also during which action this happended. * WIth
persistState() store enhancer, you can persist debug
sessions across page reloads.
Testing
We can use JEST as a testing engine. It works in the node environment
and does not access DOM.
Test cases for action
creators
Let us assume you have action creator as shown below:
import * as action from'../actions/actions'; import * as types from'../../constants/ActionTypes';
describe('actions', () => { it('should create an action to check if item is loading', () => { const isLoading = true, const expectedAction = { type: types.ITEMS_REQUEST_SUCCESS, isLoading } expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction) }) })
Test cases for reducers
We have learnt that reducer should return a new state when action is
applied. So reducer is test ed on this behaviour. Consider a reducer as
given below:
Whenever a change occurs in a react-redux app,
mapStateToProps() is called. In this function, we exactly
specify which state we need to provide to our react component. With the
help of connect() function, we are connecting these app's
state to react component. connect() is a high order
function which takes component as a parameter. It performs certain
operations and returns a new component with correct data which we
finally exported.