Handling exceptions in C#Bot client-side
Exception handling involves catching errors and handling them gracefully in a way which does not adversely impact the user experience. Rather than the application completely failing, it is put into a state which can be recovered or provides useful information to the end user (or developer) of what went wrong.
Given that there are countless examples of how you can handle exceptions, we will provide a practical example and strategies for dealing with these exceptions. Loading a list of data asynchronously from the server-side into the client-side is a useful example of handling different state changes and potential exceptions.
Example
Let’s say we wanted to make a list of ShipEntities
. We can start by making a component which maps each item in the list and displays its details. The IShipModels
interface maps how the object will appear when it is returned from the server-side.
interface IShipModels {
countShipEntitys: { number: number };
shipEntitys: ShipEntity[];
}
function ShipItemsList(props: {ships: IShipModels}) {
return (
<div>
<p>Ship Count: {props.ships.countShipEntitys.number}</p>
{props.ships.shipEntitys.map((s) => (
<>
<p>ShipName: {s.shipName}</p>
<p>BuildDate: {s.buildDate}</p>
</>
))}
</div>
);
}
This component is straightforward, but it does not consider the case where the list is is empty. We can update the code to handle this gracefully.
function ShipItemsList(props: {ships: IShipModels}) {
if (props.ships.shipEntitys.length > 0) {
return (
<div>
<p>Ship Count: {props.ships.countShipEntitys.number}</p>
{props.ships.shipEntitys.map((s) => (
<>
<p>ShipName: {s.shipName}</p>
<p>BuildDate: {s.buildDate}</p>
</>
))}
</div>
);
} else {
return (<p>No Ships could be found</p>);
}
}
Now we have covered two cases:
- The ShipList is
populated
with entities. - The ShipList is
empty
.
There are two more cases we will need to consider when bringing this component into the larger context. When fetching from an API endpoint, there are at least two other cases we want to be thinking about when performing the operation asynchronously:
- What to display when the list is
loading
. - What to display when there is an
error
.
To handle these extra cases we can create additional components for these before bringing everything together.
function Error() {
return (
<div>
<p className="error">There was an error loading the list of ships</p>;
</div>
);
}
function Loading() {
return (
<div>
<p>Loading...</p>;
</div>
);
}
We now know what to render when the list is in each of these states. Now we need only create a component with this state logic, and to perform the fetch from the API. In this case we have prepared a custom generic hook for you to use to fetch the data and return the appropriate state.
function useLoading<T>(request: DocumentNode) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [data, setData] = useState<T>();
useEffect(() => {
store.apolloClient
.query<T>({ query: request })
.then((d) => {
console.log(d);
setLoading(false);
setData(d.data);
})
.catch((e) => {
console.log(e);
setLoading(false);
setError(e);
});
}, [request]);
return { loading, error, data };
}
The ShipList
component below shows how we can perform the fetch operation at the start and handle what to render based on if
statements. If the component has finished loading
and there are no errors
it will render the list.
export default function ShipList() {
const { loading, error, data: shipList } = useLoading<IShipModels>(
getFetchAllQuery(ShipEntity)
);
if (loading) {
return <Loading />
}
if (error) {
return <Error />;
}
if (shipList) {
return <ShipItemsList ships={shipList} />
}
return <></>;
}
This article has provided a walkthrough for handling exceptions gracefully both inside components and when they are dependent on an external API call running asynchronously. Effectively managing state in your custom components is important for providing improving the experience of developers and end users.
Was this article helpful?