Skip to content

Instantly share code, notes, and snippets.

@iegik
Last active December 21, 2023 12:13
Show Gist options
  • Save iegik/f8065668de61c0589ebca52a3ac3e41c to your computer and use it in GitHub Desktop.
Save iegik/f8065668de61c0589ebca52a3ac3e41c to your computer and use it in GitHub Desktop.
React Native tips & tricks

State management

All of the state management libraries you mentioned have their unique features and advantages, and the choice of which one to use ultimately depends on the specific needs and requirements of your project. However, here's a brief overview of each library:

  1. MobX: MobX is a simple, lightweight, and easy-to-learn library for managing state in React applications. It has a reactive programming model and provides automatic state updates, which means you don't have to write complex code for updating your app's state. It also has good performance and works well with TypeScript.

  2. React Context: React Context is a built-in feature of React that allows you to share data between components without having to pass props down the component tree. It's a good choice for simple use cases where you need to share state between components without any complex logic.

  3. Redux: Redux is a widely-used library that provides a single source of truth for managing state in large-scale React applications. It has a well-defined architecture and supports time-travel debugging, which is a useful feature for debugging complex applications. However, it can be quite verbose and difficult to set up and learn.

  4. Redux Saga: Redux Saga is a middleware library for Redux that enables you to write asynchronous code in a more readable and testable way. It has good support for complex use cases like handling long-running processes and sequential workflows.

  5. Effector: Effector is a relatively new library that provides a different kind of state management model called the "Effector model." It's designed to be more performant and scalable than other libraries and supports complex use cases like concurrency and time-travel debugging.

As you can see, each library has its strengths and weaknesses, and the best one to use depends on the specific needs of your project. However, if you're just starting out with state management in React, I would recommend starting with either MobX or React Context, as they are both easy to learn and provide good performance for most use cases.

RxJS vs MobX

RxJS and MobX are both state management libraries but they have different approaches and use cases.

RxJS is a reactive programming library that allows you to manage asynchronous data streams. It is useful for implementing complex event-driven applications such as real-time dashboards or dynamic user interfaces. RxJS is based on the Observer pattern where the publisher of data (observable) notifies its subscribers (observers) whenever data changes. RxJS provides operators and functions to manipulate, transform and combine data streams.

MobX is a state management library that provides an observable-based model to manage the state of your application. It uses the concept of observables to automatically re-render components whenever the state changes. MobX is useful for small to medium sized applications where you need to manage the state of your data in a simpler and more declarative way. It provides a clean and easy-to-understand API that allows you to create reactive data models with minimal boilerplate.

In summary, RxJS is more useful when you are building complex event-driven applications that require fine-grained control over data streams, whereas MobX is better suited for managing the state of your application in a more declarative and intuitive way.

RxJS vs React Context

RxJS and React Context are both libraries used in JavaScript development, but they serve different purposes.

RxJS is a library for reactive programming that allows developers to handle asynchronous data streams with observables, making it easier to manage and transform data over time. RxJS provides a suite of functions and operators that can be used to create pipelines for processing data, making it a powerful tool for handling complex data flows.

React Context, on the other hand, is a library that provides a way to share state across nested components in a React app, without passing props down from parent to child. It allows for global state management, so developers can store data that needs to be accessed by multiple components or across a complex component hierarchy.

While there may be some overlap between RxJS and React Context, they serve different purposes and are not directly comparable. RxJS is mainly used for asynchronous data handling and React Context helps manage state and allows for global state management.

In some cases, RxJS could be used in conjunction with React Context to handle complex data streams, but this is not a direct substitute for the functionality provided by React Context. Ultimately, the choice between using RxJS or React Context will depend on the specific requirements of the project and the preferences of the developer.

PropTypes

npm install --save prop-types
yarn add prop-types
  • PropTypes.any - [warn] not null
  • PropTypes.array - array
  • PropTypes.arrayOf() - exact type of array
  • PropTypes.bool - boolean
  • PropTypes.func - function
  • PropTypes.number - number

NOTE: Neither shape nor objectOf by default cause the prop to become required

  • PropTypes.object - object

  • PropTypes.objectOf() - object with property values of a certain type

  • PropTypes.shape({}) - exact type of object

  • PropTypes.exact({}) - exact type of object with warnings on extra properties

  • PropTypes.string - string

  • PropTypes.symbol - symbol

  • PropTypes.oneOf([]) - enum

  • PropTypes.node - React Node (numbers, strings, elements or an array/fragment)

  • PropTypes.element - React element <MyComponent />

  • PropTypes.elementType - React element type (ie. MyComponent).

  • PropTypes.instanceOf() - JS's instanceof

  • PropTypes.oneOfType([null]).isRequired - [warn] nullable or do not use

  • .isRequired - is required

React Native

Android

Different App Icons for Debug and Release builds

mkdir -p android/app/src/debug/
for i in android/app/src/main/res/mipmap-*/*.png ; do convert $i -colorspace Gray `echo "$i" | sed 's/main/debug/'`; done
cp ios/app/Images.xcassets/AppIcon.main.appiconset/ ios/app/Images.xcassets/AppIcon.debug.appiconset/
for i in ios/app/Images.xcassets/AppIcon.main.appiconset/*.png ; do convert $i -colorspace Gray `echo "$i" | sed 's/main/debug/'`; done

https://engineering.circle.com/different-app-icons-for-your-ios-beta-dev-and-release-builds-af4d209cdbfd

Android 25

sed -i -- "s/buildToolsVersion .*/buildToolsVersion \"25.0.1\"/" node_modules/**/build.gradle
sed -i -- "s/compileSdkVersion .*/compileSdkVersion 25/" node_modules/**/build.gradle
sed -i -- "s/targetSdkVersion .*/targetSdkVersion 25/" node_modules/**/build.gradle

iOS

Different App Icons for Debug and Release builds

  • On the top bar, click the + sign and “Add User-Defined Setting”
  • Name it BUNDLE_ID_SUFFIX (or something similar — you’ll need to re-use this name later)
  • Open the dropdown and give the following values for each configuration, leaving the “Release” value blank
  • Now open up Info.plist and locate the “Bundle Identifier” setting. Edit it and append ${BUNDLE_ID_SUFFIX} to your existing bundle id. For example, if your bundle id is com.crab.awesome, you’d change it to com.crab.awesome${BUNDLE_ID_SUFFIX}
  • Go back to your project settings, select your Target, and choose “Build Settings” Under “Asset Catalog Compiler — Options”, find the “Asset Catalog App Icon Set Name” setting.
  • Don’t expand this one, just set the value to be AppIcon${BUNDLE_ID_SUFFIX}.
  • Open your Images.xcassets file. Click the + on the bottom left, and select “New App Icon”. Name it AppIcon.dev. Do it again for AppIcon.beta (notice a theme here?)

Clean project

rm -rf ios/build
  • [CTRL] + [SHIFT] + [CMD] + [K] - clean project
  • Delete all refirences to modules

Upgrading React Native

npm i -g react-native-git-upgrade
react-native-git-upgrade
rnpm link
  • Drag react-native/React/React.xcodeproj to the Libraries, manually link Products/libReact.a

  • In Xcode, go to the project scheme (Product -> Scheme -> Manage Scheme -> double click your project).

  • Click on the 'Build' option at the left pane.

  • Uncheck 'Parallelize Build' under Build Options.

  • Then in Targets section, click '+' button then search for 'React'. Select it and click 'Add'. 'React' should now appear under Targets section. Click and drag it to the top so that it will be the first item in the list (before your project).

  • Clean the project and build.

Scripts

  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "cd android && ./gradlew test && cd .. && jest",
    "android": "node node_modules/react-native/local-cli/cli.js run-android",
    "bundle-android": "node node_modules/react-native/local-cli/cli.js bundle --platform android --dev false --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle --sourcemap-output android/app/src/main/assets/index.android.map --assets-dest android/app/src/main/res/",
    "ios": "node node_modules/react-native/local-cli/cli.js run-ios --simulator=\"iPhone 4s\"",
    "ios-release": "node node_modules/react-native/local-cli/cli.js run-ios --simulator=\"iPhone 4s\" --configuration Release",
    "apk": "cd android && ./gradlew assembleRelease && cd ..",
    "android-release": "cd android && ./gradlew installRelease && cd ..",
    "clean-npm": "rm -rf $TMPDIR/react-* && watchman watch-del-all && rm -rf node_modules/ && npm cache clean && npm i",
    "clean-android": "cd android && ./gradlew clean && cd ..",
    "clean-ios": "rm -rf ios/build/ModuleCache/* ios/build/Build/*",
    "clean": "npm run clean-npm && npm run clean-android && npm run clean-ios",
  },

Hacks

ListView

Setting initialListSize property value, improves performance of rendering parent component.

<ListView
  initialListSize={1} // Forcing rendering 1 item (performance)
/>
{children ? children : null}

Debugging in react-native

Debugging requests to the native components in React Native

import MessageQueue
  from 'reac-native/Libraries/BatchedBridge/MessageQueue'

MessageQueue.spy(info => console.info(info))

TODO

npm uninstall babel-preset-react-native
npm install babel-preset-react-native@2.1.0`

react-native version: react-native-cli: 2.0.1 react-native: 0.48.3

Known issues

Code Contracts

import * as React from 'react';
import PropTypes from 'prop-types';

const Hello = ({ name }) => `Hello, ${name}`

Hello.propTypes = {
    name: PropTypes.string.isRequired
}
Hello.defaultProps = {}

export default Hello;

PropTypes for children:

Foo.propTypes = {
    children: PropTypes.node,
}

HOC

const withServerTime = (HOC) => {
    ...
    WithServerTime.contextTypes = {
        serverTime: PropTypes.number.isRequired,
    };
    ...
}
  • PropTypes.string, PropTypes.number - all, that converts to text node ``
  • PropTypes.node - can be node or element
  • PropTypes.func - dumb components
  • not required - can be null

https://codesandbox.io/s/1846ppz167

Known issues

Popular bugs

Failed prop type: checker is not a function

propTypes: {
   obj: {
      obj: PropTypes.shape({})
   }
}

to

propTypes: {
   obj: PropTypes.shape({
      obj: PropTypes.shape({})
   })
}

Code Contracts

import * as React from 'react';

const Hello:React.FC<{ name: string }> = ({ name }) => `Hello, ${name}`

  • Enable allowSyntheticDefaultImports: true, in tsconfig.json
  • * as React - reference: facebook/react#18102

TypeScript types

  • React.FunctionComponent - functional component

  • children: React.ReactNode; best, accepts everything

  • functionChildren: (name: string) => React.ReactNode; - recommended function as a child render prop type

  • style?: React.CSSProperties - to pass through style props

  • onChange?: React.FormEventHandler<HTMLInputElement> form events! the generic parameter is the type of event.target

  • props: Props & React.PropsWithoutRef<JSX.IntrinsicElements["button"]> to impersonate all the props of a button element without its ref

  • import { ReactComponent as MyIcon } from 'assets/my-icon.svg'; - svg as component https://medium.com/better-programming/react-best-way-of-importing-svg-the-how-and-why-f7c968272dd9

  • See more

Errors

To ignore "...Provider' cannot be used as a JSX component" in React@18 you can ignore specific lines:

node_modules/@types/react/index.d.ts

return (
  // @ts-expect-error Provider' cannot be used as a JSX component
  <One.Provider value={...}>
    {/* @ts-expect-error Provider' cannot be used as a JSX component */}
    <Another.Provider value={...}>
      {children}
    </Another.Provider>
  </One.Provider>
)

forwardRef

// function name used as displayName
export default forwardRef<HTMLButtonElement, ComponentProps>(function Component (props, ref) {
...
}

React

Higher-Order Components (HOC)

export default (View) => class extends Component {
  state = {
    text: this.props.text || '',
  }
  render() {
    return <View {...{...this.props, ...this.state}}/>
  }
}
@hoc
(props = {}) => {
  return (
    <div>{props.text}</div>
  )
}

Render prop (HOC)

const FooHOC = (props) => {
    const { render } = props;
    const [state, setState] = useState();
    // ...
    return render(state)
}

Render Props > HOCs

const withMouse = (Component) => () => (
    <Mouse render={mouse => (
        <Component {...this.props} mouse={mouse}/>
    )}/>
)

React with context

import React from 'react';
import moment from 'moment';

const withServerTime = (HOC) => {
  const ServerTimeComponentName = HOC.displayName || HOC.name || 'HOC';

  const WithServerTime = (props, { serverTime }) => (
    <HOC {...props} serverTime={serverTime} />
  );

  WithServerTime.displayName = `withServerTime(${ServerTimeComponentName})`;

  return WithServerTime;
};

const toServerDate = ({ date, serverTime }) => moment(date + serverTime);

export default withServerTime(toServerDate);

Redux

import {selectValueForProp} from './selectors'
import {action} from './action'
export const mapStateToProps = state => ({
  prop: selectValueForProp(state),
});
export const mapDispatchToProps = dispatch => ({
  onChange: (value) => {
      value && dispatch(action(value)) // Command pattern? `value && dispatch(command(value).execute())`
  },
  dispatch,
});
export const mergeProps = (stateProps, dispatchProps) => ({
  toggle: () => stateProps.on ? dispatchProps.onEnable() : dispatchProps.onDisable(),
});
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Component)

Redux Saga

  • mapStateToProps = state => ({}) -
  • mapDispatchToProps = dispatch => ({}) -

connect(mapStateToProps, mapDispatchToProps)(Component)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment