(by @pedpess)
These are my own paper version notes meeting the digital world. They are related to my learnings from 2017-18, or just stuff I thought would be nice to keep a record during the time I was grasping React Native to use in several projects of mine.
I also did a study notes about Javascript. Check it out! ;)
PS1: Even though, most of React Native comes from React, I will keep the focus of this study note just with mentions to React Native.
PS2: As usual, don't expect all the material here to be 100% accurate or up-to-date. If you find some part that's wrong, or missing or old, please send me a comment to fix it! :)
- Declarative: It just declares what you want. Examples: HTML and React ;D.
- Imperative: Steps in our code in order to retrieve a desired state. "Mutations happen and you state usually changes".
React Native as React are declarative frameworks, which you can define what you want to build "what" you want to build rather than "how". If you want to show a button as a component you simply declare it. Example: <Button>{'This is my Button'}</Button>
.
Both React and React Native manage a similar internal representation of a DOM (Document Object Model) through a "virtual DOM". The key difference on how React and React Native perform nicely during render time is because of a thing called Reconciliation.
In a nutshell: Reconciliation does the differentiation between what changed in the DOM tree and update just the UI components that had their values changed. The React and React Native keep a reference to the previous state of the DOM tree and compare with the current state. The previous state or copy of the DOM tree is called "virtual DOM", with that, the frameworks don't manipulate the DOM directly.
Javascript is bundled, transpiled and minified. Also, the threads that are executed are the single JS thread, UI, and Layout from the native side Android and iOS.
A bit of its architecture can be seen below.
JS Bridge
Serialized JSON
React JS iOS/Android UI
+-------------+ ----------------> +-------------------+
| | render button | +--------+ |
| <Button /> | | | Button | |
| | | +--------+ |
| | | |
| | | |
| | | |
+-------------+ <---------------- +-------------------+
button pressed
Most of the computation happens in the React JS side using the JS single thread. It's costly, so better use it in a way that you won't block the JS Bridge and crash your app.
Components are basically functions where you pass props, state or both.
Components can be split by Stateless or Stateful.
-
Stateless components, also known as, "dummy components" just hold props and not the state. It should be pure, no mutations.
-
Stateful components are known as "container components". It holds state and props. This is the foundation which contains other inner components, most of them are stateless and they use the state and props coming from the container or parent.
The React Native lifecycles are MOUNT > UPDATE > UNMOUNT.
Mount:
componentDidMount()
: Do processing that is not needed for UI. Asynchronous calls are most used here.
Update:
The update for React Native UI happens whenever the state changes or props, component is rerendered.
-
componentWillReceiveProps()
: Receives the nextProps argument and updates any state fields that rely on props. -
shouldComponentUpdate()
: Receives nextProps and nextState arguments that compare the changed values, and if they change, render should be called again. Iffalse
is returned then the render method is not called and UI won't update. -
componentDidUpdate()
: Receives prevProps and prevState arguments and do anything that is not needed for UI (Network requests etc).
Unmount:
componentWillUnmount()
: Dump the allocation of memory for you stateful class component: 1) Remove the event listeners, 2) Stops network requests and 3) Clear timeouts and intervals.
The PureComponent
has a predefined shouldComponentUpdate()
that does a shallow differentiation of props. It's good to avoid rerendering.
PropTypes are apart from React and React Native framework as prop-types
. It validates the type of data from your props are at runtime. The PropType should be called as static
.
-
FlatList: It's a lazy loaded list. It won't update a sorted list straight from
.sort()
. Using spread operator[...obj].sort()
or.concat()
and then.sort()
fix this. -
SectionList: Instead of a full array list like in FlatList, you have sections with header and data.
-
TextInput: For this component to accept user's input you should think that React is always the source of truth. The inputs won't get updated unless you create a function that will toggle the state and pass use it in its built-in props called
onChangeText={}
. Toggle the state just after field validation.
In React Native you can style your components using a style object. However, style object can cause unnecessary rerendering. To overcome that, there is an already made tool in React Native called StyleSheet
, this is used to make sure your components won't be rerendered when receiving a style object.
Example:
const styles = StyleSheet.create({
container: {
height: 10,
width: 10,
}
});
Functions can also cause rerendering. Don't use a function in onPress
props in the buttons. Doing that, functions will be recreated all the time changing the state and causing rerendering. Create an external function out of the props and call it in onPress
.
Example:
const toggleFunc = () => {
// doSomething
}
render() {
return(
<Button onPress={this.toggleFunc} />
);
}
Create an object, arrays, functions et al, out of the render()
method, because otherwise, a new object will be created at each render.
Animations in RN were a bit complex to be performed smoothly well because of the heavy traffic in the communication 'JSON' that is needed from the JS bridge (sometimes you can even block the bridge completely). However, Animated API came to fix this and it's a wrapper with different ways on how to perform animations keeping the JS thread alive and not decreasing the UI frame rate that should be kept in 60fps. The three main animations from the API are timing()
, decay()
and spring()
.
In order to control the pace of how the animation should happen, you can use Easing
module, which is used in the timing()
animation.
The native driver is an attribute which receives a boolean value to active it in your animations or not. It computes the values of your animation in the JS thread straight to the native side having one input to get one output. Nevertheless, this attribute has the caveat that it cannot be used in layout properties like flexbox and position.
Animated API is big, so some of the other references that are usually used together with it are PanResponder
to respond to the touch event of a user and Interpolation
to map the input and output ranges of an animated property.
Tip: About interpolations, you can't compute an animated value (Animated.Value(0)
) straight with a number. It will cause a crash and fix that you need to use the interpolation property to an animated value instead to do the calculation of your input and output.
- Redux is unidirectional data flow.
- It has a single source of truth called the store. The store is the state holder of your application.
- One of the most important parts of the Redux is the Reducer. It receives an old state and calculates the next state to return a new state of the same object (immutability).
A raw pic of its architecture here below:
+-------------+
+-------------+ | |
| | update | |
| Store +------------------------>+ View |
| | | |
+-------+-----+ | |
^ +------+------+
| |
| |
| |
return | new piece of state | triggers action
| |
| |
| |
| v
+-------+--------+ +-------+--------+
| | dispatch action | |
| Reducer +<--------------------+ Action Creator |
| | | fn() |
+----------------+ +----------------+
createStore()
is the function responsible to aggregate the core of Redux, it will receive your reducers, an initial state that you might want to keep and an enhancer that can be a middleware (like thunk). If you have multiple enhancers you can use the functioncompose()
.- In some cases you might end up have multiple reducers manipulating your store, for that, Redux offers a
combineReducer()
function to nest all your reducers in an object. You can also use multiplecombineReducer()
functions to split the logic and just aggregate the reducers that are somehow related to each other.
Example:
const reducers = combineReducers({
user: combineReducers({
registration: userRegistrationReducer
})
});
In this example, the user can have multiple reducers that could be handling user related stuff e.g. login, logout, registering.
To make your actions asynchronous you may use redux-thunk library as a first step. Thunk is a middleware that should be used as an enhancer argument of your createStore()
function. You can use async action creators to fetch data from APIs for example.
If you want to fetch information about a user trying to log in there would be at least 3 async action types for it:
- SENT
- SUCCESS
- FAILED
Each of these would perform a different state manipulation through a user reducer.
Thumbs up 👍:
- Do as much as you can with the local component state, then add Redux if you visualize you gonna hit pain points in the future.
- Always return a new state in your reducer, better with a spread operator Ex:
return [newStateProperty, ...oldStateProperties]
. Doing that you are making your object being cloned into a new one keeping the old one immutable.
Thumbs down 👎:
- Forget to pass props down or connect to the component from state store
- Too much-nested state
- Duplicate your state
- Not updating all props
- Not sure where data from the store is managed
Redux can be used apart from React. However, in order to update our views in React Native, we should use the react-redux library to our components observe if some change happens in the state shared state aka store. To make the components aware of this change it should be used a connect()
function, which will receive the state you want your props to map (mapStateToProps) and what actions you want to dispatch from your components (mapDispatchToProps). Moreover, to glue your Redux store to your RN components a component called <Provider />
should be used, thus, <Provider />
+ connect()
= Happy Family :).
Now our previous Redux architecture got an update.
maps +---------------+
+---------------> | Provider |
| | |
+---------------+ mapStateToProps +-------+--------+ +---------------+
| | mapDispatchToProps | connect()(View)| | |
| Store +-------------------> | | | |
| | +----------------+ | View |
| | | |
+-------+-------+ | |
^ | |
| | |
| return new state +-------+-------+
| |
| | triggers action
+-------+--------+ |
| | |
| | |
| Reducer | v
| | +-----------------------+
| | | Action Creator |
+-------+--------+ | |
^ +------------+----------+
| |
| |
| ask to update state |
| |
| |
| |
+-------+--------+ |
| | |
| Action | dispatch action |
| | <-----------------------------------------------------+
| |
+----------------+
connect()()
is a 'High Order Function' (HOF) and a facade, which receives a component as an argument into an 'Immediately Invoked Function Expression' (IFFE) and return it.
The state can be persisted in different forms and there are some libraries for that alike redux-persist, redux-offline. What they basically do is to rehydrate and reload the reducers checking for values to be persisted. One applicable example would be storing a token. You can also do that with the AsyncStorage from RN for small state persistence.
© Pedro Pessoa, 2017. Unauthorized use and/or duplication of this material without express and written permission from this text author and/or owner is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to Pedro Pessoa and with a reference to this gist link with appropriate direction to the original content.