localStorage not defined using axios interceptor in NextJS - javascript

I have a file called api.js in my root directory which takes care of calls to the external API (request and response interceptors). I am using this to inject the access_token, so my request interceptor looks like so;
import axios from 'axios';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL
});
// Add request interceptor
api.interceptors.request.use(
async config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
config.headers['Content-Type'] = 'application/json';
config.headers['Accept'] = 'application/json';
return config;
},
error => {
Promise.reject(error);
}
);
In my pages directory, i have a file called users.js ... All i want to do is return the list of users from my external API and display it in a grid when the /users page is loaded. My users.js file currently looks like so;
import api from "../services/api"
export default function Users() {
return (
<div>
</div>
)
}
export async function getStaticProps() {
const res = await api.get('/accounts');
}
But when I run this, i am getting the following error;
ReferenceError: localStorage is not defined
The error references the api.js file at the following line;
const token = localStorage.getItem('access_token');
I cant seem to figure out whats going on. Any help would be greatly appreciated

You are in server-side while executing fetch in getStaticProps() and localStorage doesn't exist in server side.So if you fetch data in client side the localStorage will not be undefined.
getStaticProps — fetches data at build time.
getStaticPaths — pre-render dynamic routes at build time.
getServerSideProps — fetches data on each request.
swr — fetches data from the Client at run time.
If you need to fetch data on the client, you must use swr.
import api from "../services/api"
const fetcher = url => api.get('/accounts'); // .then(res => res.data)
function Users() {
const { data, error } = useSWR('/api/data', fetcher)
// ...
}
Check this for swr.
Check this link for getStaticProps.
Also check this for more information about client side/server side fetch.

Related

TypeError: fs.readFileSync is not a function in Next.js

In a JSON file, I save data for a bot in total.json.
{ "guilds": 3, "users": 21 }
In my index.tsx, I would like to put this data in the page, so I try this:
import fs from 'fs';
function stats() {
const botcount = JSON.parse(fs.readFileSync(`../util/total.json`, { encoding: 'utf8' }));
const userscount = botcount.users;
console.log(userscount);
return userscount;
}
In the terminal, the function correctly returned the number (21), but in my page, I found this error:
TypeError: fs__WEBPACK_IMPORTED_MODULE_6___default(...).readFileSync is not a function
You can only use fs module in Node js NOT in browser. To access JSON data from a file in Nextjs you can use axios or fetch. Here is an example with axios
import axios from 'axios';
async function stats() {
var {data} = await axios.get("http://localhost:8888/utils/total.json");//Change this to your url
const botcount = JSON.parse(data)
const userscount = botcount.users;
console.log(userscount);
return userscount;
}
As #JaivBhup already mentioned, you can't use fs since it's not browser compatible.
A better approach IMO is to use a backend and fetch data from there (axios is a great package for this). If you don't have a backend of some kind, you should consider using the Next.js api routes.
You can use it as if you have been using Node.js!
See the docs or this could also be useful for you.
// File: pages/api/my-json.js
import fs from 'fs'
import path from 'path'
export default (req, res) => {
// Let's say your json is in /public/assets/my-json.json
const filePath = path.resolve('./public', 'assets', 'my-json.json');
const json = fs.readFileSync(filePath);
res.statusCode = 200
res.json(json);
}
The important part is path.resolve(...), which instructs vercel to include the scanned path in the serverless lambda. The shown code works to read images (or other files from your fs) both locally and remotely on vercel!
I tweaked it a little bit, so it loads the json file instead of filenames.

In development mode .env variables are working, however once deployed to netlify they are showing as undefined?

I have used create-react-app to build a web app and put the api keys in the .env file in the root folder of my project.
as shown below...
REACT_APP_EDAMAM_API_KEY=***********
REACT_APP_EDAMAM_APP_ID=*********```
however I'm receiving this error when the site is deployed to netlify...
Type Error: failed to fetch
and in the response header it says both the api_key and the app_id are undefined?
here is the api call
const fetchMyApi = async (searchQuery) => {
const API_KEY = process.env.REACT_APP_EDAMAM_API_KEY;
const APP_ID = process.env.REACT_APP_EDAMAM_APP_ID;
const url = `https://api.edamam.com/search?app_id=${APP_ID}&app_key=${API_KEY}&q=`;
const response = await fetch(url + searchQuery);
const data = await response.json();
return data.hits;
};
export default fetchMyApi;
any help would be much appreciated!

CORS proxy error in react app and express server

I am running a React app with an Express server back-end on port:5000. I am making a request to one of my endpoint using the state of the element to get some data and then render it to the page. I initially setup the proxy in the package.json file of the React app as "proxy": "http://localhost:5000" (documentation. Now it was giving me a proxy error saying cannot proxy to localhost:5000. So I used CORS in my express server as a universal middleware for all of the routes using app.use(cors()).That removed the proxy error but the request is still not working (the code is not even reaching the endpoint because nothing is being logged to the console) and I am pretty sure it's because of the same error. Here are the code snippets
router.get("/", async (req, res) => {
var elements = [];
elements = await Element.find();
res.json(elements);
});
const getElements = async () => {
try {
console.log('getElements call')
const data = await axios.get("/api/elements");
console.log(data);
dispatch({ type: GET_ELEMENTS, payload: data });
} catch (e) {
console.error(e);
}
};
const { getElements, elements, loading } = elementContext;
useEffect(() => {
getElements();
}, [])
Expected behaviour: I want the endpoint to send an array name elements to the call which can then be set to the elements state using the reducer and then can be accessed by destructing in the end. I hope I have given adequate information. Any help will be appreciated. Thank you.

React / Redux and Swagger client

I'm trying to figure out the best way to structure a React / Redux app that will primarily use a swagger client for api access.
The problem is I'm not entirely sure where to store a reference to the swagger client. After logging in and obtaining a JWT auth token, I need to tell all subsequent requests to add the authorize header. With axios this is trivial because it persists it's headers until told otherwise. It doesn't appear the swagger client does this. So ideally, I would create a swagger client once upon login, add the header info and just reference it for all future requests (that way too it only fetches the schema json once in a single page application).
Since I'm doing this in the context of an action, would it be best to store the Swagger client in the Redux store (and how would I accomplish that)? Or would I create a static instance of it outside of Redux?
// app init
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
const store = createStoreWithMiddleware(reducers);
export const swaggerClient = { instance: authService.createFromState().then(() => {
ReactDOM.render(
<Provider store={store}></Provider>
...
);
});
do some login stuff, create swagger client:
// redux action
import { swaggerClient } from '../index';
// ... do login, get bearerToken
Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${bearerToken}`;
return req;
}
}).then((client) => {
// store reference for all future ajax calls
swaggerClient.instance = client;
});
and in case the page is refreshed, we need to rebuild the swagger client from the bearerToken in local storage
// authService
import { swaggerClient } from '../index';
function createFromState() {
// if authentication is known from localstorage, we can rebuild
// a swagger client
if(isAuthenticated()) {
const authentication = getAuthentication();
return Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${authentication.bearerToken}`;
return req;
}
}).then((client) => {
swaggerClient.instance = client;
return client;
});
}
}
I'm a little confused if this is the right direction, probably a newbie question. Having to wait for the swagger client to load while restoring from localstorage seems a kinda crazy way to do this (to prevent race conditions on future calls).

How to pass Request cookies through node-fetch in isomorphic app?

I'm trying to build isomorphic project using React, Express and isomorphic fetch (based on whatwg-fetch on client and node-fetch on server), from this common boilerplate. I'm using cookies for my access token, and credentials: 'same-origin' on front-end side to send it to GraphQL -- works pretty well.
The problem is that I can't use the same solution for server side -- node-fetch just don't support using of XMLHttpRequest cookies from the box. My fetch request is under few abstract layers from router, so I can't just use cookie value from req.
Here is my server.js code (full version):
server.get('*', async (req, res, next) => {
try {
// some presettings here..
await Router.dispatch({ path: req.path, query: req.query, context }, (state, component) => {
data.body = ReactDOM.renderToString(component);
});
res.send(template(data));
} catch (err) {
next(err);
}
});
and Route's index.js (full version):
export const action = async (state) => {
const response = await fetch('/graphql?query={me{id,email}}', {
credentials: 'same-origin',
});
const { data } = await response.json();
// ...
return <Login title={title} me={data.me} />;
};
How can I pass my token from server.js to my fetch module? Or, maybe there are some better decisions?
First off, I hope you have found an answer by now!
Secondly, cookies are really just headers. If you need to send a cookie to authorize server-side requests, you can always just create the string that you need for the cookie value and send it as a header.
For an example, take a look at how this server-side node-fetch wrapper appends saved cookies to the outbound request: https://github.com/valeriangalliat/fetch-cookie/blob/master/index.js#L17

Categories

Resources