Chega um momento no estudo do React em que devemos enfrentar o Redux. Quando eu lidei com isso da primeira vez foi difícil e eu fiquei um tempo confuso, mas com o tempo a sua estrutura foi ficando mais clara, então eu decidi, como material de estudo, criar um esquema. Aos poucos vou melhorando esse esquema para torná-lo mais prático, e acredito que ele possa ajudar outros estudantes do assunto, assim resolvi publicá-lo aqui, seguido de explicações:

<iframe style="border: 1px solid rgba(0, 0, 0, 0.1);" width="800" height="450" src="https://www.figma.com/embed?embed_host=share&url=https%3A%2F%2Fwww.figma.com%2Ffile%2FUWtybjLYUS9pRVJvDIWkgP%2FRedux%3Fnode-id%3D0%3A1" allowfullscreen></iframe>

link para imagem mais visível

Agora, vamos às explicações.

Para começar, é preciso importar as bibliotecas do redux:

é só mandar um

yarn add redux react-redux redux-thunk redux-devtools-extension
  1. Estrutura de pastas

    uma boa estrutura inicial de pastas para o Redux seria a seguinte:

    redux
    ├── types
    │   └── index.js
    ├── actions
    │   └── index.js
    ├── thunk
    │   └── index.js
    ├── reducers
    │   └── index.js
    └── store
    		└── index.js
    

    Essa é apenas uma proposta, e há quem não utilize a pasta store, preferindo utilizar o arquivo index.js direto da pasta redux, por exemplo, ou use os thunks junto das actions...

    Faça a sua escolha! Busque a estrutura que lhe parecer mais lógica.

  2. Responsabilidades

  3. Estrutura mais complexa

    Nosso exemplo acima é bem simples, mas aplicações volumosas podem ter várias actions, thunks e reducers, por exemplo para usuários (cadastro, login, alteração de dados...) e produtos no carrinho (adicionar, remover, limpar...) ou outros tipos de dados (mensagens entre usuários, chat com robô para tirar dúvidas, etc). Nesse caso, cada arquivo terá seus dados correspondentes, por exemplo:

    ├── reducers
    │  ├── index.js
    │  ├── users.js
    │  ├── products.js
    │  └── messages.js
    

    Nesse caso, seu index.js deve fazer a junção desses reducers (com as actions e thunks você não precisa se preocupar, pois eles são disponibilizados via import, então a estrutura de pastas não altera sua funcionalidade):

    
    // reducers/index.js
    import { combineReducers } from 'redux';
    // aqui importamos todos os reducers:
    import users from './users';
    import products from './products';
    import messages from './messages';
    
    const store = combineReducers({ products, users, messages });
    export default store;
    
  4. Pondo para funcionar

    Para que a estrutura acima funcione, temos que disponibilizá-la globalmente em nossa aplicação. fazemos isso no arquivo App.jsx:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    import { BrowserRouter } from 'react-router-dom';
    import { Provider } from 'react-redux';
    import store from './redux/store';
    
    ReactDOM.render(
      <React.StrictMode>
        <Provider store={store}>
          <BrowserRouter>
            <App />
          </BrowserRouter>
        </Provider>
      </React.StrictMode>,
      document.getElementById('root')
    );
    

    O componente Provider recebe nosso store e o disponibiliza para tudo o que estiver contido dentro do componente App - isto é, toda nossa aplicação.

    N.B.: Se sua aplicação tem uma estrutura simples, com apenas um reducer, você pode exportar seu único reducer e passá-lo para o Provider como valor de store, pulando o item 3 acima.

  5. Usando e atualizando o estado no redux

    Uma vez estruturado o estado no seu redux, agora você pode acessá-lo de qualquer lugar da sua aplicação, ou atualizá-lo, por meio dos hooks.

    import { useSelector, useDispatch } from 'react-redux'
    import { addProduct } from '../redux/thunk'
    import { reset } from '../redux/actions'
    
    const myComponent = () => {
    	// vc tem que instanciar o useDispatch dentro do seu componente:
    	const dispatch = useDispatch()
    	
    	const newProduct = {id: 5, name: 'flipflops', size: 45, gift: true }
    	
    	// atualizar os dados
    	dispatch(addProduct(newProduct))
    	
    	// recuperar dados do redux
    	const myList = useSelector(state => state.productList)
    
    ...
    
    	return (
    		...
    		<button onClick={() => dispatch(reset())}> Limpar carrinho </button>
    
    	)
    

    Pronto! Nada de dados passando por props na lógica do nosso carrinho de compras.

    O uso do Redux não nos priva de utilizarmos useState ou passar dados por props, mas agora esses recursos ficam circunscritos à lógica interna do componente. A parte pesada da manipulação do estado, com dados que precisam ser acessados de páginas e rotas distintas, é muito bem administrada pelo Redux.

    Existem outros hooks e outras formas de fazer uma estrutura parecida com a descrita acima; a documentação do Redux pode ser consultada em https://redux.js.org/, e a do Thunk em https://www.npmjs.com/package/redux-thunk.

    Para debugar o redux, eu inseri acima um código que permite a comunicação da sua aplicação com o redux dev tools. Veja a documentação em https://github.com/zalmoxisus/redux-devtools-extension

Algumas considerações: