Skip to content

Additional docs #348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ We no longer recommend using an express server with Rails. It's simply not neces
2. Open a browser tab to http://localhost:4000 for the Hot Module Replacement Example just using an express server (no Rails involved). This is good for fast prototyping of React components. However, this setup is not as useful now that we have hot reloading working for Rails!
3. Try Hot Reloading steps below!

## React Native

See React Native mobile app that works for both iOS and Android under the `mobile` folder.

## Contributors
[The Shaka Code team!](http://www.shakacode.com/about/), led by [Justin Gordon](https://github.com/justin808/), along with with many others. See [contributors.md](docs/contributors.md)

Expand Down
4 changes: 4 additions & 0 deletions mobile/ReactNativeTutorial/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ This projects uses Eslint with React and React Native rules. To run linters type
```
npm run flow
```

### Detailed docs

Can be found in `docs` folder. See [Introduction](docs/Introduction.md) to start.
2 changes: 1 addition & 1 deletion mobile/ReactNativeTutorial/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def enableProguardInReleaseBuilds = false

android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
buildToolsVersion "23.0.1"

defaultConfig {
applicationId "com.reactnativetutorial"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { actions as reduxActions } from 'ReactNativeTutorial/app/reducers';
import * as api from 'ReactNativeTutorial/app/api';

export const fetch = () =>
async function fetchCommentsEffect(dispatch, _getState, call) {
async function fetchCommentsThunk(dispatch, _getState, call) {
dispatch(reduxActions.setLoadingComments(true));
let response;
try {
Expand All @@ -25,7 +25,7 @@ export const fetch = () =>
export const updateForm = reduxActions.updateCommentForm;

export const createComment = () =>
async function createCommentEffect(dispatch, getState, call) {
async function createCommentThunk(dispatch, getState, call) {
const state = getState();
const commentsStore = commentsStoreSelector(state);
const tempId = reduxUtils.getNewId(commentsStore);
Expand Down
75 changes: 75 additions & 0 deletions mobile/ReactNativeTutorial/docs/Containers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## Containers and pure components

We split React components in two types: pure components and containers. To fully understand
the difference between these two types we need to understand the notion of a side effect.

### Side effects

According to wikipedia

> In computer science, a function or expression is said to have a side effect
> if it modifies some state or has an observable interaction with calling functions
> or the outside world.

In our app the examples of the side effects might be altering Redux state, fetching
comments and drawing actual views on the screen.

Now we have to refine this definition to make it actually work properly for the React
Native app.

First, will drawing actual views on the screen be a side effect of a typical
React Native component? It seems natural to answer yes, but a component
just defines a set of rules of how to construct a component in terms of simpler components.
The actual drawing occurs inside React Native framework. So this is not a side effect of
a typical React Native component.

Second, we don't consider user interactions triggering callbacks, defined
in component to be a side effect of a component. That is because we think of side effects
as something that happens when we do render of a component. User interactions are
themselves come from the outer world and simply forwarded to thunks.

So in the scope of this app we have two side effects:
1. Updating props as a result of Redux state change
2. Fetching comments on ComponentDidMount event

### Pure components

Pure components have only `Props` as inputs and have no side effects.
A super simple criterion for checking that component is pure is to make sure it is
implemented as a function, rather than class extending `React.Component`.
The whole purpose of keeping components pure is to make the testing of rendering
super easy.

### Containers

Containers on the other hand are entities that have side effects. Their single
purpose is to accumulate side effects and delegate a set of props to pure
components. So they don't contain any rendering logic and tested purely for side
effects.

To make testing easier we singled out each side effect into a higher order component.
A higher order component (or simply HOC) is a function that takes one components as an
argument and returns another component. For example this HOC

```
export const withInitFunction(initFunction: Function, Component: ReactClass<any>) =>
class ComponentWithInit extends React.Component {
componentDidMount() {
initFunction();
}

render() {
return <Component {...this.props} />;
}
};
```

adds initial action when a component is mounted.

Then a container is just a set of such HOCs, e.g.
```
import withAddProps from '../hocs/withAddProps';
import Add from '../components/Add/Add';

export default withInitFunction(withAddProps(Add));
```
8 changes: 5 additions & 3 deletions mobile/ReactNativeTutorial/docs/Introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ The app is waiting for further user interactions.
We deliberately picked libraries and approaches to make this tutorial a bootstrap
for scalable react native app. The details for each part of the app are given below:

- [Folders and structure](Structure.md)
- [Redux](Redux.md)
- [Selectors](Selectors.md)
- Containers
- UI components
- Thunks
- [Containers](Containers.md)
- [UI components](UI.md)
- [Thunks](Thunks.md)
- [Testing](Testing.md)
6 changes: 3 additions & 3 deletions mobile/ReactNativeTutorial/docs/Redux.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ For a details of a Redux framework please refer to

The basic idea of Redux is having a set of functions which takes state and actions as
arguments and return a new state based on the action params, i.e. functions
in the form `(state, action) => nextState`. Such functions are called reducers. E.g. comments
in the form `(state, action) => nextState`. These functions are called `reducers`. E.g. comments
reducer in our app looks like this:

```
Expand Down Expand Up @@ -71,8 +71,8 @@ to create new state, rather than modifying existing state. There are two approac
you from accidentally mutating your state, but also has advantages in caching computed
values (see [Selectors](Selectors.md) for details).

Next we define simple reducer for each action. These funtions use `Immutable.js` api for
merging and updating the state (effectively it creates a new state with these changes)
Next we define simple reducer for each action. These functions use `Immutable.js` api for
merging and updating the state (effectively they create a new state with these changes)

After that comments reducer combines individual reducers using `switch case` on action type.

Expand Down
4 changes: 2 additions & 2 deletions mobile/ReactNativeTutorial/docs/Selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ state => state.get('commentsStore');
We use `reselect` library that memoizes the previous input and doesn't recompute if
the input isn't changed. That is critical because on each action you receive a new Redux
state and have to save time on recalculations of the parts that didn't change. Additionally
this ensures, that you don't re-render the parts that didn't change. Here's the example\
this ensures, that you don't re-render the parts of the UI that didn't change. Here's the example
of the reselect usage:
```
export default createSelector(
Expand Down Expand Up @@ -44,5 +44,5 @@ the input isn't changed. That is critical because on each action you receive a n
and other libs use shallow compare of objects, that means you have to be careful with
respect to deep objects as their comparison in some circumstances can
give false positive and trigger selector recalculation as well as unnecessary re-render.
Additionally since `Immutable.js` api is already used inside redux, it would be easier
Additionally since `Immutable.js` api is already used inside reducers, it would be easier
to reuse it here, rather than introducing new libs like `lodash`.
33 changes: 33 additions & 0 deletions mobile/ReactNativeTutorial/docs/Structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Folders structure

When deciding on how to structure different components of the app the tradeoff is usually
localizing code to reduce complexity vs putting the code into global scope for easy reuse.
To localize code we use `bundles`, other code is located in the `app` folder. Each
bundle corresponds to a certain user flow. E.g. a good example of a bundle is a signup flow -
a set of interconnected similar screens.

### Components and containers

Components and containers naturally go into `bundles`. If we want to reuse
some components in other bundles we put them into a single global bundle called `common`.

### Thunks

Thunks are closely related to the UI, so it's natural to put them into `bundles`.

### Reducers

Since Redux state is a singleton global object it makes sense to put reducers under the `app`
folder.

### Selectors

Selectors are itermediaries between Redux and UI, so might go into `app` folder as well as
`bundles` folder. But since it's quite common for selectors to reuse each other, a better
choice might be putting them altogether under the `app` folder.

### Styles

The styles that are related to components are located right next to them. However,
global style parameters like colors and sizes sit in the `app` folder.

17 changes: 17 additions & 0 deletions mobile/ReactNativeTutorial/docs/Testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Testing

We use [Jest snapshot testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html)
approach that is officially suported by React Native. In our opinion this is superior approach
since it makes testing flexible and saves you from double implemeting components when
describing the desired outcome in tests.

React Native recommends using snapshot testing for components testing. We go a bit further
and also use snapshots for testing of:
- Reducers - by applying an action to a state and snapshotting the resulting state
- Selectors - by snaphotting the result of a selector applied to different states
- Containers - by testing side effects of individual higher order components
- Thunks - by applying them to a mock redux store and recording the dispatched
redux actions into a snapshot. We also use a custom middleware that mocks `call` functions
(see [Thunks](Thunks.md)) producing side effects and replaces them with fake `CALL`
redux actions that are also recorded in a snapshot.

91 changes: 91 additions & 0 deletions mobile/ReactNativeTutorial/docs/Thunks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## Thunks

Thunk is an action which can handle async function calls and dispatch Redux actions against
the Redux store. We think of Thunks as a central point for working with web Api requests and
Redux store and all business logic of the app. Thus redux reducers have really plain and
simple Redux actions and all the complex state changes and fething the data for this
changes are located in thunks.

We have to note that while thunks being a central place for async requests is a standard
practice, thunks calling a sequence of simple Redux actions is not. The other
widely used approach is using thunks only for async stuff and use redux actions as UI
actions. To understand what's the difference between two approaches we have to mention
briefly the difference between object graph and data storage.

### Object graph vs Data storage

Object graph is a hierarchial set of objects in memory, linked by associations. This is what we're
used to when writing UI programs. E.g. a user has many posts, each post has a list of
likes, each like has an owner (user), etc. The associations between objects can be
very deep and even cyclical. If you think for a moment this structure is really close to
the way user interfaces are organized.

Data storage on the hand, is optimized for storing the data, i.e. it has a flat set of tables
with entites, probably linked to other entities by their respective ids. This is actually
really close to sql databases.

Now in our app we use Redux store which is represented by flat stores. Using `normalizr`
to transform nested API responses is considered best practice for Redux. So
Redux is `Data storage`, while UI data (props) is an `Object graph`.

### Our approach

We consider selectors as a mappers from `Data storage` (Redux) to an `Object Graph`
(Props). Thunks work the other way round - they map simple UI actions to a set of simple
Redux actions. This keeps reducers interface really simple and all the real custom logic
is contained in thunks and localized to the UI they are working for.

Here's an example of thunk from our app

```
() =>
async function fetchCommentsThunk(dispatch, _getState, call) {
dispatch(reduxActions.setLoadingComments(true));
let response;
try {
response = await call(api.fetchComments);
} catch (e) {
call(Alert.alert, 'Error', 'Could not connect to server', [{ text: 'OK' }]);
return;
} finally {
dispatch(reduxActions.setLoadingComments(false));
}
dispatch(reduxActions.createComments(response.entities.comments));
};
```

You see the interface of the reducer is really simple here, just two actions:
`setLoadingComments` and `createComments`. Now in a different UI part you can reuse
this redux actions the create a new thunk with its own logic. The reducers maintain constant
complexity when your app grows. This is the precise reason we prefer this approach
over the alternaive (see below)

### Alternative approach

An altenative approach is to react to each UI action inside a reducer and use thunks only
for async stuff. You have to understand that this approach, though, leads to the growing
of reducers code as your app grows, because for a new piece of UI have to create
new actions and handle them inside reducers. What's worse - the code for handling
different actions is often doing the same things. This make reducers hard to reason about.

This approach though has advantage in offering atomic state updates. If you noticed above
we are dispatching `reduxActions.setLoadingComments(false)` and then
`reduxActions.createComments(response.entities.comments)`. These are basically two state
mutations and while it's not a problem in this app, it could have some undesirable
effects in other circumstances

##### P.S. Call as a third argument to thunk

While most times you'll see apps using just two arguments in thunks
(`dispatch` and `getState`), we also add a third argument `call`. It is used for calling
side effects inside Thunks (see [Side effects](Containers.md)). `call` is taking function
with a side effect as a first argument and executes it.
The rest of the `call` arguments are passed to the function being executed. E.g.
```
const square = x => x * x;
const result = call(square, 3);
// result = 9
```

This little tweak allows us to test thunks very efficiently.

7 changes: 7 additions & 0 deletions mobile/ReactNativeTutorial/docs/UI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## UI components

UI components are a pure React Native components (see [Containers](Containers.md) for details).
We try to store all app state inside the Redux store (i.e. not using `setState`) and reacting
only using thunks, not plain Redux actions.
This is an opinionated approach in regards to both `setState` and using only thunks
(see [Thunks](Thunks.md) for details).