useEffect returning context function with null value - reactjs

I am using a context configuration (not redux) that consists of three files. A state file, reducer file, and context file. It makes an axios call to my Atlas MongoDB collection. I have tested the API using Postman and the get request works as intended, returning a series of transaction data.
The getTransactions() function in the useEffect hook will only return the requested data once. If I make any modifications to the code and save or navigate to a different page and return, the transactions array of objects is now defined as null. My first instinct was that I had an asynchronous syntax error but my understanding of useEffect is that it mitigates async issues.
The function in question looks like this:
const initialState = {
transactions: null,
current: null,
filtered: null,
error: null
};
const getTransactions = async () => {
try {
const res = await axios.get("/api/transactions");
dispatch({ type: GET_TRANSACTIONS, payload: res.data });
} catch (err) {
dispatch({ type: TRANSACTION_ERROR, payload: err.response.msg });
}
};
The reducer handles the type dispatched by the state function inside a switch statement:
case GET_TRANSACTIONS:
return {
...state,
transactions: action.payload,
loading: false,
};
I make the following call in my react component:
useEffect(() => {
getTransactions();
// eslint-disable-next-line
}, []);
My API is running on a node server that uses a route that first checks for user auth and then queries the database. Again, the API has been tested and returns the data as expected given there is a valid JWT auth token in the headers. The route is below:
router.get("/", auth, async (req, res) => {
try {
const transactions = await Transaction.find({ user: req.user.id }).sort({
date: -1,
});
res.json(transactions);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
I need to be able to have the data returned consistently in my component in an array of objects called transactions so it can be mapped to populate a DOM element. I have tried to provide as much of the code here as I felt was relevant but if I'm missing something, I can share that. All required imports have been doublechecked and are correct. I have not included the import lines in this question to conserve space. Thanks in advance!

Related

How do I separate api / async request logic from react components when using recoil

So at the moment I am having to put my request / api logic directly into my components because what I need to do a lot of the time is set state based on the response I get from the back end.
Below is a function that I have on my settings page that I use to save the settings to recoil after the user hits save on the form:
const setUserConfig = useSetRecoilState(userAtoms.userConfig);
const submitSettings = async (values: UserConfigInterface) => {
try {
const { data: {data} } = await updateUser(values);
setUserConfig({
...data
});
} catch (error) {
console.log('settings form error: ', error);
}
}
This works perfectly...I just dont want the function in my component as most of my components are getting way bigger than they need to be.
I have tried making a separate file to do this but I can only use the recoil hooks (in this instance useSetRecoilState) inside of components and it just complains when I try and do this outside of a react component.
I have tried implementing this with recoils selector and selectorFamily functions but it gets kind of complicated. Here is how I have tried it inside of a file that has atoms / selectors only:
export const languageProgress = atom<LanguageProgress>({
key: "LanguageProgress",
default: {
level: 1,
xp: 0,
max_xp: 0
}
})
export const languageProgressUpdate = selectorFamily<LanguageProgress>({
key: "LanguageProgress",
get: () => async () => {
try {
const { data: { data } } = await getLanguageProgress();
return data;
} catch (error) {
console.log('get language progress error');
}
},
set: (params:object) => async ({set}) => {
try {
const { data: { data } } = await updateLanguageProgress(params);
set(languageProgress, {
level: data.level,
xp: data.xp,
max_xp: data.max_xp
});
} catch (error) {
console.log('language progress update error: ', error);
}
}
});
What I want to do here is get the values I need from the back end and display it in the front which I can do in the selector function get but now I have 2 points of truth for this...my languageProgress atom will initially be incorrect as its not getting anything from the database so I have to use useGetRevoilValue on the languageProgressUpdate selector I have made but then when I want to update I am updating the atom and not the actual value.
I cannot find a good example anywhere that does what I am trying to here (very suprisingly as I would have thought it is quite a common way to do things...get data from back end and set it in state.) and I can't figure out a way to do it without doing it in the component (as in the first example). Ideally I would like something like the first example but outside of a component because that solution is super simple and works for me.
So I dont know if this is the best answer but it does work and ultimately what I wanted to do was seperate the logic from the screen component.
The answer in my situation is a bit long winded but this is what I used to solve the problem: https://medium.com/geekculture/crud-with-recoiljs-and-remote-api-e36581b77168
Essentially the answer is to put all the logic into a hook and get state from the api and set it there.
get data from back end and set it in state
You may be looking for useRecoilValueLoadable:
"This hook is intended to be used for reading the value of asynchronous selectors. This hook will subscribe the component to the given state."
Here's a quick demonstration of how I've previously used it. To quickly summarise: you pass useRecoilValueLoadable a selector (that you've defined somewhere outside the logic of the component), that selector grabs the data from your API, and that all gets fed back via useRecoilValueLoadable as an array of 1) the current state of the value returned, and 2) the content of that API call.
Note: in this example I'm passing an array of values to the selector each of which makes a separate API call.
App.js
const { state, contents } = useRecoilValueLoadable(myQuery(arr));
if (state.hasValue && contents.length) {
// `map` over the contents
}
selector.js
import { selectorFamily } from 'recoil';
export const myQuery = selectorFamily({
key: 'myQuery',
get: arr => async () => {
const promises = arr.map(async item => {
try {
const response = await fetch(`/endpoint/${item.id}`);
if (response.ok) return response.json();
throw Error('API request not fulfilled');
} catch (err) {
console.log(err);
}
});
const items = await Promise.all(promises);
return items;
}
});

loss of connection is not causing an error in firestore client side?

I am trying to do a double action in my dispatch function. One action will update the current react state ! the other will update database I don't want to update my react state if the update to firestore fails for some reason, this thing works well if the database reject your call for some reason, but the issue lies in if I simply turn off Wi-Fi the try/catch or .catch doesn't seem to notice any error.
How to handle this ?
my current implementation is update state first then if the update fails I do a step back since I don't want to wait for the await making my app slow , so i update local state then update database and if any thing errors out I return state to default any idea how to handle this and why the loss of connection is not causing any error.
const AddData = async () => {
const payload = {//some pay load };
const docData = doc(collectionRef, clientId);
dispatch({
type: types.ADD_DATA,
payload: {
data: payload,
},
});
await updateDoc(docData, {
data: payload,
// not catching !
}).catch((err) => {
alert('Step BACK');
});
// not catching !
try {
} catch (error) {
alert('Module ExcercsiesState m Location Add routine');
errorHandeling(error, 'An error has happened', reduxDispatch, SetSnackBarMsg);
}
};

react-query always return stale data and no call is made to server

I recently started using react-query and have encountered the issue that always stale data is returned and no call to server is made. here is the react query related code:
export function useGetAccount(id: number){
return useQuery([`account${id}`, id], async (args) => {
const [key, accountId] = args.queryKey
const [acc, teams, modules] = await Promise.all([
getAccount(),
getTeams(),
getModules()])
let account: AccountDetail = {
accountId: acc.accountId,
userId: acc.userId,
companyId: acc.companyId,
login: acc.login,
email: acc.email,
description: acc.description,
isActive: acc.isActive,
providers: acc.providers,
teams: teams,
modules: modules
}
return account
async function getAccount() {
const api = createApi() // <= axios wrapper
const { data } = await api.get(`accounts/${accountId}`, undefined, undefined)
return data as AccountModel
}
async function getTeams() {
const api = createApi()
const { data } = await api.get(`accounts/${accountId}/teams`, undefined, undefined)
const { collection } = data as ResponseCollectionType<AccountTeam>
return collection
}
async function getModules() {
const api = createApi()
const { data } = await api.get(`accounts/${accountId}/resources`, undefined, undefined)
const { collection } = data as ResponseCollectionType<ModuleAccessModel>
return collection
}
})
}
I even reduced the cache time but still to no avail. I do not see any calls made to server side except after a long delay or if I open the browser in incognito mode then first time the data is fetched and then no call is made.
this is used in a component which shows the details and is passed the id as a prop. everything is working fine except that the data is the one which was retrieved first time and even a refresh (F5) returns the stale data.
what changes do I need to make in this case?
[observation]: Ok, it does make a call but only after exact 5 minutes.
well the problem is not in react-query but in axios, described here Using JavaScript Axios/Fetch. Can you disable browser cache?
I used the same solution i.e. appending timestamp to the requests made by axios and everything worked fine.

Redux: Make http call ONLY IF data not available in store?

I do not want to make an http call unless it is actually required:
This is a workaround I have come up with, I am checking the state before making http call
export const fetchOneApi = (id) => async (dispatch, getState) => {
const docDetails = getState().DocState;
// tricking redux to not send http request unless actually required
if (docDetails.docList[id]) {
return dispatch({
type: FETCH_DOC_SUCCESS,
payload: docDetails.docList[id],
});
}
try {
dispatch({
type: FETCH_DOC_REQUEST,
});
const { data } = await api.get(`/api/${id}`);
dispatch({
type: FETCH_DOC_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: FETCH_DOC_FAIL,
payload: error.error,
});
}
};
Wondering if there is some redux hook or feature that takes care of this OR atleast a better approach.
I've written multiple different custom versions of this functionality for various projects. I toyed with sharing some examples but it's all excessively complicated since I really love to abstract things.
Based on your question, what you are asking for is the createAysncThunk function from redux-toolkit. This function creates an action creator which handles dispatching the pending, fulfilled, and rejected actions at the appropriate times.
There are many ways to customize the behavior of the async thunk. Conditional fetching is described in the docs section "Cancelling Before Execution":
If you need to cancel a thunk before the payload creator is called, you may provide a condition callback as an option after the payload creator. The callback will receive the thunk argument and an object with {getState, extra} as parameters, and use those to decide whether to continue or not. If the execution should be canceled, the condition callback should return a literal false value:
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
},
{
condition: (userId, { getState, extra }) => {
const { users } = getState()
const fetchStatus = users.requests[userId]
if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') {
// Already fetched or in progress, don't need to re-fetch
return false
}
}
}
)
In your example you are short-circuiting by dispatching a success action with redundant data. The standard behavior for the above code is that no action will be dispatched at all if the fetch condition returns false, which is what we want.
We want to store the pending state in redux in order to prevent duplicate fetches. To do that, your reducer needs to respond to the pending action dispatched by the thunk. You can find out which document was requested by looking at the action.meta.arg property.
// example pending action from fetchUserById(5)
{
type: "users/fetchByIdStatus/pending",
meta: {
arg: 5, // the userId argument
requestId: "IjNY1OXk4APoVdaYIF8_I",
requestStatus: "pending"
}
}
That property exists on all three of the dispatched actions and its value is the argument that you provide when you call your action creator function. In the above example it is the userId which is presumably a string or number, but you can use a keyed object if you need to pass multiple arguments.

How to get data from my axios request without using a setTimeout?

I am having a problem, I believe many of you have faced it somehow, so I am using axios to get my data :
let data = axios.get(
"http://localhost:8080/api/taskExecution?&search=&page=1&size=8"
).then(response => {
if (response.status !== 200) {
console.log("LOADING");
} else {
return response.data;
}
});
let tasks = [];
data.then(response => {
tasks = response;
});
console.log(tasks);
return tasks;
Something like this, response data returns array of 8 items, but tasks is still empty which is normal because the axios request takes some time, I can use setTimeout for 100ms and inside it, I put console.log(tasks); and it will work but is not a proper solution, because what if the server takes 5s to returns the data?
This code is in my reducer, so I have to get my tasks and return them so I can display them, and show a loader when the request is executed.
This is my reducer :
import update from "immutability-helper";
import Axios from "axios";
export default function getTasksReducer(state = [], action) {
switch (action.type) {
case "GET_TASKS":
let data = Axios.get(
"http://localhost:8080/api/taskExecution?&search=&page=1&size=8"
).then(response => {
if (response.status !== 200) {
console.log("LOADING");
} else {
return response.data;
}
});
let tasks = [];
data.then(response => {
tasks = response;
});
console.log(tasks);
return tasks;
default:
return state;
}
}
I need some help in this code and also in the conception of it, I mean where should I change my loader state and so forth.
I guess you can spent some time in understanding Promises and async/awiat
For eg: if you do this, your console will have all tasks listed.
data.then(response => {
tasks = response;
console.log(tasks);
});
Reason for that is the function you are passing to .then function of a promise is not executed immediately, its executed once a Promise is resolved(In your case after finishing execution of the http request.)
To get a more line by line execution feel you can use async/await
let tasks = await Axios.get("http://localhost:8080/api/taskExecution?&search=&page=1&size=8")
.then(response => {
if (response.status !== 200) {
console.log("LOADING");
} else {
return response.data;
}
});
console.log(tasks);
return tasks;
The catch is that you can use await only inside an async function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Alright buddy, so there're a few things I want to raise up:
First, you should always use your reducers only return new state to Redux, and that's it. You just merge new data that comes from actions, with your old state and return it back. And you can't use Promise or async/await in there because Redux doesn't and won't support that behavior.
Second, all the business logic should be placed in your actions. Data fetching(like in your case), computations and that kind of stuff needs be in actions. And now, you've come to the point where you, most likely, should start using libraries like redux-thunk or redux-saga to handle asynchronous operations within your actions. redux-thunk is less complicated than redux-saga, but redux-saga empowers you with a lot of cool features yet it could a bit complicated.
You can go big or start small with these libs, either way, they will force you to move you data fetching into your action. If you want support loading of data, then just dispatch actions that tell Redux: "I'm loading data" or "I got an error while loading data" or "I've loaded data". And when that action comes in, update your store, show loader, data or an error if you need. You can take a look on this or that example of using redux-thunk for data fetching, there's everything you need to get started with async actions.
Hope it helps <3
As mentiond as in the other answers, I would setup my reducer only to handle state update and the action creator function to fetch data. Here is the minimal starting point if I were to do it. you can use the loading state to display loading spinners or progress bars.
my action creator function with the individual actions:
export const GET_TASKS_START = "GET_TASKS";
export const GET_TASKS_SUCCESS = "GET_TASKS_SUCCESS";
export const GET_TASKS_FAILURE = "GET_TASKS_FAILURE";
export const getData = () => dispatch => {
dispatch(GET_TASKS_START);
axios
.get("http://localhost:8080/api/taskExecution?&search=&page=1&size=8")
.then(response => {
dispatch({ type: GET_TASKS_SUCESS, payload: response.data });
})
.catch(err => {
dispatch({ type: GET_TASKS_FAILURE, payload: err.response });
});
};
and the reducer would handle the state update as follows:
import {
GET_TASKS_SUCCESS,
GET_TASKS_FAILURE,
GET_TASKS_START
} from "./Action.js";
const initialState = {
tasks: [],
error: null,
loading: false
};
export default function tasksReducer(state = initialState, action) {
switch (action.type) {
case GET_TASKS_START:
return {
...state,
loading: true
};
case GET_TASKS_SUCCESS:
return {
...state,
loading: false,
tasks: action.payload,
error: null
};
case GET_TASKS_FAILURE:
return {
...state,
loading: false,
error: action.payload
};
}
}
I would suggest console logging and checking the responses (data and errors ) and modify the two functions accordingly

Resources