Make store values available outside React Redux components - reactjs

I'm currently building a react-native application that uses react-redux to manage state. I'm attempting to dedup some authentication related logic by putting it into a service that isn't an extension of React's Component class. When a user logs in to the app, they are returned a user_token to validate subsequent API calls to the server. This token is stored in the auth reducer, so it would be available in a call of mapStateToProps via state.auth.user_token. I've defined a function in src/utils/fetch.js that performs API calls for me:
export function fetchAPI(endpoint, method = 'GET', data) {
let url = 'http:localhost:3000/api/' + endpoint;
let headers = { 'User-Token': 'XXXXXX' };
if (method === 'POST') {
headers['content-type'] = 'application/json';
}
let options = {
method: method,
headers: headers,
mode: 'cors' // no-cors, cors, *same-origin
};
if (data) {
options.body = JSON.stringify(data);
}
return fetch(url, options).then((response) => {
...
I'm wondering if there is a graceful / normal way of making this utility service aware of the values inside of state. I don't want to explicitly extract that property in every component that makes authenticated API calls - I'd basically have to do it everywhere and it feels redundant and verbose. Changing the method signature to accept this parameter feels bad:
export function fetchAPI(user_token, endpoint, method = 'GET', data) {
^^^^^^^^^^ doesn't change while someone is logged in
But something like this feels better:
import storeProvider from './storeProvider';
export function fetchAPI(user_token, endpoint, method = 'GET', data) {
const user_token = storeProvider.getUserToken();
... (go do great stuff with user_token)
Is there a conventional way to do this? Has anyone seen this sort of extracting of state into something that isn't a Container?

After you create the store export it. Import the store to the fetchAPI. Then use store.getState(), and extract the user token from the state.
Example:
// you'll to export the store after you use create store
import store from './store';
// this selector can be used across the app
const getUserToken = ({ auth }) => auth.user_token;
export function fetchAPI(user_token, endpoint, method = 'GET', data) {
const user_token = getUserToken(store.getState());
... (go do great stuff with user_token)

Related

Should I store a Axios instance configured with interceptors that use a state value in a state or ref or useMemo?

Similiar to Should we use axios inside both components and store?
I have a state called authorization which contains the Bearer token value that would be used in Axios calls. The state is available in a context and accessible using the useContext hook.
I create the AxiosInstance where I add a interceptors.request.use to add the Authorization header.
What I've done so far was useMemo with the authorization value a a dependency. But since Axios operation is asynchronous it seems that I may get the wrong axios instance.
I did a bit of refactoring using useRef and I still had a bit of an issue.
What I then did was implement the Observer pattern and send a notification to the component that provides the Axios client that the authorization header was changed and update the ref. However, again there's still some cases where the old client is being invoked.
What I am wondering is should I store it in useState or is there a fundamental problem with the approach of storing the Axios client and instead should I just bite the bullet and create a new axios client per request which takes the authorization header that's presently in the state?
The way I typically do it is to save the authentication information in a React context or to redux, and create axios instances as needed to access the authorization token.
Maybe something like:
const getBearerToken() => { ... [your implementation to retrieve from context here] ... };
const webserviceRequest = (url) => () => axios.create({
baseURL: url,
... [AxiosRequestConfig options here] ...
headers: {
Authorization: `Bearer ${getBearerToken()}`,
...
},
});
Then, you can define your webservice requests by invoking this function, e.g.:
const sampleGetRequest = () => webserviceRequest(SAMPLE_URL)().get('');
const samplePostRequest = (postData) => webserviceRequest(SAMPLE_URL)().post('', postData);
These return a Promise<AxiosResponse> which you can call as normal, e.g.
sampleGetRequest().then((response) => { ... }).catch((error) => { ... })
and so on.
The key point is that the webserviceRequest returns a function which creates an asynchronous webservice request with the current authorization token. You don't ever save the webserviceRequest function because that authorization token can become stale.
With the dependency on React context, I'd avoid using interceptors for this (I'd avoid using Axios all together but that's just my opinion).
Instead, try creating a custom hook
import axios from "axios";
import { useContext } from "react";
const api = axios.create({ /* baseURL, etc */ });
const useApi = () => {
const authorization = useContext(AuthContext); // guessing
if (authorization) {
api.defaults.headers.common.Authorization = `Bearer ${authorization}`;
} else {
delete api.defaults.headers.common.Authorization;
}
return api;
};

nextAuth unstable_getsession and getToken returning null not working correctly [duplicate]

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.

react-query: useQuery returns undefined and component does not rerender

I'm playing around with reactQuery in a little demo app you can see in this repo. The app calls this mock API.
I'm stuck on a an issue where I'm using the useQuery hook to call this function in a product API file:
export const getAllProducts = async (): Promise<Product[]> => {
const productEndPoint = 'http://localhost:5000/api/product';
const { data } = await axios.get(productEndPoint);
return data as Array<Product>;
};
In my ProductTable component I then call this function using:
const { data } = useQuery('products', getAllProducts);
I'm finding the call to the API does get made, and the data is returned. but the table in the grid is always empty.
If I debug I'm seeing the data object returned by useQuery is undefined.
The web request does successfully complete and I can see the data being returned in the network tab under requests in the browser.
I'm suspecting its the way the getAllProducts is structured perhaps or an async await issue but can't quite figure it out.
Can anyone suggest where IO may be going wrong please?
Simply use like this
At first data is undefined so mapping undefined data gives you a error so we have to use isLoading and if isLoading is true we wont render or map data till then and after isLoading becomes false then we can render or return data.
export const getAllProducts = async (): Promise<Product[]> => {
const productEndPoint = 'http://localhost:5000/api/product';
const res= await axios.get(productEndPoint);
return res.data as Array<Product>;
};
const { data:products , isLoading } = useQuery('products', getAllProducts);
if(isLoading){
return <FallBackView />
}
return (){
products.map(item => item)
}
I have managed to get this working. For the benefits of others ill share my learnings:
I made a few small changes starting with my api function. Changing the function to the following:
export const getAllProducts = async (): Promise<Product[]> => {
const response = await axios.get(`api/product`, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
return response.data as Product[];
};
I do not de-construct the response of the axios call but rather take the data object from it and return is as an Product[]
Then second thing I then changed was in my ProductTable component. Here I told useQuery which type of response to expect by changing the call to :
const { data } = useQuery<Product[], Error>('products', getAllProducts);
Lastly, a rookie mistake on my part: because I was using a mock api in a docker container running on localhost and calling it using http://localhost:5000/api/product I was getting the all to well known network error:
localhost has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present...
So to get around that for the purpose of this exercise I just added a property to the packages.json file: "proxy":"http://localhost:5000",
This has now successfully allowed fetching of the data as I would expect it.

Axios - New Instance overrides Global Defaults

quick question on implementation of multiple axios instances.
I call my own API as needed inside my mobile app. Certain API endpoints are protected by checking that the token issued by Google Signin or Facebook Signin is authenticated and matches the expected user. Other endpoints do not require a token and actually require the frontend to not send a token at all to proceed down the proper logic path on the backend.
I set the global axios instance when I fetch the user token on frontend. I am hoping that all axios calls include the token by default. To achieve this, I set the default Authorization header as such:
import axios from 'axios'
...
axios.defaults.headers.common.Authorization = `Bearer ${fbUserInfo.accessToken}`
This code runs on the initial page of the app that is loaded, so everytime I use axios somewhere else in the app, the token is now included. It works as expected until I proceed to the following step.
In order to create some recurring API calls where a token should not be included, I create a "tokenless" instance inside one of my redux action creators.
const axiosTokenlessInstance = axios.create()
...
axiosTokenlessInstance.defaults.headers.common.Authorization = false
This does indeed override the default axios settings when I call it directly, however, I've realized that it also has overridden my default settings when I just call axios directly again.
I expected the behavior to be that I could still call axios.post or axios.get elsewhere and it would still include the token. Was I mistaken and do I have to create a "tokened" instance? I'd prefer not to do this as I'd have to go through and replaced tokened instance explicitly everywhere rather than just using axios default. Thanks!
Try creating a file called setAuthToken.js that handles the auth instance separately. This way any time you need to use the auth, you can just call this function ( I would even suggest limiting to calling this auth only once and having the token saved in the users localStorage):
import axios from 'axios';
const setAuthToken = token => {
if(token){
axios.defaults.headers.common['x-auth-token'] = token;
}
else{
delete axios.defaults.headers.common['x-auth-token'];
}
}
export default setAuthToken;
Now let's say you have a backend function call that needs auth verification, you can do it like so:
import setAuthToken from "../utils/setAuthToken";
export const loginUser = (token) => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOGGED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
Specifying the blank header Authorization inside axios.create() fixes the issue and doesn't override the global axios headers.
So this works as expected:
const axiosTokenlessInstance = axios.create({
baseURL: Config.API_HOST,
headers: { Authorization: '' },
})
While this overrides global axios settings, not just affecting the specified instance:
const axiosTokenlessInstance = axios.create()
axiosTokenlessInstance.defaults.headers.common.Authorization = false

React + axios + redux interceptors

I have an React + redux + axios app, white jwt authentication. So, i need to intercept every request to server and set header with token. The question is where I have to set this headers, or implement interceptors. (also, i need redux store in scope, to get tokens from store). My idea - implement it in the Index component. Is it right way?
I suggest you to set the header axios.defaults.headers.common.authorization. Take a look here Global axios defaults. If you need a working example, this public repo can help you out.
Why do you have to manually set the header. Can't you just store the JWT in a cookie and then the browser will forward it automatically for you. Just make sure you pass credentials: 'include' in your HTTP options.
create a redux-middleware to do these things.
Apart from acting like interceptor to add header token,
you also do request/response transformation.
Also,you can mention the next action to which you want to dispatch the result if you don't want to return the promise and result.
Middleware gives you a chance to get the store state and also fetch & dispatch other action
const apiInterceptor = store => next => action => {
if(action.type !== 'ajax') return next(action)
const state = store.getState();
state.token // get token here
//add this api check part in some other module.
if(action.api=='UPDATE_CLIENT')
{
method = 'post';
url = 'some url';
}
else
{
method = 'get';
url = 'other url';
}
fetch(url, {
method: method,
headers : 'add token here',
body: JSON.stringify(action.body())
})
.then(response => response.json())
.then(json => json)//either return result
//OR dispatch the result
.then(json => {
dispatch({type:action.next_action,payload : json})
})
}
Integrate the middleware with store.
import customMiddleware from './customMiddleware'
const store = createStore(
reducer,
applyMiddleware(customMiddleware)
)
I offer you to refer redux-thunk.
You must create api-wrapper-helper to inject to redux-thunk as extra argument, then access to api-wrapper-helper from redux actions.
api-wrapper-helper is a function that get url and method as argument and send request to api-server then return result. (you can set headers in this file)
For example you can see ApiClient.js of react-redux-universal-hot-example boilerplate.
This is an old post but its getting a few views, so something like this would work nicely and is easily testable.
apiclient.js
import axios from 'axios';
import { setRequestHeadersInterceptor } from './interceptors';
const apiClient = axios.create(
{
baseUrl: 'https://my.api.com',
timeout: 3000
});
apiClient.interceptors.request.use(setRequestHeadersInterceptor);
export default apiClient;
interceptors.js
export const setRequestHeadersInterceptor = config =>
{
// have each interceptor do one thing - Single Responsibility Principle
};
you should store your auth details in a httpOnly secure cookie, the transmission to/from the server will be automatic then
// Interceptor
axios.interceptors.response.use(function (response) {
// success case here
return response
}, function (error) {
// Global Error Handling here
// showErrorToaster(error['message']);
return Promise.reject(error.response.data)
})

Resources