I am new to Redux, so any help would be appreciated.
I want to add a variable to my fetch GET request URL inside the action creator.
yourapi.com/getuser/{user1}
I might not be following the correct process, I am very new to redux. I am using NextJS with React-Redux for this project.
My action:
// Get User Object
export const load_user = () => async dispatch => {
try {
const res = await fetch(`/api/getuser`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
});
const data = await res.json();
if (res.status === 200) {
dispatch({
type: LOAD_USER_SUCCESS,
payload: data
});
} else {
dispatch({
type: LOAD_USER_FAIL
});
}
} catch(err) {
dispatch({
type: LOAD_USER_FAIL
});
}
};
That part seems ok.
In this getuser.js file, the action calls (The action creator) how do I append a username variable onto the URL ${API_URL}/GetUser/{username} ?
export default async (req, res) => {
if (req.method === 'GET') {
const username = ??????????
try {
// How to get username???
const apiRes = await fetch(`${API_URL}/GetUser/username`, {
method: 'GET',
headers: {
'Accept': 'application/json',
}
});
const data = await apiRes.json();
if (apiRes.status === 200) {
return res.status(200).json({
user: data
});
} else {
return res.status(apiRes.status).json({
error: data.error
});
}
} catch(err) {
return res.status(500).json({
error: 'Something went wrong when retrieving user'
});
}
} else {
// Error. Not a GET request. They tried POST or PUT etc.
res.setHeader('Allow', ['GET']);
return res.status(405).json({
error: `Method ${req.method} not allowed`
});
}
};
I tried
const user = useSelector(state => state.user)
but I get the error
Invalid hook call error - TypeError: Cannot read properties of null (reading 'useContext')
I am trying to set up authentication for a project. Once a user signs up for our app they get sent to our home page with an id in the query. This id then gets used to submit user and then the jwt token gets saved inside redux state.
All our calls now go through an axios client where the jwt token is passed on every request. The token gets read with store.getState(injectStore)
This all works fine inside getserversideProps, but the issue comes in when using calls on the frontend that goes through NextJs built in 'pages/api' folder. Any calls inside those folders causes the store.getState() to be undefined. I do not understand why since it uses the exact same client as geserversideProps.
Example GetServersideProps(working)
try {
const response = await serverApiClient.get('v1/config');
return {
props: {
},
};
} catch ({ error: { statusCode = 500, message = 'Internal Server Error' } }) {
if (statusCode === 401) {
return {
redirect: {
permanent: false,
destination: '/',
},
};
}
throw new Error(message as string);
}
};
Example Frontend bff call(not working)
try {
// Call below get sent to next built in api
const players = await apiClient.get(`/defenders?sortBy=${statId}&team_id=${teamShortName}`);
return players;
} catch (error) {
return { error };
}
};
export default async function handler(req: NextApiRequest) {
console.log('Start request')
try {
const { sortBy, team_id: teamId } = req.query;
const response = await serverApiClient.get(`/v1/players/picks?position=DEF&sort_by=${sortBy}&team_id=${teamId}`);
Api Client
mergeConfigs(
params: Record<string, string>,
headers: Record<string, string>,
configs: Record<string, string>,
): AxiosRequestConfig {
const defaultConfigs = ApiClient.getDefaultConfigs();
*const token = store?.getState()?.jwtToken?.value*
//ISSUE ABOVE - This store .getState() is only undefined in nextJS api folder calls.
return {
...defaultConfigs,
...configs,
params,
headers: {
...defaultConfigs.headers,
...headers,
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
};
}
get(
uri: string,
params = {},
headers = {},
configs = {},
): Promise<AxiosResponse | any> {
return this.client
.get(uri, this.mergeConfigs(params, headers, configs))
.then((response) => {
return (response.data ? response.data : response);
})
.catch((error) => {
const errorObject = {
error: error?.response?.data,
};
throw Object.assign(errorObject);
});
}
If anyone has some advice on why that getStore is undefined in frontend-to-backend calls please assist. Thanks all!
Here's what I have going:
import 'whatwg-fetch';
function fetchVehicle(id) {
return dispatch => {
return dispatch({
type: 'FETCH_VEHICLE',
payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
.then(status)
.then(res => res.json())
.catch(error => {
throw(error);
})
});
};
}
function status(res) {
if (!res.ok) {
return Promise.reject()
}
return res;
}
EDIT: The promise doesn't get rejected, that's what I'm trying to figure out.
I'm using this fetch polyfill in Redux with redux-promise-middleware.
Fetch promises only reject with a TypeError when a network error occurs. Since 4xx and 5xx responses aren't network errors, there's nothing to catch. You'll need to throw an error yourself to use Promise#catch.
A fetch Response conveniently supplies an ok , which tells you whether the request succeeded. Something like this should do the trick:
fetch(url).then((response) => {
if (response.ok) {
return response.json();
}
throw new Error('Something went wrong');
})
.then((responseJson) => {
// Do something with the response
})
.catch((error) => {
console.log(error)
});
The following login with username and password example shows how to:
Check response.ok
reject if not OK, instead of throw an error
Further process any error hints from server, e.g. validation issues
login() {
const url = "https://example.com/api/users/login";
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify({
email: this.username,
password: this.password,
}),
})
.then((response) => {
// 1. check response.ok
if (response.ok) {
return response.json();
}
return Promise.reject(response); // 2. reject instead of throw
})
.then((json) => {
// all good, token is ready
this.store.commit("token", json.access_token);
})
.catch((response) => {
console.log(response.status, response.statusText);
// 3. get error messages, if any
response.json().then((json: any) => {
console.log(json);
})
});
},
Thanks for the help everyone, rejecting the promise in .catch() solved my issue:
export function fetchVehicle(id) {
return dispatch => {
return dispatch({
type: 'FETCH_VEHICLE',
payload: fetch(`http://swapi.co/api/vehicles/${id}/`)
.then(status)
.then(res => res.json())
.catch(error => {
return Promise.reject()
})
});
};
}
function status(res) {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
}
For me,
fny answers really got it all. since fetch is not throwing error, we need to throw/handle the error ourselves.
Posting my solution with async/await. I think it's more strait forward and readable
Solution 1: Not throwing an error, handle the error ourselves
async _fetch(request) {
const fetchResult = await fetch(request); //Making the req
const result = await fetchResult.json(); // parsing the response
if (fetchResult.ok) {
return result; // return success object
}
const responseError = {
type: 'Error',
message: result.message || 'Something went wrong',
data: result.data || '',
code: result.code || '',
};
const error = new Error();
error.info = responseError;
return (error);
}
Here if we getting an error, we are building an error object, plain JS object and returning it, the con is that we need to handle it outside.
How to use:
const userSaved = await apiCall(data); // calling fetch
if (userSaved instanceof Error) {
debug.log('Failed saving user', userSaved); // handle error
return;
}
debug.log('Success saving user', userSaved); // handle success
Solution 2: Throwing an error, using try/catch
async _fetch(request) {
const fetchResult = await fetch(request);
const result = await fetchResult.json();
if (fetchResult.ok) {
return result;
}
const responseError = {
type: 'Error',
message: result.message || 'Something went wrong',
data: result.data || '',
code: result.code || '',
};
let error = new Error();
error = { ...error, ...responseError };
throw (error);
}
Here we are throwing and error that we created, since Error ctor approve only string, Im creating the plain Error js object, and the use will be:
try {
const userSaved = await apiCall(data); // calling fetch
debug.log('Success saving user', userSaved); // handle success
} catch (e) {
debug.log('Failed saving user', userSaved); // handle error
}
Solution 3: Using customer error
async _fetch(request) {
const fetchResult = await fetch(request);
const result = await fetchResult.json();
if (fetchResult.ok) {
return result;
}
throw new ClassError(result.message, result.data, result.code);
}
And:
class ClassError extends Error {
constructor(message = 'Something went wrong', data = '', code = '') {
super();
this.message = message;
this.data = data;
this.code = code;
}
}
Hope it helped.
2021 TypeScript Answer
What I do is write a fetch wrapper that takes a generic and if the response is ok it will auto .json() and type assert the result, otherwise the wrapper throws the response
export const fetcher = async <T>(input: RequestInfo, init?: RequestInit) => {
const response = await fetch(input, init);
if (!response.ok) {
throw response;
}
return response.json() as Promise<T>;
};
and then I'll catch errors and check if they are an instanceof Response. That way TypeScript knows that error has Response properties such as status statusText body headers etc. and I can apply a custom message for each 4xx 5xx status code.
try {
return await fetcher<LoginResponse>("http://localhost:8080/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "user#example.com", password: "passw0rd" }),
});
} catch (error) {
if (error instanceof Response) {
switch (error.status) {
case 401:
throw new Error("Invalid login credentials");
/* ... */
default:
throw new Error(`Unknown server error occured: ${error.statusText}`);
}
}
throw new Error(`Something went wrong: ${error.message || error}`);
}
and if something like a network error occurs it can be caught outside of the instanceof Response check with a more generic message i.e.
throw new Error(`Something went wrong: ${error.message || error}`);
The answer by #fny (the accepted answer) didn't work for me. The throw new Error() wasn't getting picked up by the .catch. My solution was to wrap the fetch with a function that builds a new promise:
function my_fetch(url, args) {
return new Promise((resolve, reject) => {
fetch(url, args)
.then((response) => {
response.text().then((body) => {
if (response.ok) {
resolve(body)
} else {
reject(body)
}
})
})
.catch((error) => { reject(error) })
})
}
Now every error and non-ok return will be picked up by the .catch method:
my_fetch(url, args)
.then((response) => {
// Do something with the response
})
.catch((error) => {
// Do something with the error
})
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("https://example.com/api/users")
.then(handleErrors)
.then(response => console.log("ok") )
.catch(error => console.log(error) );
I wasn't satisfied with any of the suggested solutions, so I played a bit with Fetch API to find a way to handle both success responses and error responses.
Plan was to get {status: XXX, message: 'a message'} format as a result in both cases.
Note: Success response can contain an empty body. In that case we fallback and use Response.status and Response.statusText to populate resulting response object.
fetch(url)
.then(handleResponse)
.then((responseJson) => {
// Do something with the response
})
.catch((error) => {
console.log(error)
});
export const handleResponse = (res) => {
if (!res.ok) {
return res
.text()
.then(result => JSON.parse(result))
.then(result => Promise.reject({ status: result.status, message: result.message }));
}
return res
.json()
.then(result => Promise.resolve(result))
.catch(() => Promise.resolve({ status: res.status, message: res.statusText }));
};
I just checked the status of the response object:
$promise.then( function successCallback(response) {
console.log(response);
if (response.status === 200) { ... }
});
Hope this helps for me throw Error is not working
function handleErrors(response) {
if (!response.ok) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject({
status: response.status,
statusText: response.statusText,
});
}, 0);
});
}
return response.json();
}
function clickHandler(event) {
const textInput = input.value;
let output;
fetch(`${URL}${encodeURI(textInput)}`)
.then(handleErrors)
.then((json) => {
output = json.contents.translated;
console.log(output);
outputDiv.innerHTML = "<p>" + output + "</p>";
})
.catch((error) => alert(error.statusText));
}
Another (shorter) version that resonates with most answers:
fetch(url)
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(json => doStuff(json)) //all good
//next line is optional
.catch(response => handleError(response)) //handle error
I'm struggling to figure out why the response I get from my API isn't mapping to an object that I have in typescript.
Here's the function in my service that calls the API:
register(user: IUser): Observable<IUser> {
var headers = new Headers();
headers.append('Content-Type', 'application/json');
var options = new RequestOptions({
headers: headers,
url: this._registerUrl,
body: JSON.stringify(user)
});
return this._http.post(this._registerUrl, { user }, options)
.map((res: Response) => res.json() as IUser)
.catch(this.handleError);
}
This is the function that calls the service:
register(): void {
let user: IUser = {
email: this.email,
username: this.username,
password: this.password
}
this._userService.register(user)
.subscribe(result => {
debugger;
if(result.errorCode > 0)
this.handleError(result.errorCode);
else {
localStorage.setItem('userId', result.userId.toString());
localStorage.setItem('username', result.username.toString());
localStorage.setItem('email', result.email.toString());
}
});
}
The object that I am returning from the API matches the object that I have in the frontend. It is returning the data and I can see it in the body of my response. All of the data is right, but it's in the body and is not turning it into an IUser object.
Does anybody have any ideas? Thanks.
EDIT
This is what the response object looks like when it comes back from the service.
I am using aurelia auth for login. But I can't get error message from server. In catch method err.response is undefined. Err is object with body of type Readable stream. Below is my code:
this.auth.login(bodyContent)
.then(response=>{
})
.catch(err=>{
console.log(err);
console.log(err.response);
});
in chrome developer tools I can see response message.
This is err printed:
I found solution here (https://gist.github.com/bryanrsmith/14caed2015b9c54e70c3) and it is the following:
.catch(error => error.json().then(serverError =>
console.log(serverError)
}));
The explanation can be found in the Aurelia docs:
The Fetch API has no convenient way of sending JSON in the body of a request. Objects must be manually serialized to JSON, and the Content-Type header set appropriately. aurelia-fetch-client includes a helper called json for this.
I also came across this same question recently.
I ended up creating a class called FetchError to encapsulate these sorts of errors. I then throw FetchError whenever an error occurs during a fetch.
login.ts:
import { FetchError } from '../../errors';
login() {
var credentials = { grant_type: "password", username: this.username, password: this.password };
return this.auth.login(credentials, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
.then((response) => {
return this.auth;
}).catch(err => {
this.errorMessage = "Login failed";
throw new FetchError("Unable to log in", err);
});
};
The FetchError class uses the 'http-status-codes' node module to look up the textual description.
errors.ts:
import * as HttpStatus from 'http-status-codes';
export class BaseError extends Error {
constructor(message) {
super(message);
this.message = message;
}
}
export class FetchError extends BaseError {
statusCode: number;
statusText: string;
description: string;
constructor(message: string, err: any) {
super(message);
if (err instanceof Response) {
var resp = <Response>err;
this.statusCode = resp.status;
if (resp.status == 12029)
this.statusText = "A connection to server could not be established";
else
this.statusText = HttpStatus.getStatusText(resp.status);
resp.json()
.then(body => {
this.description = body.Message;
console.log(`Error: ${this.message}, Status: ${this.statusText}, Code: ${this.statusCode}, Description: ${this.description}`);
})
}
else if (err instanceof Error) {
var error = <Error>error;
this.description = err.message;
console.log(`Error: ${this.message}, Description: ${this.description}`);
}
else {
this.description = "???";
console.log(`Unknown error: ${this.message}`);
}
}
}
I'm sure there are better ways of doing this. I'm still getting my head around this.