Easy to use global state store for React or Next: useGlobalHook

In this article, I will tell and show with examples how to implement a global state store in React or Next, and why it is needed at all.

It is very popular now to implement this on Redux, but In my opinion the implementation on Redux is worse perceived, harder for beginners, and requires more code.

We will use useGlobalHook, I use it all the time when developing projects, and in my opinion, it is very convenient.

(I must say right away that this is not advertising, because I am not the author of useGlobalHook, and I am writing this post solely because I like it. I want to teach this method to several of my students, and at the same time share it with you).

Why do we need a global state store at all?

I could not refuse this paragraph, as someone may not know the answer to this question.

If you are a beginner developer using React, then you have most likely already come across the useState hook. Just in case, let’s analyze its use with a simple example:

The example is just to show how the useState hook works.

The example is just to show how the useState hook works.

When you click on the button with the name, the block with H1 will be redrawn and the selected name will be displayed. This will not reload the page. This is what we need useState for.

When we create the useState variable, we declare two variables – the first (userName) will store the value, and the second (setUserName) serves as a function to set the value to the first variable. When we change the value of a variable, the part of the project that uses that variable is redrawn without reloading the page.

However, it often happens that we need the ability to use the same variable in different parts of the project, moreover, to be able to set its value from different parts of the project, and so that when the value of the variable changes, the part of the project in which this variable is used is rerendered.

For example, we want to store the username, and display it in different components (in the application header, in modals, etc.), as well as some other data that is used in different places in the project.

How to be? Since the example is simple, it might seem like a good idea to loop through props, but in real projects you may end up having to loop through more than one component, creating a very complex system.

There must be an easier way! Of all the existing ones, I like useGlobalHook the most, so let’s move on to it right away.

Implementation of global storage

First of all, install the npm package use-global-hook

npm i use-global-hook

Or if you use yarn like me, then

yarn add use-global-hook

Next, let’s create a specific file structure:

In the src folder, create a store folder

In the store folder, create an index.js file with the following content:

import GlobalHook from "use-global-hook";
import * as actions from "./actions";

const initialState = {
  
  user_name: "",
  user_role: "user",
  // здесь перечислите все переменные которые должны быть глобальными
  // можете задать им значния по умолчанию

};

const useGlobal = GlobalHook( initialState, actions );

export default useGlobal;

In the initialState variable, we list absolutely all the variables that we want to be available from any part of the project.

What about import * from “./actions”?

The fact is that we can only change the value of global variables through special functions, which we will write a little later. Such functions are called actions, so we will store them in the actions folder.

Create this folder in the src/store folder, and create an index.js file in it.

For convenience of perception and modification of the code, we will not write all the functions for changing the global state in one file. Instead, we will divide these functions into logical groups, and create a separate file for each group in the actions folder.

In /src/actions/index.js we will import the functions from these files.

Let’s try to imagine a real situation – user authorization. We have to access the backend by sending an email and password, and the backend will return the user’s data to us, namely, his name and role. We will have to write them to our global storage, and then display them in certain components.

In which logical group can we define this function? That’s right, the authorization system. That’s why we create a sign.js file in the actions folder

Before we write our function, we must import all the functions from the sign.js file to the file src/actions/index.js, as well as export them from this file in order to use them later via useGlobalHook. Here’s how we do it:

Write to file src/actions/index.js following:

import * as sign from './sign';

export {

  sign,
  
}

Okay, now let’s write our authorization function, where we will receive data from the backend and change global states. Since this is the authorization function (in English signin) in the file src/actions/sign.js Let’s create an In function and export it:

export async function In( store, email, pass ) {

  try {

    // тут должен быть запрос к серверу, который запишет ответ в переменную data
    // но чтобы не усложнять пример я просто имитирую ответ сервера
    // создав переменную data:

    const data = {
      name: "Андрей",
      role: "admin"
    }

    // типа мы получили ответ сервера с нужными данными
    // теперь мы должны записать их в глобальный стейт

    // делаем мы это вот так:

    store.setState({

      user_name: data.name, // задаём значение user_name
      user_role: data.role, // задаём значение user_role

    });

    // тут может быть редирект на другую страницу

  } catch ( err ) {

    console.error( err );
    // тут обработка ошибок. я не стал усложнять пример
    // но в реальном проекте не игнорируйте этот блок
    // (если не знаете, погуглите try catch )

  }
  
}

Pay attention to an important thing! When we declare useGlobalHook actions (i.e. create such functions), we always take “store” as the first argument in them. Through the store we can interact with the global storage inside this function in the following ways:

Change global variables:

store.setState({
  глобальнаяпеременна: новоезначение,
})

Get the value of a global variable (inside this function):

store.state.названиепеременной

Call another function to change the global value (another action):

store.actions.названиеФункции()

This is how we interact with the global storage inside the action functions.

Outside of similar functions, we interact with it another wayabout which I will now just tell you.

Let’s create a test page on which we will simulate authorization. Look, we need to first enter the email and password, and they should be written to a variable. We do not need this data globally, so we will use the usual useState.

This is important – no need to push into the global storage what is used only on one page, or only in one component!

import { useState } from "react";

const Auth = () => {

  const [ email, setEmail ] = useState("");
  const [ password, setPassword ] = useState("");

  return (

    <div>

      <input

        type = "text"
        value = { email }
        onChange = { ( event ) => { setEmail( event.target.value ) }}

      />

      <input

        type = "password"
        value = { password }
        onChange = { ( event ) => { setPassword( event.target.value ) }}

      />

      <button>Авторизоваться</button>

    </div>

  );

}

export default Auth;

The framework is ready, now we have to use our global storage in it. For the sake of simplicity, let’s do this: if our global variable user_name is empty, we will show the authorization form. If there is something in it, we will show the inscription “%Name%, you have successfully authorized!”.

To access global states and actions, we do the following. In any component where we need them, we write like this:

import useGlobal from 'путь/к/папке/store';

const MyComponent = () => {

  const [ globalState, globalActions ] = useGlobal();

  return (
    ///...
  );

}

See, in globalState we have our global variables, and in globalActions our global action functions. Let’s first create a condition in our authorization page, which I wrote about above – if the username is empty, we show authorization, if not, then the message in which this variable is used.

For convenience, I destructure from globalState those properties that we need. In this example, we will use the user_name variable, let’s destructure it:

const [ globalState, globalActions ] = useGlobal();

const { user_name } = globalState;

That’s it, we can get the value from the user_name global variable inside this page! (To get the value on another page or in another component, we do the same). Let’s write the condition about which I wrote above. Now our login page looks like this:

import { useState } from "react";
import useGlobal from 'путь/../store'; // тут должен быть п

const Auth = () => {

  const [ globalState, globalActions ] = useGlobal();

  const { user_name } = globalState;

  const [ email, setEmail ] = useState("");
  const [ password, setPassword ] = useState("");

  return (

    user_name === "" 
    
      ? <div>

          <input

            type = "text"
            value = { email }
            onChange = { ( event ) => { setEmail( event.target.value ) }}

          />

          <input

            type = "password"
            value = { password }
            onChange = { ( event ) => { setPassword( event.target.value ) }}

          />

          <button>Авторизоваться</button>

        </div>

      : <div>

          { user_name }, вы успешно авторизовались!

        </div>

  );

}

export default Auth;

We learned how to use the values ​​of global variables, and now let’s learn how to call action functions that change these values. We need to call the global In function, which is located in the sign. For convenience, let’s destructure the functions of the sign file from globalActions into the sign variable:

const [ globalState, globalActions ] = useGlobal();

const { user_name } = globalState;

const { sign } = globalActions;

Remember how we imported functions from the sign file in store/actions/index.js and exported them from there? Therefore, they can now be called from globalActions.sign.

When we have several logical groups (action files), they will also be called – we create a separate file in store/actions/, write all the functions of this logical group there, then import them into store/actions/index.js and export from there as I explained above. And we get the call structure as follows:

globalActions.название_Файла_Логической_Группы.название_Функции_Из_Этого_Файла()

In order not to prescribe “globalActions.” before calling the function each time, we will destructure the groups of functions, as I described above.

Now we need to call the In function by clicking on the button and pass email and password to it:

<button onClick = { () => sign.In( email, password ) } >
            
  Авторизоваться
  
</button>

Pay attention to an important aspect! When we declared the In function, the first argument in it was store, and email was already the second, and password was the third:

export async function In( store, email, pass ) {

And when we call it, we specify email as the first argument!

This is a feature of useGlobalHook action functions – when we call them, we do not pass the first argument store, but in the function we always expect store as the first argument in order to be able to interact with the global store inside the function. Remember, this is very important!

So, our test case is ready. Here is the final code of the authorization page:

import { useState } from "react";
import useGlobal from 'путь/к/папке/store';

const Auth = () => {

  const [ globalState, globalActions ] = useGlobal();

  const { user_name } = globalState;
  const { sign } = globalActions;

  const [ email, setEmail ] = useState("");
  const [ password, setPassword ] = useState("");

  return (

    user_name === "" 
    
      ? <div>

          <input

            type = "text"
            value = { email }
            onChange = { ( event ) => { setEmail( event.target.value ) }}

          />

          <input

            type = "password"
            value = { password }
            onChange = { ( event ) => { setPassword( event.target.value ) }}

          />

          <button onClick = { () => sign.In( email, password ) } >
            
            Авторизоваться
            
          </button>

        </div>

      : <div>

          { user_name }, вы успешно авторизовались!

        </div>

  );

}

export default Auth;

I hope the logic of using global variables and functions to change them is clear. If not, read the article again carefully. The example turned out to be large, but in reality, having understood this system, it is very easy and convenient to use it.

Function to quickly change a global variable

It often happens that you need some quick way to change any global variable, so as not to write a function to change the value of this particular variable. To do this, I came up with a universal function for changing any global variable. Let’s write it and learn how to use it! To file store/actions/index.js add the changeStates function, now this file looks like this:

import * as sign from './sign';

async function changeStates( store, states ) {

  store.setState( states );

}

export {

  changeStates,
  sign,
  
}

Now in any React component, we can change the value of one or more global variables like this:

import useGlobal from 'путь/к/папке/store';

const SomeComponent = () => {

  const [ globalState, globalActions ] = useGlobal();

  const { user_name, user_role } = globalState;
  const { changeState } = globalActions; // деструктурируем универсальную ф-цию

  function Test() {

    // вызывая её, указываем переменные
    // которые хотим изменить, и их новые значения

    changeState({

      user_name: "Андрей",
      user_role: "admin"

    });

  }

  return (

    <div>

      <h1>{ user_name }</h1>
      <p>{ user_role }</p>
      <button onClick = { Test }>Проверим?</button>

    </div>

  )

}

export default SomeComponent;

The examples in this article are very simple, but with these tricks you can implement any complex logic that requires a global store.

I didn’t digress on purpose, and showed only what is necessary in order to understand how to use useGlobalHook. Now this tool is in your hands.

I have used it in many serious projects, and if I am faced with the choice of which global storage manager to use – I always choose useGlobalHook, it is convenient, and has proven itself well in my eyes.

This is just one of the options for implementing global storage. There are others like the popular Redux, MobX, React.Context. Of the listed ones, I fell in love with useGlobalHook, so I decided to share with you how I use it, and I hope that this information will be useful to someone.

I do not want to breed holivar among fans of other state managers on the topic “which is cooler.” Just try using useGlobalHook and see how it feels.

Thank you for your attention!

And good luck with your projects.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *