Free React Course
For Beginners

L6 - Managing state in React

React For Beginners by Itera

Key Points

  • Problems with setState and useState
  • Context
  • Redux
  • MobX

The problem with setState and useState

// Component to show user details
const L3: FC<UserInputProps> = ({ nationality }) => (
    <strong>{nationality}</strong>
);

// Some component #2
const L2: FC<UserInputProps> = ({ nationality }) => (
    <L3 nationality={nationality} />
);

// Some component #1
const L1: FC<UserInputProps> = ({ nationality }) => (
    <L2 nationality={nationality} />
);

            L1
            ⬇️
            nationality 
            |-> L2
                ⬇️
                ...
                    ⬇️
                    nationality
                    |-> LN 
        

Imagine, we need to change something

  • A lot of useless work - need to update each component in hierarchy
  • Risk of mistake - one typo and everything failed
  • Performance reasons - all consumers will be rerendered

React context

Context provides a way to pass data through the component tree without having to pass props down manually at every level

Working with context

Create context with default value

import { createContext } from "react";
const Context = createContext({
    nationality: "unknown",
    setNationality: (_: string) => {},
});
    

Create a state with similar properties

class App extends Component {
state = {
    nationality: "Unknown",
    setNationality: (v: string) =>
    this.setState({ ...this.state, nationality: v }),
    };
}

Pass context into the render tree

render() {
    return (
        <Context.Provider value={this.state}>
            <h1>My App</h1>
            <L1 />
            <SomeContainer />
        </Context.Provider>
    );
}

Use context from anywhere

import { useContext } from "react";
const L3: FC = () => {
    const { nationality } = useContext(Context);
    return <strong>{nationality}</strong>;
};
            

Typical mistakes with Context

💔Using instead of local state

Use context for commonly used data only

💔Mutating context directly

const SomeContainer: FC<SomeContainerProps> = () => {
    const ctx = useContext(context);
    const btnHandler = () => {
        const { nationality } = fetchUser();
        ctx.nationality = nationality;
    };
    return <MyBtn text="Fetch user" onClick={btnHandler} />;
};

Problems with context

It's not state management tool

Context was designed for sharing, not for updating

Performance issues

Changing context forces to rerender all context consumers. If your application is big and highly depends on the context - this might heart the performance.

Developer Experience issues

Context doesn't have it's own update mechanism. It's relies on the component state to track changes. You will need to create special components which will handle state just to avoid a mess.

Solution - use State Management Tools

Redux - the most popular management tool

  • 7.5M downloading in a week
  • 57.8K ⭐ in GitHub

The idea of the Redux is simple

Any event
⬇️
⬇️
⬇️
Store
⬇️
UI

In practice Redux is not that simple

  • High learning curve
  • Lot's of boilerplate
  • Needs extra tools to be good

Dan Abramov, father of the Redux

I would like to amend this: don't use Redux until you have problems with vanilla React.

MobX - another state management tool

  • 880K downloading in a week
  • 25K ⭐ in GitHub

The idea of the MobX is not that simple

MobX based on observables and patching your components

A lot of people dislike MobX because it works like a "magic"

MobX has several advantages

  • It's much easy to use
  • It's has performance improvements out of the box
  • It's not connected to the React at all

Practice with MobX

Install MobX and dependencies

npm i mobx mobx-react

Create a simple class

class UserDetailsStore {
    nationality = "unknown";
    constructor() {
        makeAutoObservable(this); // !important
    }

    setNationality(v: string) {
        this.nationality = v;
    }
}

Create an instance of the store

const userDetailsStore = new UserDetailsStore();

Mark component as an observer and use it

const L3: FC = observer(() => {
    const nationality = userDetailsStore.nationality;
    return <strong>{nationality}</strong>;
});
          

Update is also simple

type SomeContainerProps = {};
const SomeContainer: FC<SomeContainerProps> = () => {
        const btnHandler = () => {
        const { nationality } = fetchUser();
        userDetailsStore.setNationality(nationality);
    };
    return <MyBtn text="Fetch user" onClick={btnHandler} />;
};

MobX has auto linting

import { configure } from "mobx";

configure({
    enforceActions: "always",
    computedRequiresReaction: true,
    reactionRequiresObservable: true,
    observableRequiresReaction: true,
    disableErrorBoundaries: true
});

Better use MobX + Context

What to use?

  • Context - may be for the tiny projects 🤔
  • MobX - the default one ❤️❤️
  • Redux + Redux Toolkit - in case your team already knows it ❤️

Home task

  • Using context write the timer that will have two buttons
  • start and stop
  • Timer should:
  • Be stopped by default
  • On start, App should display time in format HH:MM:SS
  • On start page title should be changed to "Timer is running"
  • On stop timer should be stopped, the latest value should be present
  • Page title should be returned back to normal
  • Install MobX
  • Implement same functionality with MobX

Useful links

Join me

Twitter
GitHub