Qual é a melhor maneira de acessar o repositório redux fora de um componente de reação?

192

@connectfunciona muito bem quando estou tentando acessar a loja dentro de um componente de reação. Mas como devo acessá-lo em algum outro código? Por exemplo: digamos que eu queira usar um token de autorização para criar minha instância axios que possa ser usada globalmente no meu aplicativo, qual seria a melhor maneira de conseguir isso?

Este é meu api.js

// tooling modules
import axios from 'axios'

// configuration
const api = axios.create()
api.defaults.baseURL = 'http://localhost:5001/api/v1'
api.defaults.headers.common['Authorization'] = 'AUTH_TOKEN' // need the token here
api.defaults.headers.post['Content-Type'] = 'application/json'

export default api

Agora eu quero acessar um ponto de dados da minha loja. Aqui está como isso seria se eu estivesse tentando buscá-lo em um componente de reação usando @connect

// connect to store
@connect((store) => {
  return {
    auth: store.auth
  }
})
export default class App extends Component {
  componentWillMount() {
    // this is how I would get it in my react component
    console.log(this.props.auth.tokens.authorization_token) 
  }
  render() {...}
}

Existem idéias ou padrões de fluxo de trabalho por aí?

Subodh Pareek
fonte
Você não quer que sua instância do Axios viva em um middleware redux? Ele estará disponível por toda a sua aplicação desta maneira
Emrys Myrooin
Você pode importar o apina Appclasse e depois de obter a autorização de token que você pode fazer api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, e ao mesmo tempo você pode armazená-lo em localStorage bem, então quando as atualizações do usuário da página, você pode verificar se o token existe no localStorage e se , você pode configurá-lo., acho que será melhor configurar o token no módulo api assim que você o receber.
Dhruv Kumar Jha
1
Dan Abromov fornece um exemplo na fila questão aqui: github.com/reactjs/redux/issues/916
chrisjlee
1
Se você só precisa acessar um estado em particular a partir de um redutor em particular, pode tentar com redutores de nome redux que permite acessar o estado mais recente de qualquer lugar.
miles_christian

Respostas:

146

Exporte a loja do módulo com o qual você ligou createStore. Então, você tem certeza de que ambos serão criados e não poluirão o espaço global da janela.

MyStore.js

const store = createStore(myReducer);
export store;

ou

const store = createStore(myReducer);
export default store;

MyClient.js

import {store} from './MyStore'
store.dispatch(...)

ou se você usou o padrão

import store from './MyStore'
store.dispatch(...)

Para vários casos de uso de armazenamento

Se você precisar de várias instâncias de uma loja, exporte uma função de fábrica. Eu recomendaria fazê-lo async(retornando a promise).

async function getUserStore (userId) {
   // check if user store exists and return or create it.
}
export getUserStore

No cliente (em um asyncbloco)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
Steven Spungin
fonte
47
AVISO para aplicativos isomórficos: fazer isso do lado do servidor compartilhará o repositório redux entre todos os seus usuários !!!
Lawrence Wagerfield
6
A questão também não indica explicitamente "navegador". Como o Redux e o JavaScript podem ser usados ​​no servidor ou no navegador, é muito mais seguro ser específico.
Lawrence Wagerfield
7
Como a loja de exportação parece criar um pesadelo de importações cíclicas, o createStore inclui seus redutores, que exigem suas ações (pelo menos o tipo de ação enum e interfaces de ação), que não devem importar nada que tente importar a loja. Portanto, você não deve usar a loja em seus redutores ou ações (ou discutir de outra maneira a importação cíclica).
Eloff
6
Esta é a resposta correta, mas se você (como eu) querem ler em vez de envio uma ação na loja, você precisa chamarstore.getState()
Juan José Ramírez
3
Bem, minha interpretação sobre "acessar redux store" foi "ler" a loja. Apenas tentando ajudar os outros.
Juan José Ramírez
47

Encontrei uma solução. Então eu importo a loja no meu api util e assino lá. E nessa função de ouvinte, defino os padrões globais dos axios com meu token recém-buscado.

É assim que meu novo api.jsvisual é:

// tooling modules
import axios from 'axios'

// store
import store from '../store'
store.subscribe(listener)

function select(state) {
  return state.auth.tokens.authentication_token
}

function listener() {
  let token = select(store.getState())
  axios.defaults.headers.common['Authorization'] = token;
}

// configuration
const api = axios.create({
  baseURL: 'http://localhost:5001/api/v1',
  headers: {
    'Content-Type': 'application/json',
  }
})

export default api

Talvez possa ser melhorado ainda mais, porque atualmente parece um pouco deselegante. O que eu poderia fazer mais tarde é adicionar um middleware à minha loja e definir o token naquele momento.

Subodh Pareek
fonte
1
você pode compartilhar a store.jsaparência do seu arquivo?
Vic
exatamente algo que eu estava procurando, graças a @subodh tonelada
Harkirat Saluja
1
Sei que a pergunta é antiga, mas você pode aceitar sua própria resposta como correta. Isso facilita sua resposta para outras pessoas que possam vir aqui.
Filipe Merker
3
Recebi " TypeError : WEBPACK_IMPORTED_MODULE_2__store_js .b está indefinido" quando tento acessar a loja fora de um componente ou função. Alguma ajuda sobre o porquê disso?
ben
21

Você pode usar o storeobjeto retornado da createStorefunção (que já deve ser usada no seu código na inicialização do aplicativo). Você pode usar esse objeto para obter o estado atual com o store.getState()método ou store.subscribe(listener)se inscrever para armazenar atualizações.

Você pode até salvar esse objeto na windowpropriedade para acessá-lo de qualquer parte do aplicativo, se realmente quiser ( window.store = store)

Mais informações podem ser encontradas na documentação do Redux .

gerador de lixo
fonte
19
soa meio hacky para salvar storeawindow
Vic
3
@ Vic Claro que é :) E normalmente você não quer fazê-lo. Eu só queria mencionar que você pode fazer o que quiser com essa variável. Provavelmente, a melhor idéia seria armazená-lo no arquivo em que você criou sua vida "createStore" e depois apenas importar.
trashgenerator
Eu tenho vários iframes que precisam acessar o estado dos outros iframes. Eu sei que é meio hacky, mas acho que ter a loja na vitrine seria melhor do que usar mensagens para iframes. Alguma ideia?? @Vic @trashgenerator?
Sean Malter
10

Como o @sanchit, o middleware proposto é uma boa solução se você já estiver definindo sua instância do axios globalmente.

Você pode criar um middleware como:

function createAxiosAuthMiddleware() {
  return ({ getState }) => next => (action) => {
    const { token } = getState().authentication;
    global.axios.defaults.headers.common.Authorization = token ? `Bearer ${token}` : null;

    return next(action);
  };
}

const axiosAuth = createAxiosAuthMiddleware();

export default axiosAuth;

E use-o assim:

import { createStore, applyMiddleware } from 'redux';
const store = createStore(reducer, applyMiddleware(axiosAuth))

Ele definirá o token em todas as ações, mas você só poderá ouvir ações que alterem o token, por exemplo.

erksch
fonte
como você pode conseguir o mesmo com uma instância de axios personalizada?
Anatol
3

Para o TypeScript 2.0, seria assim:

MyStore.ts

export namespace Store {

    export type Login = { isLoggedIn: boolean }

    export type All = {
        login: Login
    }
}

import { reducers } from '../Reducers'
import * as Redux from 'redux'

const reduxStore: Redux.Store<Store.All> = Redux.createStore(reducers)

export default reduxStore;

MyClient.tsx

import reduxStore from "../Store";
{reduxStore.dispatch(...)}
Ogglas
fonte
3
Se você estiver votando para baixo, adicione um comentário.
Ogglas
3
Votado com baixa porque sua resposta não tem explicação e usa TypeScript em vez de Javascript.
Will
2
@ Will Obrigado por dizer o porquê. Imao o código não requer especificação, mas se você gostaria de algo específico explicado, por favor diga o que. O TypeScript é realmente usado, mas se as digitações forem removidas, o mesmo código será executado no ES6 sem problemas.
Ogglas
Lembre-se de que isso será muito ruim se você estiver processando o lado do servidor, ele compartilhará o estado com todas as solicitações.
Rob Evans
2

Pode ser um pouco tarde, mas acho que a melhor maneira é usar axios.interceptors como abaixo. Os URLs de importação podem mudar com base na configuração do seu projeto.

index.js

import axios from 'axios';
import setupAxios from './redux/setupAxios';
import store from './redux/store';

// some other codes

setupAxios(axios, store);

setupAxios.js

export default function setupAxios(axios, store) {
    axios.interceptors.request.use(
        (config) => {
            const {
                auth: { tokens: { authorization_token } },
            } = store.getState();

            if (authorization_token) {
                config.headers.Authorization = `Bearer ${authorization_token}`;
            }

            return config;
        },
       (err) => Promise.reject(err)
    );
}
S. Mert
fonte
1

Fazendo isso com ganchos. Eu tive um problema semelhante, mas estava usando o react-redux com ganchos. Eu não queria restringir meu código de interface (por exemplo, reagir componentes) com muito código dedicado à recuperação / envio de informações de / para a loja. Em vez disso, eu queria funções com nomes genéricos para recuperar e atualizar os dados. Meu caminho foi colocar o aplicativo

const store = createSore(
   allReducers,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 );

em um módulo nomeado store.jse adicionando exportantes conste adicionando as importações usuais do react-redux no store.js. Arquivo. Em seguida, importei para index.jso nível do aplicativo, que depois importei para o index.js com o habitualimport {store} from "./store.js" Os componentes filho acessaram a loja usando os useSelector()botõesuseDispatch() ganchos .

Para acessar a loja no código de front-end sem componente, usei a importação análoga (ou seja import {store} from "../../store.js") e, em seguida, usei store.getState()e store.dispatch({*action goes here*})manipulei a recuperação e atualização (er, enviando ações para) a loja.

Charles Goodwin
fonte
0

Uma maneira fácil de acessar o token é colocar o token no LocalStorage ou no AsyncStorage com React Native.

Abaixo um exemplo com um projeto React Native

authReducer.js

import { AsyncStorage } from 'react-native';
...
const auth = (state = initialState, action) => {
  switch (action.type) {
    case SUCCESS_LOGIN:
      AsyncStorage.setItem('token', action.payload.token);
      return {
        ...state,
        ...action.payload,
      };
    case REQUEST_LOGOUT:
      AsyncStorage.removeItem('token');
      return {};
    default:
      return state;
  }
};
...

e api.js

import axios from 'axios';
import { AsyncStorage } from 'react-native';

const defaultHeaders = {
  'Content-Type': 'application/json',
};

const config = {
  ...
};

const request = axios.create(config);

const protectedRequest = options => {
  return AsyncStorage.getItem('token').then(token => {
    if (token) {
      return request({
        headers: {
          ...defaultHeaders,
          Authorization: `Bearer ${token}`,
        },
        ...options,
      });
    }
    return new Error('NO_TOKEN_SET');
  });
};

export { request, protectedRequest };

Para a Web, você pode usar em Window.localStoragevez do AsyncStorage

Denis Zheng
fonte