I'm trying to create a custom hook and i would like to catch an error on the api call in order to change the display. This is the custom hook :
function useBook() {
const [book, setBook] = useState<Book | any>()
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | boolean>(false)
useEffect(() => {
const fetchData = async () => {
try {
const data = await getBook(Number(Object.values(id)))
setBook(data)
setLoading(false)
} catch (error) {
setError(true)
setLoading(false)
}
}
fetchData()
dispatch({ type: 'auth/isLogin' })
}, [])
return { book, loading, error }
}
And this is the api function :
export type Book = {
title: string
id: number
authorName: string
authorSurname: string
coverImage: string
releaseDate: string
pages: number
price: number
}
export type Error = {
isError: boolean
}
async function getBook(id: number): Promise<Book | Error> {
try {
const { data } = await axios.get<Book>(
`http://localhost:3000/books/${id}`,
{
headers: {
Accept: 'application/json',
},
}
)
return data
} catch (error) {
console.log('Error is : ', error)
return {
isError: true,
}
}
}
The problem is that I never enter in the 'catch error' case and thus the state of error is always false. How could I change that ?
Trying throwing an error instead of return:
export type Book = {
title: string
id: number
authorName: string
authorSurname: string
coverImage: string
releaseDate: string
pages: number
price: number
}
export type Error = {
isError: boolean
}
async function getBook(id: number): Promise<Book | Error> {
try {
const { data } = await axios.get<Book>(
`http://localhost:3000/books/${id}`,
{
headers: {
Accept: 'application/json',
},
}
)
return data
} catch (error) {
console.log('Error is : ', error)
throw new Error("error")
}
}
Related
I made an async custom function wrapper that takes care of response returning 401 unauthorized.
How do i properly type return type of my fetching function to make my data from useQuery infer that type?
// ASYNC WRAPPER
type HandlerType = (args?: any) => Promise<any>;
export const asyncWrapper =
(handler: HandlerType) =>
async (args?: any): Promise<any> => {
try {
const result = await handler(args);
return result;
} catch (err: any) {
if (err.response.status === 401) {
// refresh token then again call handler
await sessionService.refreshToken();
const result = await handler(args);
return result;
}
}
};
//FETCHING REQUEST
export type QuestionsType = {
answerOptions: {
_id: string;
answerText: string;
isCorrect: boolean;
};
questionText: string;
};
const getQuestions = asyncWrapper(
async (difficulty: string): Promise<QuestionsType[]> //type not working => {
const token = localStorage.getItem("accessToken");
try {
const response = await axios.get("/questions", {
headers: {
Authorization: token,
},
});
return response.data;
} catch (e) {
throw new Error("Custom");
}
}
);
const { data } = useQuery(["quiz"], quizService.getQuestions); // data type is "any"
Use generics to type it, here is a playground
export const asyncWrapper =
<A, R>(handler: (args: A) => Promise<R>) =>
async (args: A): Promise<R> => {
try {
return handler(args);
} catch (err: any) {
if (err.response.status === 401) {
// refresh token then again call handler
return handler(args);
}
}
throw new Error("Handle this")
};
//FETCHING REQUEST
export type QuestionsType = {
answerOptions: {
_id: string;
answerText: string;
isCorrect: boolean;
};
questionText: string;
};
const getQuestions = asyncWrapper(
async (difficulty: string): Promise<QuestionsType[]> => {
const token = localStorage.getItem("accessToken");
try {
return [];
} catch (e) {
throw new Error("Custom");
}
}
);
I have this middleware that gets the aws lambda function event:
export function hasuraActionHandler<Input, Output>(
allowedRoles: string[],
handler: (
input: Input,
hasuraRole: string,
hasuraUserId?: string,
ipAddress?: string,
// #ts-ignore
context: Context,
) => Promise<typeof formatJSONResponse<Output>>
) :any {
return async (event, context, callback) => {
const { hasuraRole, hasuraUserId, input, ipAddress } =
getHasuraActionParams<Input>(event);
if (!allowedRoles.includes(hasuraRole)) {
return callback(null, {
statusCode: 403,
body: JSON.stringify({
message: 'Forbidden',
}),
});
}
try {
callback(null, {
statusCode: 200,
body: await handler(input, hasuraRole, hasuraUserId, ipAddress, context),
});
} catch (e) {
console.error(e);
return callback(null, {
statusCode: 400,
body: JSON.stringify({
message: e
}),
});
}
};
}
getHasuraActionParams function:
export function getHasuraEventParams<T>(event: APIGatewayEvent): {
data: T;
hasuraRole: string;
hasuraAllowedRoles?: string[];
hasuraUserId?: string;
} {
const data = parse(event.body).event.data
const {
"x-hasura-allowed-roles": hasuraAllowedRoles,
"x-hasura-role": hasuraRole,
"x-hasura-user-id": hasuraUserId
} = parse(event.body).event.session_variables;
return { data, hasuraRole, hasuraAllowedRoles, hasuraUserId };
}
the aws function:
const ban_account = hasuraActionHandler<ban_accountArgs, ban_account_output>(
["admin"],
async (input, hasuraRole, hasuraUserId, context) => {
....
return formatJSONResponse({
id: "1"
});
}
);
ban_account_output type:
type ban_account_output = {
id: string;
};
the formatJSONResponse function:
export const formatJSONResponse = (response: Record<string, unknown>) => {
return {
statusCode: 200,
body: JSON.stringify(response)
};
};
Question is, how to type the Promise<typeof formatJSONResponse<Output> in the middleware? As is it throws this: TS2635: Type '(response: Record ) => { statusCode: number; body: string; }' has no signatures for which the type argument list is applicable.
Is it also possible to type the :any on the middleware?
codesandbox: https://codesandbox.io/s/lingering-pond-wcu8go?file=/src/index.ts
In trying to execute this action, and while I'm getting the correct data in response on the backend, the data variable is coming back undefined. I'm also getting a TS error saying Property 'data' does not exist on type 'void'
action/churches.js
export const getChurchesBySearch = (searchQuery) => async (dispatch) => {
try {
const { data } = await apis.getChurchesBySearch(searchQuery);
dispatch({ type: "FETCH_BY_SEARCH", payload: data });
} catch (error) {
console.log(error.message);
}
};
Like I mentioned, the correct response is being returned on the backend, but when assigning it to data, data is undefined.
Here are the other files:apis/churches.js
export const getChurchesBySearch = (searchQuery) => {
console.log(searchQuery.city);
axios.get(
`${url}/search?city=${searchQuery.city}&denom=${searchQuery.denom}`
);
};
controller/churches.js
export const getChurchesBySearch = async (req, res) => {
const { city, denom } = req.query;
try {
const churches = await Church.find({
$or: [{ city: city }, { denom: denom }],
}).sort({ attend: -1 });
res.status(200).json(churches);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
You are missing the return statement in the getChurchesBySearch function.
export const getChurchesBySearch = (searchQuery) => {
console.log(searchQuery.city);
return axios.get(
`${url}/search?city=${searchQuery.city}&denom=${searchQuery.denom}`
);
};
Just to make it clear router uses the code below and my messages.js are inside api folder....
router.use("/messages", require("./messages"));
so my api call is correct.
Backend for posting the message.... I know conversationId will be null if no conversation exists but... I am trying to send message where conversation exists already and still I am getting cannot read the conversationId of undefined....
// expects {recipientId, text, conversationId } in body
// (conversationId will be null if no conversation exists yet)
router.post("/", async (req, res, next) => {
try {
if (!req.user) {
return res.sendStatus(401);
}
const senderId = req.user.id;
const { recipientId, text, conversationId, sender } = req.body;
// if we already know conversation id, we can save time and just add it to message and return
if (conversationId) {
const message = await Message.create({ senderId, text, conversationId });
return res.json({ message, sender });
}
// if we don't have conversation id, find a conversation to make sure it doesn't already exist
let conversation = await Conversation.findConversation(
senderId,
recipientId
);
if (!conversation) {
// create conversation
conversation = await Conversation.create({
user1Id: senderId,
user2Id: recipientId,
});
if (onlineUsers.includes(sender.id)) {
sender.online = true;
}
}
const message = await Message.create({
senderId,
text,
conversationId: conversation.id,
});
res.json({ message, sender });
} catch (error) {
next(error);
}
});
module.exports = router;
This is the frontend that posts the data to the backend....
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
Okay so here is detail information on how I am dispatching it.
class Input extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
};
}
handleChange = (event) => {
this.setState({
text: event.target.value,
});
};
handleSubmit = async (event) => {
event.preventDefault();
// add sender user info if posting to a brand new convo,
// so that the other user will have access to username, profile pic, etc.
const reqBody = {
text: event.target.text.value,
recipientId: this.props.otherUser.id,
conversationId: this.props.conversationId,
sender: this.props.conversationId ? null : this.props.user,
};
await this.props.postMessage(reqBody);
this.setState({
text: "",
});
};
render() {
const { classes } = this.props;
return (
<form className={classes.root} onSubmit={this.handleSubmit}>
<FormControl fullWidth hiddenLabel>
<FilledInput
classes={{ root: classes.input }}
disableUnderline
placeholder="Type something..."
value={this.state.text}
name="text"
onChange={this.handleChange}
/>
</FormControl>
</form>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(Input));
const mapDispatchToProps = (dispatch) => {
return {
postMessage: (message) => {
dispatch(postMessage(message));
},
};
};
// message format to send: {recipientId, text, conversationId}
// conversationId will be set to null if its a brand new conversation
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body);
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
So I have attached what I want to do here now....
But I am still getting the problem....
// CONVERSATIONS THUNK CREATORS, this is how I am getting data from the backend
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
export const setNewMessage = (message, sender) => {
return {
type: SET_MESSAGE,
payload: { message, sender: sender || null },
};
};
// REDUCER
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
I am getting an error saying Cannot read property 'conversationId' of undefined while using a reducer function... Should I give the setintial value of the message to empty?
export const addMessageToStore = (state, payload) => {
const { message, sender } = payload;
// if sender isn't null, that means the message needs to be put in a brand new convo
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
return [newConvo, ...state];
}
return state.map((convo) => {
if (convo.id === message.conversationId) {
const convoCopy = { ...convo };
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
return convoCopy;
} else {
return convo;
}
});
};
Issue
The saveMessage function is declared async
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
but the postMessage action creator isn't async so it doesn't wait for the implicitly returned Promise to resolve before continuing on and dispatching to the store. This means that data.message is undefined since a Promise object doesn't have this as a property.
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body); // <-- no waiting
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
Solution
Declare postMessage async as well and await the data response value.
export const postMessage = (body) => async (dispatch) => {
try {
const data = await saveMessage(body); // <-- await response
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
I have been working on a project where I am trying to update my selected data but Axios didn't Update it even after giving a success msg.
User Response it returns from axios:-
completed: true
date: "2021-02-28"
mupp_path: "PATH 1 - LIVING YOUR WHY - Build/Apply/Inspire/Spread (BAIS) – Finding & Achieving Meaning and Purpose in work and life"
project_name: "Design and Test the Training Content for the i-Infinity 3 verticals "
selected: true
task_id: 14
task_name: "This is adding a new task to chekc full inbox functionality "
task_type: "THIS_WEEK"
Actions.js
export const taskTodayUnselect = (id) => async (dispatch) => {
try {
dispatch({ type: types.UNSELECTED_TASK_TODAY_REQUEST });
const { data } = await axios.put(
selectTaskForToday,
{
task_id: id,
selected: false,
},
{
headers: {
Authorization: `JWT ${token}`,
},
}
);
if (data) {
return dispatch({ type: types.UNSELECTED_TASK_TODAY_SUCCESS, payload: data });
}
} catch (error) {
return dispatch({ type: types.UNSELECTED_TASK_TODAY_FAILURE, payload: error });
}
};
thisweek.js
export default function ThisWeek() {
const unselectTaskTodayAPI = (id) => {
dispatch(taskTodayUnselect(id)).then((response) => {
let result = response.payload;
console.log(result);
if (result.success === 'true') {
notifySuccess(result.message);
fetchTaskData(categoryID);
}
});
};
const selectTask = (item) => {
if (item.selected) {
unselectTaskTodayAPI(item);
console.log('unselect');
} else {
selectTaskTodayAPI(item.task_id);
}
};
return (
<TaskDataComponent
item={item}
key={item.task_id}
label="This Week"
selectTask={selectTask}
/>
);
Don't Worry about the TaskDataComponent , it only handle the onClick function which invoke the selectedTask function