How to update state with an error message in React? - javascript

Hi I was trying to update state depending on the error message caught. My code is:
const [errors,setErrors] = useState("Blah blah");
const verifyIfObject = (object) => {
try {
if (object === "object") {
return true;
} else {
const errMsg = `Error: ${object} is not a js object...`;
throw errMsg;
}
}
catch (e) {
console.log(e);
setErrors(e);
}
};
When executing the code with error I get the error message in console:
"Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."
Please advise how to change the code so that error messages can be stored in state

a try/catch block needs to be used in an async function. You may simplify your function like this:
const [errors,setErrors] = useState("Blah blah");
const verifyIfObject = (object) => {
if (object !== "object") {
setErrors(`Error: ${object} is not a js object...`);
return false
}
return true
};

Related

JavaScript: differences between async error handling with async/await and then/catch

Just wanted to preemptively say that I am familiar with async/await and promises in JavaScript so no need to link me to some MDN pages for that.
I have a function to fetch user details and display it on the UI.
async function someHttpCall() {
throw 'someHttpCall error'
}
async function fetchUserDetails() {
throw 'fetchUserDetails error'
}
function displayUserDetails(userDetails) {
console.log('userDetails:', userDetails)
}
async function fetchUser() {
try {
const user = await someHttpCall()
try {
const details = await fetchUserDetails(user)
returndisplayUserDetails(details)
} catch (fetchUserDetailsError) {
console.log('fetching user error', fetchUserDetailsError)
}
} catch (someHttpCallError) {
console.log('networking error:', someHttpCallError)
}
}
It first makes HTTP call via someHttpCall and if it succeeds then it proceeds to fetchUserDetails and it that succeeds as well then we display the details on Ui via returndisplayUserDetails.
If someHttpCall failed, we will stop and not make fetchUserDetails call. In other words, we want to separate the error handling for someHttpCall and it’s data handling from fetchUserDetails
The function I wrote is with nested try catch blocks which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability using plain then and catch
This was my first atttempt
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
The problem with this is that the second then will run i.e. displayUserDetails even with someHttpCall failing. To avoid this I had to make the previous .catch blocks throw
so this is the updated version
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
However now the second catch will get called as a result of the throw. So when the someHttpCall failed, after we handled the someHttpCallError error, we would enter this block (fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) } which is not good since fetchUserDetails never gets called so we shouldn't need to handle fetchUserDetailsError (I know someHttpCallError became fetchUserDetailsError in this case)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal. So I am wondering how I can improve this by using .then and .catch to achieve the same goal here.
I am wondering how I can improve this by using .then and .catch to achieve the same goal here
You don't get to avoid the nesting if you want to replicate the same behaviour:
function fetchUser2() {
return someHttpCall().then(
(user) => {
return fetchUserDetails(user).then(
(details) => {
return displayUserDetails(details)
},
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
},
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
}
(The exact equivalent to try/catch would use .then(…).catch(…) instead of .then(…, …), but you might not actually want that.)
The function I wrote is [nested] which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability […]
For that, I would recommend to combine await with .catch():
async function fetchUser() {
try {
const user = await someHttpCall().catch(someHttpCallError => {
throw new Error('networking error', {cause: someHttpCallError});
});
const details = await fetchUserDetails(user).catch(fetchUserDetailsError => {
throw new Error('fetching user error', {cause: fetchUserDetailsError});
});
return displayUserDetails(details);
} catch (someError) {
console.log(someError.message, someError.cause);
}
}
(The cause option for Error is still quite new, you might need a polyfill for that)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.
Actually, that sounds like an ideal situation. That means that you don't have to nest any try / catch blocks which could make you code a lot more readable. This is one of the things that async / await is meant to solve.
A solution could be is to create custom errors by extending the Error interface to be able to determine how and where the error occurs.
class CustomError extends Error {
constructor(name, ...args) {
super(...args)
this.name = name
}
}
Throw your errors within the functions that correspond with the error.
async function someHttpCall() {
throw new CustomError('HttpCallError', 'someHttpCall error');
}
async function fetchUserDetails(user) {
throw new CustomError('UserDetailsError', 'fetchUserDetails error')
}
Now you can control your error flow by checking the name property on the error to differentiate your errors.
async function fetchUser() {
try {
const user = await someHttpCall()
const details = await fetchUserDetails(user)
return displayUserDetails(details)
} catch (error) {
switch(error.name) {
case 'HttpCallError':
console.log('Networking error:', error)
break
case 'UserDetailsError':
console.log('Fetching user error', error)
break
}
}
}
I've been inspired by Rust's Result type (which forces you to handle every potential error along the way).
So what I do is handle exceptions in every individual function, and never allow one to throw, instead returning either an Error (if something went wrong) or the desired return value (if no exception occurred). Here's an example of how I do it (comments included):
TS Playground
If you aren't familiar with TypeScript, you can see the JavaScript-only version of the following code (with no type information) at the TypeScript Playground link above (on the right side of the page).
// This is the code in my exception-handling utility module:
// exception-utils.ts
export type Result <T = void, E extends Error = Error> = T | E;
export function getError (value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));
}
export function isError <T>(value: T): value is T & Error {
return value instanceof Error;
}
export function assertNotError <T>(value: T): asserts value is Exclude<T, Error> {
if (value instanceof Error) throw value;
}
// This is how to use it:
// main.ts
import {assertNotError, getError, isError, type Result} from './exception-utils.ts';
/**
* Returns either Error or string ID,
* but won't throw because it catches exceptions internally
*/
declare function getStringFromAPI1 (): Promise<Result<string>>;
/**
* Requires ID from API1. Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*/
declare function getNumberFromAPI2 (id: string): Promise<Result<number>>;
/**
* Create version of second function with no parameter required:
* Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*
* The previous two functions work just like this, using the utilities
*/
async function fetchValueFromAPI2 (): Promise<Result<number>> {
try {
const id = await getStringFromAPI1(); // Error or string
assertNotError(id); // throws if `id` is an Error
return getNumberFromAPI2(id); // Error or number
}
catch (ex) {
return getError(ex);
}
}
async function doSomethingWithValueFromAPI2 (): Promise<void> {
const value = await fetchValueFromAPI2(); // value is number or Error
if (isError(value)) {
// handle error
}
else console.log(value); // value is number at this point
}

There is a way to return an error inside catch without throw a new one?

I have a custom error that I call inside try. And I want to return this error inside catch without throw a new one.
const callSomething = async () => {
try {
doSomething();
} catch (error) {
// This function receive the error with the additional properties, so we need the custom error object
customErrorTreatment(error);
}
};
This function is where the error is first call.
const doSomething = async () => {
try {
// This function throw a custom error class with additional properties
throwApiError({
responseMessage: 'Some error occour',
responseStatus: 500,
});
} catch (error) {
// If I return error, the function callSomething just receive the value without error.
return error;
// I can call throwApiError again, but feels ugly, that is the only way?
return throwApiError({
responseMessage: error.responseMessage
responseStatus: error.status,
});
}
};
This is the custom error class and function
export const ApiError = class ApiError extends Error {
constructor({ responseMessage, responseStatus, error }) {
super(error);
this.responseMessage = responseMessage;
this.responseStatus = responseStatus;
}
};
const throwApiError = ({ responseMessage, responseStatus, error }) => {
throw new ApiError({ responseMessage, responseStatus});
};
Don't call throwApiError() again. Just throw error if you want the promise to stay rejected - that's how promises work.
Or get rid of your catch() handler entirely so the error just propagates naturally back up to a higher level without your intervention. You don't appear to be doing anything in the catch handler so perhaps you can just remove it.
In short No, because to generate an error you need to throw, and your method is a common method for error handling. but there is another way to manage errors like this:
const callSomething = async () => {
let { result, error } = resdoSomething();
if (error) {
return throwApiError({
responseMessage: error.responseMessage
responseStatus: error.status,
});
}
console.log(result)
// do somethings
};
and
const doSomething = async () => {
try {
let result = myfunction()
return {result: result , error : null}
} catch (error) {
return {result : null, error: error};
}
};
In this way you can reduce the number of try/catch

How can I return an error from a function?

Let's say I have a function like this:
const getPlayer = (id) => {
return players[id;]
}
//--------------------------
const client = getPlayer(9);
How can I return the err parameter to the client variable if no player is found? For example:
if (client.err) {
//do something
}
I tried passing the error via throw new Error('my error') , but the function still doesn't get it, what am I doing wrong?:(
So your first instinct was correct, you should use the 'throw' keyword to raise an error. To act on the error you need to use try/catch like I've done below.
const getPlayer = (id) => {
if(id in players) {
return players[id];
}
throw new Error("Oh noes...!");
}
try {
const client = getPlayer(9);
} catch(error) {
console.log(error.message);
}
When an error is thrown inside a function being executed in a try block, execution immediately jumps to the catch block, allowing you to respond to the error appropriately.
Checkout try/catch syntax for that.
For example:
const getPlayer = (id) => {
if (!id) {
throw new Error('no id provided');
}
return players[id]
}
To get this "error" state, when it triggers you can do following:
try {
const client = getPlayer(null);
} catch(error) {
console.log(error.message);
}
I have tried something but not sure if this is what you are after:
let a = (x) => {
if (x == 0) {
throw new Error("Votes are zero");
} else {
return x;
}
};
Run it in the console with the values as a(0) --> will throw you a new error and a(5)

Pattern: How to access object.properties (correctly) from result returned by async function

I have a function that fetch data from DB or API point and add properties to it, like this:
async function function_one (arg) {
try {
if (arg != number) throw new Error('error')
let data = await findOne(arg);
data['property'] = 1+2+3
....
return data //this is an object with it's own properties
} catch (e) {
console.log(e); //errorHandling with winston
}
}
and another (master) function that use data from previous function(s):
async function Master (user_input) {
try {
let object = await function_one(user_input);
console.log(object.property_one) //weak warning
let another_object = await another_function (fixed_arg);
//some other logic with object properties.
return result_data
} catch (e) {
console.log(e);
}
}
So when I'm trying to access object properties in Master function like:
let object = await function_one(user_input);
console.log(object.property_one)
My IDE (WebStrom) shows something like that: but I know that if function_one will execute correctly (w/o catch block) this property would exist. And if Master function will fail, user won't receive a message from function_one. (IDE says that throw exception caught locally).
So what am I doing wrong and what should I do? Handle every async function in Master function like that:
async function Master (user_input) {
try {
let object = await function_one(user_input)
.then(data => {
//working with properties here and then return it?
})
.catch(error => {//handle it});
or return from function_one all properties like: return {property_one, property_two, ... }
Using destructuring assignment helps me with this problem

React - variable is undefined instead of true/false - component gets rendered first?

Alright, so what I'm trying to achieve is a function, that returns a Button with a different label depending on wether a file exists, or not. So far so good, the code itself seems to be working, but in the wrong order.
What i did is print out 'file does exist' or 'does not exist' in the part of the function that does the actual checking, saving a boolean to a test variable, using said variable to determine which button gets returned in the end.
Now what happens it that, even if the first part prints out 'file does exist' (which should save true to test), console.log(test)a bit further down returns undefined which, of course, results in the conditional not to work.
I am sure I'm overlooking something very simple but I just can't figure it out.
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import fs from 'fs';
var ConfigChecker = React.createClass({
render: function(){
var test;
fs.open('./App.js', 'r', (err, fd) => {
if (err) {
if (err.code === 'ENOENT') {
console.log('file does not exist');
test = false;
}
throw err;
}
console.log('this should be executed first:');
console.log('file does exist')
test = true;
});
if (test)
{
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="true" />);
}
else {
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="false" />);
}
}
});
export default ConfigChecker;
this is what gets returned in the dev console
Async calls do not work as you think they do. Anytime you see a function which is async (expects a callback, returns a Promise) in JavaScript prefix the function's name in your mind with "registerInterestIn{functionName}". So fs.open becomes fs.registerInterestInOpen.
With that in mind, your code now looks like this:
var test;
fs.registerInterestInOpen('./App.js', 'r', ...);
// Still undefined here, we just registered an interest
// in the results of opening ./App.js ... nothing has
// actually *happened* yet.
if (test) {
} else {
}
// And when we get down here, JavaScript will actually *do*
// the opening, and call our callback with the result, when
// the operating system gets back to us with the data or an error
How to handle this issue then?
You'll need to use a bit of React state so you know which of the three states your component is in (waiting to find out if the file exists, the file exists, the file does not exist). Then you need to change your state in the callback to fs.open to tell React to re-render your component:
const ConfigChecker = React.createClass({
getInitialState() {
return { test: null };
},
componentDidMount() {
fs.open('./App.js', 'r', (err, fd) => {
if (err && err.code === 'ENOENT') {
console.log('file does not exist');
return this.setState(() => { test: false});
}
this.setState(() => { test: true});
});
},
render() {
if (this.state.test == null) {
return 'Loading ...';
} else {
return <RaisedButton label={this.state.test} />;
}
}
try this:
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import fs from 'fs';
var ConfigChecker = React.createClass({
getInitialState: function(){
return {
test: true
}
},
// or componentWillMount() should work
componentDidMount: function(){
var test;
var self = this;
fs.open('./App.js', 'r', (err, fd) => {
if (err) {
if (err.code === 'ENOENT') {
console.log('file does not exist');
self.setState({
test: false
});
// test = false;
}
throw err;
}
console.log('this should be executed first:');
console.log('file does exist')
self.setState({
test: true
});
// test = true;
});
},
render: function(){
if (this.state.test)
{
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="true" />);
}
else {
console.log("this should be executed last:");
console.log(test);
return (<RaisedButton label="false" />);
}
}

Categories

Resources