I have a separate basic nodejs server on localhost and a separate frontend.
When I try to fetch while the server is offline, fetch() throws failed to fetch* error. So for that, I tried to handle or catch that error by checking the response. ok like
if (!response.ok) throw Error(response.statusText)
Although it catches an error still it throws the error. Even I tried to console logging it.
if (!response.ok) return console.log(response.statusText)
Here are interfaces.
export interface
CommunicatorParameter {
endUrl: string;
}
interface ErrorResponse {
message: string;
}
interface V01List {
tittle: string;
url: string;
}
type V01ListData = Array<V01List>;
type ReleasedVersionsResponse = Array<string>;
type CurrentVersionResponse = string;
interface SuccessResponse{
data: V01ListData | ReleasedVersionsResponse | CurrentVersionResponse ;
}
export type ServerResponse = SuccessResponse | ErrorResponse;
export type CommunicatorResponse = SuccessResponse['data'];
Below is the service function which helps to communicate with the server. Also, I want this function should return direct data(key) value.
interface SuccessResponse{
data: V01ListData | ReleasedVersionsResponse | CurrentVersionResponse ;
}
// fetchDataFromServer.ts
// these are type and interfaces.
import { CommunicatorParameter, ServerResponse, CommunicatorResponse } from "./declarations/index.js";
// These are helper functions
import { buildValidURL, checkForBadResponse } from "./helpers/index.js";
export async function fetchDataFromServer({ endUrl }: CommunicatorParameter): Promise<CommunicatorResponse> {
const fullURL = buildValidURL(endUrl);
const response = await fetch(fullURL, {
method: 'GET'
});
console.log(response.headers)
const jsonParsedData = <ServerResponse>await response.json();
//This function simply checks response and throws an error if the response is not ok.
checkForBadResponse(response, jsonParsedData);
// Here I am doing this because if I return from try/catch or if statement the return type of function does not match and typescript complain about that. I don't think it is a good way to do it. But don't know how to do it.
return ('data' in jsonParsedData) ? jsonParsedData.data : [];
}
I have another function that will help to fetch version data from the server which uses the above communicator fun.
// fetchCurrentVersion.ts
import { fetchDataFromServer } from "../services/fetchDataFromServer.js";
export async function fetchCurrentListVersion() {
const END_URL = "/current_version";
try {
const response = await fetchDataFromServer({ endUrl: END_URL });
// Here I am checking the type of response/data which is returned by communicator as my need, see the interface SuccessResponse data key.
if (typeof response === 'string') {
return response;
}
} catch (error) {
console.log(error.message)
}
// fallback return value;
return "v0.0";
}
Is this approach good? I felt like still there is a better way of doing this. If you could help, please
Related
So, I searched for an existing solution, but I could find nothing, or maybe I'm not searching the correct way, thus, sorry if there's an existing thread about it.
In sum, it seems my code is not instantiating an object correctly as a class when it comes from an Axios call to the backend. So, when I call some function, I'm getting the error Uncaught TypeError TypeError: object.method is not a function.
Example:
First, basically, a parent component will call a service that will make a request to the backend. The result is then passed to a child component.
// imports
const Component: React.FC<ComponentProps> = () => {
const { id } = useParams<{ id: string }>();
const [object, setObject] = useState<Class>(new Class());
useEffect(() => {
(async () => {
try {
const object = await Service.getById(id);
setObject(object);
} catch (err) {
//error handling
} finally {
cleanup();
}
})();
return () => {
// cleanup
};
});
return (
<Container title={object.name}>
<Child object={object} />
</Container>
);
};
export default Component;
Then, in child component, let's say I try to call a method that was defined in the Class, there I'm getting the not a function error:
// imports
interface Interface {
object: Class;
}
const Child: React.FC<Interface> = ({ object }) => {
object.callSomeFunction(); // error starts here
return (
<SomeJSXCode />
);
};
export default Child;
Example of the Class code, I tried to write the method as a function, arrow function, and a getter, but none worked. Also, as a workaround, I've been defining a method to instantiate the object and set all properties, but I don't think that's a good long-term solution, and for classes with many properties, it gets huge:
export class Class {
id: string = '';
name: string = '';
callSomeFunction = () => {
// do something;
}
static from(object: Class): Class {
const newInstance = new Class();
newInstance.id = object.id;
newInstance.name = object.name;
// imagine doing this for a class with many attributes
return newInstance;
}
}
Finally, the Service code if necessary to better understand:
// imports
const URL = 'http://localhost:8000';
const baseConfig: AxiosRequestConfig = {
baseURL: URL,
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
};
export const backend = axios.create({
...baseConfig,
baseURL: URL + '/someEndpoint',
});
export const Service = {
async getById(id: string): Promise<Class> {
try {
const { data } = await backend.get<Class>(`/${id}`);
return data;
} catch (err) {
throw new Error(err.response.data.message);
}
},
};
As I can't share the real code due to privacy, please let me know if this is enough or if more information is needed. Thanks in advance.
I thought it was some binding issue as here, but no.
So, I actually fixed this by updating the class validator in the back end, as the parsing was only necessary to parse the strings as number. But, by adding the annotation #Type(() => Number) to my dtos, I won't need to parse the strings anymore.
I am very new to typescrypt, and I'm currently in the process of migrating a project from JS to TS.
While I was changing some things on my server endpoints, an error appeared:
Property 'email' does not exist on type 'string | JwtPayload'
This is my code so far:
try {
const token = await TokenController.getTokenFromRequest(req);
const decoded = jwt.decode(token);
const user = await AuthController.getUserByMail(decoded.username); // <- Error appears here
res.json(user);
} catch (error: any) {
ErrorController.errorCallback(error, res);
}
Edit: My code for 'getUserByMail' looks like so:
static async getUserByMail(email: string, includePassword = false) {
let query = User.findOne({
email: email
});
if (!includePassword) {
query.select('-password');
}
return query.exec();
}
I know that the error happens because I'm trying to access a property that string doesn't have. Any suggestions?
Create global.d.ts:
// global.d.ts
declare module "jsonwebtoken" {
export interface JwtPayload {
//make optional so when you try to decode stuff other than user it will show it can be undefined
email?: string;
}
}
I'm using express with typescript. I want to extend my express request interface for that I have done something like this:-
Middleware.ts
import { NextFunction, Request, Response } from 'express';
// eslint-disable-next-line #typescript-eslint/no-var-requires
const configureI18n = require('../helpers/i18n');
const [_, i18nObj] = configureI18n(false);
export interface CRequest extends Request {
i18nObj: any;
}
const langHeadersMiddleware = (
request: CRequest,
response: Response,
next: NextFunction
): void => {
try {
const language = request.headers['accept-language'];
i18nObj.setLocale(language ? language : 'en');
request.i18nObj = i18nObj;
next();
} catch (error) {
i18nObj.setLocale('en');
}
};
export default langHeadersMiddleware;
route.ts
getUserProfile.get(
'/:id',
async (request: CRequest, response: express.Response) => {
try {
const id = request.params.id;
response.json({
err: 0,
message: request.i18nObj.__('MESSAGES.USER_FETCH'),
data
});
} catch (error: any) {
response.json({ err: 1, message: error.message, error });
}
}
);
In this route I'm getting an error:-
No overload matches this call.
The last overload gave the following error.
Argument of type '(request: CRequest, response: express.Response) => Promise' is not assignable to parameter of type 'Application<Record<string, any>>'.
Type '(request: CRequest, response: Response<any, Record<string, any>>) => Promise' is missing the following properties from type 'Application<Record<string, any>>': init, defaultConfiguration, engine, set, and 61 more.
I went through so many blogs, article but everyone is using the same as I did.
you would have to fork and rework the whole express package, which would definitely not recommend :)
But You can:
add your i18n to the request in the middleware as you're doing and just use it with //#ts-ignore above it
add your i18n to the request body in the middleware and just use it
Im using a shared Axios "client" object that is passed around my application via react context, this client object has the API key auth header and base paths already configured so im not constantly defining it.
My problem is trying to use the useSwr hook, specifically when defining the fetcher. I just cannot get it to work, and im sure im missing something simple here.
Basically, I pull the api client off the context, and use a fetcher function iv defined already, but I get nothing.
Here's some snips,
The Client
const AXIOS_CLIENT_CONFIG = {
baseURL: API_BASE,
timeout: 2000,
};
export default class APIClient {
client: AxiosInstance;
accessToken: string;
headers: any;
constructor(accessToken?: string) {
this.accessToken = accessToken;
this.headers = { Authorization: `Bearer ${accessToken}` };
if (accessToken) {
this.client = axios.create({
...AXIOS_CLIENT_CONFIG,
headers: this.headers,
});
} else {
this.client = axios.create(AXIOS_CLIENT_CONFIG);
}
}
fetcher(url: string): Promise<any> {
return this.client.get(url).then((res) => res.data);
}
The Component
export default function Upload(): ReactElement {
const { api }: IAppContext = useContext(AppContext);
const { data, error } = useSwr(`/upload/${uploadId}`, api.fetcher, {
refreshInterval: 5000,
});
Using above, I see nothing, no requests, no errors. (yes, the client comes through fine, I use this throughbout my whole app, its just this fetcher part that is broken)
Just for testing if I define the following fetcher, I can see a request is made (and failed due to auth)
const fetcher = (url) => axios.get(url).then((res) => res.data);
Even logging out the function signatures, they look almost the same to me
console.log("API FETCHER", api.fetcher);
console.log("NORMAL FETCHER", fetcher);
Outputs
API FETCHER ƒ fetcher(url) {
return this.client.get(url).then(function (res) {
return res.data;
});
}
NORMAL FETCHER ƒ fetcher(url) {
return axios__WEBPACK_IMPORTED_MODULE_5___default().get(url).then(function (res) {
return res.data;
});
}
What am I doing wrong here?
After hours of screwing around, I eventually figured this out. Incase anyone else comes across the issue when trying to use an Axios client objection with class functions like I am here.
I had no bound the context of this within the class for that specific function.
Basically, I needed to add the following to my api clients constructor
// Bind "this" context
this.fetcher = this.fetcher.bind(this);
Consider this code:
setContext(async (req, { headers }) => {
const token = await getToken(config.resources.gatewayApi.scopes)
const completeHeader = {
headers: {
...headers,
authorization:
token && token.accessToken ? `Bearer ${token.accessToken}` : '',
} as Express.Request,
}
console.log('accessToken: ', completeHeader.headers.authorization)
return completeHeader
})
Which generates the following TS error:
Property 'authorization' does not exist on type 'Request'.
This comes from trying to access completeHeader.headers.authorization. The property authorization is indeed not available on the Express.request interface. It's strange that TypeScript can't infere the type from the literal object, which clearly is of type string. When not defining the type as Express.Request an error is thrown about an unsafe any assignment.
Is it required to create a new TS interface just for this one field? Or are we using an incorrect type? The field authorization looks to be like a commonly used field for sending tokens.
The reason is because you're coercing completeHeader.headers into the Express.Request type. The coerced type overrides the inferred type.
What you can do, is expand that coerced type by doing the following:
as Express.Request & { authorization: string }
or you could create an entirely new type:
type AuthorizedRequest = Express.Request & { authorization: string };
...
as AuthorizedRequest
in my case, I needed to add user & I got error in headers with authorization(req.headers.authorization), me resolve was:
Case 1:
1.1. Where was error(req.headers.authorization), but before i had got similar error but with user:
import { IAuthRequest } from "./../types/user.type";
const checkAuth =
() => async (req: IAuthRequest, res: Response, next: NextFunction) => {
try {
//2. Second i got error here(main problem)
//i got if, i set <req:IRequestUser> for resolve
//problem with <req.user>: Property 'authorization'
//does not exist on type 'Headers'.
//And you need to change <req: IAuthRequest>, and
//resolve problems
if (!req.headers.authorization) throw new Error("Please log in");
const token = req.headers.authorization.split(" ")[1];
if (!process.env.SECRET_ACCESS_TOKEN)
throw new Error("Please create <SECRET_ACCESS_TOKEN> in .env file");
const { decoded, expired } = Jwt.verifyJwtToken(
token,
process.env.SECRET_ACCESS_TOKEN
);
if (expired) return res.status(401).send("Token has been expired");
//1. first error here
//before(Property 'authorization' does not exist on
//type 'Headers'.) i have got error here(Property
//'user' does not exist on type 'Request'.), if
//<req: Request>, you can try resolve this problem
//<req: IRequestUser> and after this, i got error
//with req.headers.authorization (see <2. Second i
//got error ...>, code above)
req.user = decoded;
next();
} catch (err) {
return res.status(400).send(err);
}
};
1.2. In folder named like "types", i have created file <user.type.ts>
and added:
export interface IUserData {
_id: string;
email: string;
username: string;
}
export interface IRequestUser extends Request {
user: IUserData;
}
export type IAuthRequest = IRequestUser & {
headers: { authorization: string };
};
You need just delete comments and code above will work correctly, comment only for understanding what was in code before error, and how i resolve this problems
Case 2:
after a while I found an even easier way:
import { IAuthRequest } from "./../types/user.type";
const checkAuth =
() => async (req: Request, res: Response, next: NextFunction) => {
try {
req as IAuthRequest;
//your code...
next();
} catch (err) {
return res.status(400).send(err);
}
};
i hope that maybe it will help to someone