React context returns promise - javascript

I have a todo app. Im trying to use context api(first time). I have add, delete and get functions in context. I can use add and delete but cant return the get response to state. It returns promise if i log; context. Im using async await. I tried almost everything i know but cant solve it. Where is my fault ?
Thank you.
task-context.js
import React, { useReducer } from "react";
import TaskContext from "./task-actions";
import { TaskReducer, ADD_TASK, GET_TASKS, REMOVE_TASK } from "./reducers";
const GlobalState = (props) => {
const [tasks, dispatch] = useReducer(TaskReducer, { tasks: [] });
const addTask = (task) => {
dispatch({ type: ADD_TASK, data: task });
};
const removeTask = (taskId) => {
dispatch({ type: REMOVE_TASK, data: taskId });
};
const getTasks = () => {
dispatch({ type: GET_TASKS });
};
return (
<TaskContext.Provider
value={{
tasks: tasks,
getTasks: getTasks,
addTask: addTask,
removeTask: removeTask,
}}
>
{props.children}
</TaskContext.Provider>
);
};
export default GlobalState;
reducers.js
import taskService from "../Services/tasks-service";
export const ADD_TASK = "ADD_TASK";
export const GET_TASKS = "GET_TASKS";
export const REMOVE_TASK = "REMOVE_TASK";
const addTask = async (data, state) => {
console.log("Adding : " + data.title);
try {
let task = {
title: data.title,
description: data.description,
comment: data.comment,
progress: data.status
};
const res = await taskService.addNewTask(task);
console.log(res);
if (res) {
getTasks();
}
} catch (err) {
console.log(err);
}
return;
};
const getTasks = async () => {
let response = {}
try {
const res = await taskService.loadTasks();
response = res.data
} catch (err) {
console.log(err);
}
return { tasks: response }
};
const removeTask = async (data) => {
try {
await taskService.deleteTask(data.id);
} catch (err) {
console.log(err);
}
};
export const TaskReducer = (state, action) => {
switch (action.type) {
case ADD_TASK:
return addTask(action.data);
case GET_TASKS:
console.log(getTasks());
return getTasks();
case REMOVE_TASK:
return removeTask(action.data);
default:
return state;
}
};
task-actions.js
import React from "react";
export default React.createContext({
addTask: (data) => {},
removeTask: (data) => {},
getTasks: () => {}
});

To start with, you are getting promises returned because you are explicitly returning promises: return addTask(action.data). All your actions are returning promises into the reducer.
A reducer should be a pure function, meaning that it does not have any side effects (call code outside its own scope), or contain any async functionality, and it should return the same data given the same inputs every single time. You've essentially got the workflow back to front.
There's a lot to unpick here so I'm going to provide pseudocode rather than try and refactor the entire service, which you will have a more complete understanding of. Starting with the reducer:
export const TaskReducer = (state, action) => {
switch (action.type) {
case ADD_TASK:
return [...state, action.data];
case GET_TASKS:
return action.data;
case REMOVE_TASK:
return state.filter(task => task.id !== action.data.id);
default:
return state;
}
};
This reducer describes how the state is updated after each action is complete. All it should know how to do is update the state object/array it is in charge of. When it comes to fetching data, calling the reducer should be the very last thing you have to do.
Now on to the actions. The add action is a problem because its not actually returning any data. On top of that, it calls getTasks when really all it ought to do is return one added task (which should be getting returned from await taskService.addNewTask). I would expect that res.data is actually a task object, in which case:
export const addTask = async (data) => {
try {
const task = {
title: data.title,
description: data.description,
comment: data.comment,
progress: data.status
};
const res = await taskService.addNewTask(task);
return res.data;
} catch (err) {
return err;
}
};
Similarly for getTasks, I'm going to assume that await taskService.loadTasks returns an array of task objects. In which case, we can simplify this somewhat:
export const getTasks = async () => {
try {
const res = await taskService.loadTasks();
return res.data;
} catch (err) {
return err;
}
};
Your removeTask action is essentially fine, although you will want to return errors instead of just logging them.
Notice we're now exporting these actions. That is so we can now call them from within GlobalState. We're running into issues with name collision so I've just underscored the imported actions for demo purposes. In reality, it might be better to move all the functionality we did in the last step into your taskService, and import that straight into GlobalState instead. Since that's implementation specific I'll leave it up to you.
import {
TaskReducer,
ADD_TASK,
GET_TASKS,
REMOVE_TASK,
addTask as _addTask,
getTasks as _getTasks,
removeTask as _removeTask,
} from "./reducers";
const GlobalState = (props) => {
const [tasks, dispatch] = useReducer(TaskReducer, { tasks: [] });
const addTask = async (task) => {
const added = await _addTask();
if (added instanceof Error) {
// handle error within the application
return;
};
dispatch({ type: ADD_TASK, data: added });
};
const removeTask = async (taskId) => {
const removed = await _removeTask(taskId);
if (removed instanceof Error) {
// handle error within the application
return;
};
dispatch({ type: REMOVE_TASK, data: taskId });
};
const getTasks = async () => {
const tracks = await _getTracks();
if (tracks instanceof Error) {
// handle error within the application
return;
};
dispatch({ type: GET_TASKS, data: tracks });
};
...
}
Hopefully now you can see how the workflow is supposed to progress. First we call for data from our backend or other API, then we handle the response within the application (for instance, dispatching other actions to notify about errors or side effects of the new data) and then finally dispatch the new data into our state.
As stated at the beginning, what I've provided is essentially pseudocode, so don't expect it to work out of the box.

Related

How can I dispatch async data with Vue 3 store

I am pulling data from an api as async and transferring the payload value returned from this api to the state field in the store with store.dispatch.
But at first this state is empty. When I make a change on the page and render it, the state is filled.
the function I pulled the api
const getTransferredOrder = async () => {
isLoading.value = true;
return await TransferredOrderService.getTransferredOrderSummary()
.then((payload) => {
store.dispatch('GetTransferredList',payload)
return payload;
})
.catch(() => {
return [];
}).finally(() => {
isLoading.value = false
});
}
i call this function first in onmounted
onMounted(async () => {
await getTransferredOrder()
})
my actions,mutations and state js files
actions.js
const actions={
GetTransferredList({commit},payload){
commit('GET_TRANSFERRED_ORDER_LIST',payload)
},
}
export default actions
mutations.js
const mutations={
GET_TRANSFERRED_ORDER_LIST(state,payload){
state.transferredOrderList = payload;
},
}
export default mutations
state.js here
const state={
transferredOrderList:[],
}
export default state
Since the state is empty in the place where I store.dispatch, it is also empty on other pages.
But when I write a small console.log on the page and compile it, the store.state fills the page. What exactly does that have to do with it?
Can you look at console and write errors here.
Why are you returns? Can you change code to ->
const getTransferredOrder = async () => {
isLoading.value = true;
await TransferredOrderService.getTransferredOrderSummary()
.then((payload) => {
store.dispatch('GetTransferredList',payload);
})
.catch(() => {
store.dispatch('GetTransferredList',[]);
}).finally(() => {
isLoading.value = false
});
}

Is this the correct way to handle error when calling multi dispatch on a screen?

I am making multiple dispatch call in the homepage
Its was something like this.
useEffect(() => {
async function getStorageData() {
setLoading(true);
try {
await dispatch(fetchProductA());
await dispatch(fetchProductB());
await dispatch(fetchProductC());
await dispatch(fetchProductD());
await dispatch(fetchProductE());
} catch (err) {
console.log(err);
} finally {
setLoading(false);
}
}
getStorageData();
}, []);
The problem is that when calling the these api. I got an error when productC.
So I made a dispatch call.
I try throwing an error but that not work because when I throw an error in productC the remaining product D and E will not be called because throw end the dispatch calling
Here is my api call.
export const fetchProductC = () => dispatch => {
return axios
.get('productsapi/fetchProductC', {
headers: {
'Content-Type': 'application/json',
},
})
.then(res => {
dispatch({
type: FETCH_NEW_PRODUCTS,
payload: res.data[0]
});
})
.catch(err => {
dispatch({
type: EMPTY_NEW_PRODUCTS,
});
console.log('fetching new product error');
//throw err;
});
};
Here is the reducer
case FETCH_NEW_PRODUCTS:
return {
...state,
listC: action.payload,
};
case EMPTY_NEW_PRODUCTS:
return {
...state,
listC: [],
};
Your code after ProductC can't run because it is skipped via catch.
You can write your code like below.
await dispatch(fetchProductA()).catch(handleErr);
await dispatch(fetchProductB()).catch(handleErr);
await dispatch(fetchProductC()).catch(handleErr);
await dispatch(fetchProductD()).catch(handleErr);
await dispatch(fetchProductE()).catch(handleErr);
You should never await a dispatch in the component. Read tutorials and refactor your data flow: Redux Async Data Flow, Async Logic and Data Fetching
Redux recommends you to use thunk to do this:
// fetchTodoById is the "thunk action creator"
export function fetchTodoById(todoId) {
// fetchTodoByIdThunk is the "thunk function"
return async function fetchTodosThunk(dispatch, getState) {
// dispatch an action to set a loading state
dispatch(/*...*/)
const response = await client.get(`/fakeApi/todo/${todoId}`)
// use your response and set a success state here
dispatch(todosLoaded(response.todos))
}
}
// Your component
function TodoComponent({ todoId }) {
const dispatch = useDispatch()
const { data, state } = useSelector(/* your selector */)
const onFetchClicked = () => {
// Calls the thunk action creator, and passes the thunk function to dispatch
dispatch(fetchTodoById(todoId))
}
}

What is the correct way to handle HTTP response in React Native component

I am building a mobile app and I'm using Django REST Framework as a backend. And I am also using Redux. One of the API that I am using is to validate an OTP code. If the OTP code is matched, then I am retuning with the response from the server if this is a new user or not. If it's a new user then I'll redirect it to a registration screen, if not then I'll redirect it to the login screen, this is my problem.
I am storing the response of the server in variable named isNewUser in redux store. Then, I am accessing it Inside my component with useSelector. When I click on the button after I entered then OTP code, I dispatch two actions. First the one to validate the OTP. The second is either will be a dispatch for Login or dispatch for registration action, this is depends on the variable isNewUser which I am getting from redux store.
The problem is that, when I dispatch the first action, which is validation of the OTP and storing of the isNewUser variable, the value of this variable is not updated in my component until the next render, so I can't dispatch the second action until I click the button again so that the value of the variable is updated.
So how to fix that? I don't know if my implementation is correct or not or there is a better one.
Here is my code for the action, I didn't write the code for login and register actions yet
export const validateOTP = (otp, mobileNum) => {
return async dispatch => {
const response = await fetch("http://localhost:8000/api/validate_otp", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
otp: otp,
mobile: mobileNum
})
});
if (!response.ok) {
const errorResData = await response.json();
console.log(errorResData);
}
const resData = await response.json();
if (resData.status === false) {
throw new Error(resData.detail);
} else {
const isNewUser = resData.isNewUser;
dispatch({
type: VALIDATE_OTP,
isNewUser: isNewUser
});
}
};
};
Here is my code for the reducer:
import { VALIDATE_OTP } from "../actions/auth";
const initialState = {
isNewUser: null
};
export default (state = initialState, action) => {
switch (action.type) {
case VALIDATE_OTP: {
const isNewUserVal = action.isNewUser;
return {
...state,
isNewUser: isNewUserVal
};
}
}
return state;
};
Here is a sample code from the React Native component:
const CodeEntryScreen = props => {
const dispatch = useDispatch();
const isNewUser = useSelector(state => state.auth.isNewUser)
const [error, setError] = useState();
useEffect(() => {
if (error) {
Alert.alert("An Error Occurred", error, [{ text: "Okay" }]);
}
}, [error]);
const validateOTPHandler = async () => {
setError(null);
try {
await dispatch(authActions.validateOTP(otp, mobileNum));
console.log(isNewUser)
if(isNewUser) {
// dispatch resgister action
}
else {
// dispatch login action
}
} catch (err) {
setError(err.message);
}
};
You can fix this issue with little modifications. The easier one is this:
1) Use dispatch return value in your validateOTPHandler
In your validateOTP function, you have this at the end:
dispatch({
type: VALIDATE_OTP,
isNewUser: isNewUser
});
Make your function to return that instead:
return dispatch({
type: VALIDATE_OTP,
isNewUser: isNewUser
});
With that change, in your component, you can access to the payload of your action this way:
const validateOTPHandler = async () => {
setError(null);
try {
const { isNewUser: isNew } = await dispatch(authActions.validateOTP(otp, mobileNum));
console.log(isNew)
if(isNew) {
// dispatch resgister action
}
else {
// dispatch login action
}
} catch (err) {
setError(err.message);
}
};
That is the easier change to make it work as you want.
2) useEffect
I think this is more similar to the flow you had in mind:
Valide the code (update the store)
Re-render: you got the new value
Now do something: login or register
But do that, you need to use useEffect in order to listen the changes you made this way:
const CodeEntryScreen = props => {
const dispatch = useDispatch();
const isNewUser = useSelector(state => state.auth.isNewUser)
const [error, setError] = useState();
const [success, setSuccess] = useState(false); // true when validateOTP succeeds
useEffect(() => {
if (error) {
Alert.alert("An Error Occurred", error, [{ text: "Okay" }]);
}
}, [error]);
useEffect(() => {
if (success) {
// validateOTP succeed... let's check isNewUser :)
if (isNewUser) {
// dispatch register
} else {
// dispatch login
}
}
}, [success, isNewUser]);
const validateOTPHandler = async () => {
setError(null);
setSuccess(false);
try {
await dispatch(authActions.validateOTP(otp, mobileNum));
setSuccess(true);
} catch (err) {
setError(err.message);
}
};

Why can't I load the API?

I'm going to call the movie API using the redux in the react application.
During the process of calling the movie API using the redux-thunk,
An error occurs while calling the callAPI function on the lib/THMb path.
//movie_project/src/lib/THMb.js
import axios from 'axios';
const key = "xxxxxxx";
const url = `https://api.themoviedb.org/3/movie/now_playing?api_key=${key}&language=ko&page=1&region=KR`;
export const callAPI = async () =>{
await axios.get(`${url}`);
}
import { handleActions } from "redux-actions";
import axios from "axios";
import * as movieAPI from '../lib/THMb';
// action types
const GET_MOVIES = 'movie/GET_MOVIES';
const GET_MOVIES_SUCCESS = 'movie/GET_MOVIES_SUCCESS';
const GET_MOVIES_FAILURE = 'movie/GET_MOVIES_FAILURE';
export const getMovies = () => async dispatch => {
dispatch({ type: GET_MOVIES });
try{
const res = await movieAPI.callAPI(); // failed
dispatch({
type: GET_MOVIES_SUCCESS, // 요청 성공
payload: res.data.results, // API 요청 결과 값
})
}catch(e){
dispatch({
type: GET_MOVIES_FAILURE, // 요청 실패
payload: e,
error: true
})
throw e;
}
}
const initialState ={
movieList : [],
error: null
}
const movie = handleActions(
{
[GET_MOVIES]: state => ({
...state,
// loading..
}),
[GET_MOVIES_SUCCESS]: (state, action) => ({
...state,
movieList: action.payload,
}),
[GET_MOVIES_FAILURE]: (state, action) => ({
...state,
// loading...
})
},
initialState
)
export default movie;
enter image description here
However, no error occurs when calling url from within the getMovies function.
export const getMovies = () => async dispatch => {
dispatch({ type: GET_MOVIES }); // 요청의 시작을 알림.
try{
//const res = await movieAPI.callAPI(); // failed
// success
const res = await axios.get(`https://api.themoviedb.org/3/movie/now_playing?api_key=xxxxx&language=ko&page=1&region=KR`);
dispatch({
type: GET_MOVIES_SUCCESS, // 요청 성공
payload: res.data.results, // API 요청 결과 값
})
Why do errors occur in the first case???
That's because in the first case you are not returning anything. You should try this:
export const callAPI = async () => {
let res = await axios.get(`${url}`);
return res;
}
Hope this works for you.
the error occurs in callAPI function, not in getMovies because in payload you are assuming res variable to fetch the data from it and you successfully get it in getMovies function but not in callAPI.
because you did not return anything from callAPI method that's why res variable is null and it throws the error.
just replace you callAPI function with the below code.
export const callAPI = async () =>{
const res await axios.get(`${url}`);
return res
}
hopefully, it will work just give it a try

How to return an array received from fetching api data in a .then statement?

I'm trying to export an array inside a .then statement but its not working. I have no clue how to make it work otherwise. Actually I'm just trying to set my initial state in redux to this static data I am receiving from the movie database api.
import { API_URL, API_KEY } from '../Config/config';
const urls = [
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`,
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`,
]
Promise.all(urls.map(items => {
return fetch(items).then(response => response.json())
}))
.then(arrayOfObjects => {
var arr1 = arrayOfObjects[0].results;
var arr2 = arrayOfObjects[1].results;
export var movieData = arr1.concat(arr2);
}
)
You can try with a function. like this:
import { API_URL, API_KEY } from '../Config/config';
export const getMovies = () => {
const urls = [
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`,
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`,
]
const promises = urls.map(url => {
return new Promise((reject, resolve) => {
fetch(url).then(res => res.json())
.then(res => resolve(res.results))
})
})
return Promise.all(promises)
}
// other file
import {getMovies} from 'YOUR_API_FILE.js';
getMovies().then(moviesArr => {
// your business logics here
})
It's not clear where this code is in relation to your state/reducer, but ideally you should be using action creators to deal with any API calls and dispatch state updates, and those action creators can be called from the component.
So, initialise your state with an empty array:
const initialState = {
movies: []
};
Set up your reducer to update the state with MOVIES_UPDATE:
function reducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case 'MOVIES_UPDATE': {
return { ...state, movies: payload };
}
}
}
You can still use your function for fetching data:
function fetchData() {
return Promise.all(urls.map(items => {
return fetch(items).then(response => response.json());
}));
}
..but it's called with an action creator (it returns a function with dispatch param), and this action creator 1) gets the data, 2) merges the data, 3) and dispatches the data to the store.
export function getMovies() {
return (dispatch) => {
fetchData().then(data => {
const movieData = data.flatMap(({ results }) => results);
dispatch({ type: 'MOVIES_UPDATE', payload: movieData });
});
}
}
And it's called from within your component like so:
componentDidMount () {
this.props.dispatch(getMovies());
}
You can modify the code as below:
import { API_URL, API_KEY } from '../Config/config';
let movieData='';
exports.movieData = await (async function(){
const urls = [
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`,
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`,
];
const arrayOfObjects = await Promise.all(urls.map(items => {
return fetch(items).then(response => response.json())
}));
return arrayOfObjects[0].results.concat(arrayOfObjects[1].results);
})();

Categories

Resources