Reputation: 535
When it comes to state centralization I know how to use the context api and Redux. But to recover that state we always have to be inside a react component.
What is the best strategy to access a global state/variable inside a common function that is not inside a react component?
In the environment variables is not an option because this value is changed after the application runs. And I didn't want to put in cookies or local storage for security reasons.
Index.ts
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import apolloClient from './services/apollo';
import { PersonalTokenProvider } from './providers/personal-token';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<PersonalTokenProvider>
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>
</PersonalTokenProvider>
</React.StrictMode>,
document.getElementById('root'),
);
PresonalToken context provider
import React, { useState } from 'react';
interface ProviderProps {
children: JSX.Element[] | JSX.Element;
}
export const PersonalTokenContext = React.createContext({});
export const PersonalTokenProvider: React.FC<ProviderProps> = (
props: ProviderProps,
) => {
const [token, setToken] = useState<string | null>(null);
const { children } = props;
return (
<PersonalTokenContext.Provider value={{ token, setToken }}>
{children}
</PersonalTokenContext.Provider>
);
};
apollo client config
import { useContext } from 'react';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { PersonalTokenContext } from '../providers/personal-token';
//cant do this
const {token} = useContext(PersonalTokenContext)
const httpLink = new HttpLink({
uri: 'https://api.github.com/graphql',
headers: {
authorization: `Bearer ${token}`,
},
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export default client;
Upvotes: 5
Views: 2580
Reputation: 17430
There are multiple ways to simulate a singleton to manage the Apollo client from within React. Here's one way using useRef
to always have the latest token when making GraphQL queries and useMemo
to only create the client once.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
ApolloProvider
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
// The name here doesn't really matters.
export default function CustomApolloProvider(props) {
const { token } = useContext(PersonalTokenContext);
const tokenRef = useRef();
// Whenever the token changes, the component re-renders, thus updating the ref.
tokenRef.current = token;
// Ensure that the client is only created once.
const client = useMemo(() => {
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: tokenRef.current ? `Bearer ${tokenRef.current}` : '',
}
}));
const httpLink = createHttpLink({
uri: 'https://api.github.com/graphql',
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}, [])
return <ApolloProvider client={client} {...props} />;
}
Then in the app:
<PersonalTokenProvider>
<CustomApolloProvider>
<App />
</CustomApolloProvider>
</PersonalTokenProvider>
Pros:
Cons:
The Apollo documentation suggests using the local storage to manage the authentication token.
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; const httpLink = createHttpLink({ uri: '/graphql', }); const authLink = setContext((_, { headers }) => { // get the authentication token from local storage if it exists const token = localStorage.getItem('token'); // return the headers to the context so httpLink can read them return { headers: { ...headers, authorization: token ? `Bearer ${token}` : "", } } }); const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache() });
Pros:
Cons:
Using a simple variable at the root of the module would be enough, you wouldn't even need the token context anymore.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
makeVar
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
// module scoped var for the token:
let token;
// custom module setter:
export const setToken = (newToken) => token = newToken;
const httpLink = createHttpLink({
uri: '/graphql',
});
// Apollo link middleware gets called for every query.
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
));
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Pros:
Cons:
juanireyes suggested Apollo Reactive variables, but they're meant for a particular use-case, which is totally unnecessary to manage the token globally like we want here. It is similar to the module scope variable suggestion above, but with extra steps.
Upvotes: 3
Reputation: 748
If you are trying to use Apollo I would personally encourage you to use the updated library: @apollo/client
. Then you can use Reactive Variables to access the state from multiple places. Then you can try in your provider file something like this to access the token
variable:
import React, { useState } from 'react';
import { makeVar } from '@apollo/client';
interface ProviderProps {
children: JSX.Element[] | JSX.Element;
}
export const tokenVar = makeVar<string | null>(null);
export const PersonalTokenContext = React.createContext({});
export const PersonalTokenProvider: React.FC<ProviderProps> = (
props: ProviderProps,
) => {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
tokenVar(token)
}, [token]);
const { children } = props;
return (
<PersonalTokenContext.Provider value={{ token, setToken }}>
{children}
</PersonalTokenContext.Provider>
);
};
And finally you can access the token value from everywhere calling tokenVar()
or using the useReactiveVar
hook.
Upvotes: 3
Reputation: 34
You can access the content of the Redux store from outside of a component. I know two ways of doing so:
Import the store from the file where you declare it, and access the whole state with the getState method:
import { store } from '../myReduxConfig.js';
const myFunc = () => {
const reduxData = store.getState();
}
If you need the function to run again on redux store changes, import the store from the file where you declare it, and subscribe your function to it:
import { store } from '../myReduxConfig.js';
store.subscribe(myFunc);
const myFunc = () => {
const reduxData = store.getState();
}
Upvotes: -1