Translate

Tuesday, 3 September 2019

State Management in React Native

State Management in React Native

Managing state is one of the most difficult concepts to grasp while learning React Native, as there are so many ways to do it. There are countless state management libraries on the npm registry — such as Redux — and there are endless libraries built on top of other state management libraries to simplify the original library itself — like Redux Easy. Every week, a new state management library is introduced in React, but the base concepts of maintaining the application state has remained the same since the introduction of React.

The most common way to set state in React Native is by using React’s setState() method. We also have the Context API to avoid prop drilling and pass the state down many levels without passing it to individual children in the tree.

Recently, Hooks have emerged into React at v16.8.0, which is a new pattern to simplify use of state in React. React Native got it in v0.59.

In this tutorial, we’ll learn about what state actually is, and about the setState() method, the Context API and React Hooks. This is the foundation of setting state in React Native. All the libraries are made on top of the above base concepts. So once you know these concepts, understanding a library or creating your own state management library will be easy.

What Is a State?

Anything that changes over time is known as state. If we had a Counter app, the state would be the counter itself. If we had a to-do app, the list of to-dos would change over time, so this list would be the state. Even an input element is in a sense a state, as it over time as the user types into it.

Intro to setState

Now that we know what state is, let’s understand how React stores it.

Consider a simple counter app:

import React from 'react'
import { Text, Button } from 'react-native'

class Counter extends React.Component {
    state = {
        counter: 0
    }

    render() {
        const { counter } = this.state
        return (
            <>
                <Text>{counter}</Text>
                <Button onPress={() => {}} title="Increment" />
                <Button onPress={() => {}} title="Decrement" />
            </>
        )
    }
}

In this app, we store our state inside the constructor in an object and assign it to this.state.

Remember, state can only be an object. You can’t directly store a number. That’s why we created a counter variable inside an object.

In the render method, we destructure the counter property from this.state and render it inside an h1. Note that currently it will only show a static value (0).

You can also write your state outside of the constructor as follows:

import React from 'react'
import { Text, Button } from 'react-native'

class Counter extends React.Component {
    state = {
        counter: 0
    }

    render() {
        const { counter } = this.state
        return (
            <>
                <Text>{counter}</Text>
                <Button onPress={() => {}} title="Increment" />
                <Button onPress={() => {}} title="Decrement" />
            </>
        )
    }
}

Now let’s suppose we want the + and - button to work. We must write some code inside their respective onPress handlers:

import React from 'react'
import { Text, Button } from 'react-native'

class Counter extends React.Component {
    state = {
        counter: 0
    }

    render() {
        const { counter } = this.state
        return (
            <>
                <Text>{counter}</Text>
                <Button onPress={() => { this.setState({ counter: counter + 1 }) }} title="Increment" />
                <Button onPress={() => { this.setState({ counter: counter - 1 }) }} title="Decrement" />
            </>
        )
    }
}

Now when we click the + and - buttons, React re-renders the component. This is because the setState() method was used.

The setState() method re-renders the part of the tree that has changed. In this case, it re-renders the h1.

So if we click on +, it increments the counter by 1. If we click on -, it decrements the counter by 1.

Remember that you can’t change the state directly by changing this.state; doing this.state = counter + 1 won’t work.

Also, state changes are asynchronous operations, which means if you read this.state immediately after calling this.setState, it won’t reflect recent changes.

This is where we use “function as a callback” syntax for setState(), as follows:

import React from 'react'
import { Text, Button } from 'react-native'

class Counter extends React.Component {
    state = {
        counter: 0
    }

    render() {
        const { counter } = this.state
        return (
            <>
                <Text>{counter}</Text>
                <Button onPress={() => { this.setState(prevState => ({ counter: prevState.counter + 1 })) }} title="Increment" />
                <Button onPress={() => { this.setState(prevState => ({ counter: prevState.counter - 1 })) }} title="Decrement" />
            </>
        )
    }
}

The “function as a callback” syntax provides the recent state — in this case prevState — as a parameter to setState() method.

This way we get the recent changes to state.

What are Hooks?

Hooks are a new addition to React v16.8. Earlier, you could only use state by making a class component. You couldn’t use state in a functional component itself.

With the addition of Hooks, you can use state in functional component itself.

Let’s convert our above Counter class component to a Counter functional component and use React Hooks:

import React from 'react'
import { Text, Button } from 'react-native'

const Counter = () => {
    const [ counter, setCounter ] = React.useState(0)
    return (
        <>
            <Text>{counter}</Text>
            <Button onPress={() => { setCounter(counter + 1 ) }} title="Increment" />
            <Button onPress={() => { setCounter(counter - 1 ) }} title="Decrement" />
        </>
    )
}

Notice that we’ve reduced our Class component from 18 to just 12 lines of code. Also, the code is much easier to read.

Let’s review the above code. Firstly, we use React’s built-in useState method. useState can be of any type — like a number, a string, an array, a boolean, an object, or any type of data — unlike setState(), which can only have an object.

In our counter example, it takes a number and returns an array with two values.

The first value in the array is the current state value. So counter is 0 currently.

The second value in the array is the function that lets you update the state value.

In our onPress, we can then update counter using setCounter directly.

Thus our increment function becomes setCounter(counter + 1 ) and our decrement function becomes setCounter(counter - 1).

React has many built-in Hooks, like useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect and useDebugValue — which you can find more info about in the React Hooks docs.

Additionally, we can build our own Custom Hooks.

There are two rules to follow when building or using Hooks:

  1. Only Call Hooks at the Top Level. Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

  2. Only Call Hooks from React Functions. Don’t call Hooks from regular JavaScript functions. Instead, you can either call Hooks from React functional components or call Hooks from custom Hooks.

By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.

Hooks are really simple to understand, and they’re helpful when adding state to a functional component.

The post State Management in React Native appeared first on SitePoint.



No comments:

Post a Comment