React Hooks useContext & State management

[Fuente: https://reactjs.org/docs/hooks-reference.html#usecontext]

useContext

const value = useContext(MyContext);

Accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.

When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider.

Don’t forget that the argument to useContext must be the context object itself:

  • Correct: useContext(MyContext)
  • Incorrect: useContext(MyContext.Consumer)
  • Incorrect: useContext(MyContext.Provider)

A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

Tip

If you’re familiar with the context API before Hooks, useContext(MyContext) is equivalent to static contextType = MyContext in a class, or to <MyContext.Consumer>.

useContext(MyContext) only lets you read the context and subscribe to its changes. You still need a <MyContext.Provider> above in the tree to provide the value for this context.

Putting it together with Context.Provider

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

This example is modified for hooks from a previous example in the Context Advanced Guide, where you can find more information about when and how to use Context.

[Fuente: https://medium.com/simply/state-management-with-react-hooks-and-context-api-at-10-lines-of-code-baf6be8302c]

State Management with React Hooks and Context API in 10 lines of code!

Ultimate and super simple Redux alternative for your App.

State management in 10 lines of code?

Yes, you are reading it right. Just ten lines! But let’s take a look at two new React concepts first. Then I will introduce you the most simple state management ever.


React Context API

It’s actually not a new idea. Context API was part of React for long time, but only in experimental state.

import React from 'react';
const ThemeContext = React.createContext(
  /* optional default value */
);
const App = props => (
  <ThemeContext.Provider value={{ primaryColor: green }}>
    {props.children}
  </ThemeContext.Provider>
);
const ThemedButton = () => (
  <ThemeContext.Consumer>
    {value => (
      <Button primaryColor={{ value.primaryColor }}>
        I'm button using context!
      </Button>
    )}
  </ThemeContext.Consumer>
);
import React, { useReducer } from 'React';
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
      
    default:
      return state;
  }
};
function Increment({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, { count: 0 });  return (
    <button onClick={() => dispatch({ type: 'increment'})}>
      Increment: {state.count}
    </button>
  );
} 

Now what happens if you combine Context API and Hooks together?

State management in 10 lines of code!

This is beautiful example of synergy. Using Context API and Hooks together gives you very simple and ultimate global state management for your App.

import React, {createContext, useContext, useReducer} from 'react';
export const StateContext = createContext();
export const StateProvider = ({reducer, initialState, children}) =>(
  <StateContext.Provider value={useReducer(reducer, initialState)}>
    {children}
  </StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
  • Then we create new React component called StateProvider. This component wraps it’s children with Provider that accepts value prop.
  • useReducer accept reducer and initialState which are passed as a props from outside. So you have full control over them inside your app as you will see below.
  • The main trick here is that we pass result of the useReducer hook as a value to our ProviderSo it becomes available in any component in your app component tree.
  • useContext is another React hook that accepts context object as a parameter (StateContext in our case). Normally you would use useContext(StateContext) everywhere inside your app, where you would like to access the value of the context. But why repeat yourself, right?
  • So this useStateValue function on the last line of our code is basically a custom hook and it’s a little trick how to access your state in any component of your application with less amount of code. It returns exactly the same [state, dispatch] array, that is passed as a value to our Provider.

So we have our minimalistic state management ready! 🎉 But how do we use it?

Enhance your app with global state

In order to use this simple state management in your app you just need to wrap it with our StateProvider created above and pass reducer and initialState like this:

import { StateProvider } from '../state';

const App = () => {
  const initialState = {
    theme: { primary: 'green' }
  };
  
  const reducer = (state, action) => {
    switch (action.type) {
      case 'changeTheme':
        return {
          ...state,
          theme: action.newTheme
        };
        
      default:
        return state;
    }
  };
  
  return (
    <StateProvider initialState={initialState} reducer={reducer}>
        // App content ...
    </StateProvider>
  );
}

Then use and update the state inside your app

Now you have unlimited access to your global state in every component of your app:

import { useStateValue } from './state';

const ThemedButton = () => {
  const [{ theme }, dispatch] = useStateValue();
  return (
    <Button
      primaryColor={theme.primary}
      onClick={() => dispatch({
        type: 'changeTheme',
        newTheme: { primary: 'blue'}
      })}
    >
      Make me blue!
    </Button>
  );
}
import React, { Component } from 'react';
import { StateContext } from './state';
class ThemedButton extends Component {
  static contextType = StateContext;  render() {
    const [{ theme }, dispatch] = this.context;    return (
      <Button
        primaryColor={theme.primary}
        onClick={() => dispatch({
          type: 'changeTheme',
          newTheme: { primary: 'blue'}
        })}
      >
        Make me blue!
      </Button>
    );
  }
}

But what about splitting reducer to more then one?

There is an easy answer. For more complex applications it may be handy to have multiple reducers. Well, the reducer (the one passed as a prop to StateProvider) is completely in your hands. Personally I would go this way:

import userReducer from './reducers/user';
import basketReducer from './reducers/basket';
const mainReducer = ({ user, basket }, action) => ({
  user: userReducer(user, action),
  basket: basketReducer(basket, action)
});

And what about middleware?

Technically the middleware is a function called just before the dispatched action reaches the reducer. I would do something like this:

import userReducer from './reducers/user';
import basketReducer from './reducers/basket';const mainReducer = ({ user, basket }, action) => {
  // middleware goes here, i.e calling analytics service, etc.  
  return {
    user: userReducer(user, action),
    basket: basketReducer(basket, action)
  };
});

 

React-simply introduction

You can find my minimalistic state management gadget in my React-simply repository on Github.


NPM package

You can install my state package from NPM registry:

npm i @react-simply/state

Your feedback matters!

If you like my story and you would like to encourage me in writing more often, please clap, share and comment. Your interest is the best motivation to share my thoughts.