am having a small challenge setting up a proper initialization for my react app.
Having some settings in localstorage, I'd want to populate them with the data coming from an axios get request, before ANYTHING else in the app happens (e.g. initialization of the rest of the constructor lines).
What happens currently is that whilst the line executes, the code continues and reads the 'old' localstorage, which is not yet updated:
APP.JS
...
this.readSettingsDB(false);
this.state = {
chartTimeAggregationType: JSON.parse(localStorage.getItem('timeAggregationType')) // <-- This value is still old
dataLoading: true,
...
readSettingsDB(refreshPage) {
data = axios.get('/someurl').then(response => {
localStorage.setItem('timeAggregationType': reponse.time)
});
}
Where are you using refreshPage? Here is how I would handle your issue.
readSettingsDB = async (refreshPage) => { // arrow function make async
const data = await fetch('/someurl'); // use fetch
const response = await data.json();
localStorage.setItem('timeAggregationType': reponse) // set storage
});
}
If you want to setState first, setState comes with a callback.
readSettingsDB = async (refreshPage) => { // arrow function make async
const data = await fetch('/someurl'); // use fetch
const response = await data.json();
this.setState({
timeAggregationType: reponse
}, () => {
localStorage.setItem('timeAggregationType': this.state.timeAggregationType) // set storage
});
})
}
Related
I have a problem that I can't seem to figure out why is happening.
I created a custom hook that makes an API call to the BE every time a "filter" or "page" is changed.
If the user changes some filter before the response is received, I successfully cancel the API call with the "old" filters and trigger the new correct api call with the updated filters.
I have a Redux store, and in it I have a "loading" property that keeps track if the API call si pending or finished. Just normal, plain no fancy redux store implementation with React.
Below is the custom hook that I created, that works as expected in terms of "canceling" the request when the filters/page change. I
export const useGetPaginatedDataWithSearch = (getFunction, page, searchValue, filters={}) => {
const dispatch = useDispatch();
useEffect(()=>{
const controller = new AbortController();
const abortSignal = controller.signal;
// to the initial filters,
// we want to add the page and search value
let params = {...filters};
params.page = page
params.search = searchValue;
dispatch(getFunction(params, abortSignal))
return () => {
controller.abort()
}
}, [dispatch, page, searchValue, getFunction, filters])
}
The "getFunction" parameter, is something like this.
export const getDrivers = (params= {}, abortSignal, endpoint) => {
return (dispatch) => {
dispatch(driversApiCallStart());
const url = endpoint ? endpoint : '/drivers';
return api.get(url, {params:params, signal: abortSignal})
.then((response) => {
let data = response.data;
dispatch(getDriversSuccess(data));
})
.catch((error) => {
const actionType = 'getDrivers';
throw parseError(dispatch, driversApiCallFail, error, actionType);
});
};
};
driversApiCallStart and parseError are some functions used across all the actions for the drivers store slice, and just update the "loading" or "error" state in redux store. Redux store uses the "immer" package, this is where the "draft" comes from in the below code.
case actionTypes.DRIVERS_API_CALL_START:
draft.loading = true;
break;
case actionTypes.DRIVERS_API_CALL_FAIL:
draft.loading = false;
draft.error = {
apiCall: action.payload.apiCall,
message: action.payload.message
};
break;
Now my problem is the following. This is the expected normal order of the "actions/trigers" whatever:
the cancel request is triggered when the filters change (OK)
cancel request goes in the .catch((error) block (OK)
.catch((error) block trigers "DRIVERS_API_CALL_FAIL" => draft.loading = false => spinner stops (OK)
new api call is made with the new filters "DRIVERS_API_CALL_START" => draft.loading = true; => spinner start (OK)
My problem is that the order in the app is:
1 => 2 => 4 => 3 (spinner is not displayed although the API call is "in progress").
Please check below print screen:
redux order in the app
Expected order behavior:
1 => 2 => 3 => 4 (spinner to be displayed white the API call is "in progress").
I am having trouble with accessing the data after fetching it with SecureStore in Expo for react-native.
Here is the simple code:
const infoofuser = SecureStore.getItemAsync('userInfo').then(value =>
console.log(`this is the vlaue from infouser: ${value}`),
);
console.log(`infoouser: ${JSON.stringify(infoofuser)}`);
the first infoofuser constant definition returns the object of the intended data.
console.log(`infoouser: ${JSON.stringify(infoofuser)}`);
however returns {"_U":0,"_V":0,"_W":null,"_X":null} which U understand is a promise. I would like to simply get the data that comes from the SecureStore call and use it to set my initialState in redux.
const infoofuser = SecureStore.getItemAsync('userInfo').then(value =>
value
);
this does not work either to access the data
You can use async method using async/await. Try this:
const userInfo = useSelector(state => state.userInfo);
const getData = async () => {
try {
const infoofuser = await SecureStore.getItemAsync('userInfo');
console.log('infoofuser:', infoofuser)
/// strore on redux
} catch (err) {
// handle error
}
}
useEffect(() => {
getData()
}, [])
if (!userInfo) return null
//render something else
You can check the Expo Secure Store docs for reference.
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.
Initially the initialFetch is true, so whenever the component renders graphql and axios fetch data from db. Then initialFetch is set to false.
Once an event is added to db via graphql and axios there added state variable is set to true. Since useEffect depends on added it should re-render the component and should fetch the data from db. But for some reason it fails as I mentioned below axios fails at communicating with the server.
Note! I Used GraphQL for fetching data from MongoDB
const [added, setAdded] = useState(false)
const [initialFetch, setInitialFetch] = useState(true)
useEffect(() => {
const fetchEvents = () => {
console.log('inside fetchEvents()')
const headers = {
'Content-Type': 'application/json'
}
const requestBody = {
query: `
query {
events {
_id
title
description
price
}
}
`
}
const body = JSON.stringify(requestBody)
console.log('awaiting for events from db')
axios.post('http://localhost:5000/graphql', body, {headers}).then((res) => {
console.log('events fetched from db')
setEvents(res.data.data.events)
}).catch((err) => {
console.log(err)
})
}
if (initialFetch) {
setInitialFetch(false)
console.log('initial fetch')
fetchEvents()
}
if (added) {
setAdded(false)
console.log('added, fetching again')
fetchEvents()
}
}, [added, initialFetch])
Here axios fails to add data to db and catch(err) block is executed after waiting over 2 minutes and the app crashes. The below code where axios posts data continuously keeps failing every time I try.
const handleConfirm = () => {
// request to backend
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authContext.token}`
}
const requestBody = {
query: `
mutation {
createEvent(title: "${title}", description: "${desc}", price: ${price}, date: "${date}") {
_id
title
description
price
}
}
`
}
const body = JSON.stringify(requestBody)
console.log('adding to db')
axios.post('http://localhost:5000/graphql', body, {headers}).then((res) => {
setAdded(true)
console.log('added item to db')
console.log(res.data)
}).catch((err) => {
console.log(err)
})
}
the initial fetch happens and I add data to db. After adding to db I should be re-fetched the events from db, instead that re-render fails and the app crashes.
That error is a classic sign that you have an infinite loop in your code. It is particularly common when that infinite loop is due to infinite recursion. Each time you call a function, JS has to allocate some stack space for the stackframe. If that function always calls itself, then it will keep trying to allocate more and more space until eventually, it crashes due to having no more memory available to allocate from.
Try removing the unguarded call to fetchEvents() in your useEffect() code block.
Yes, the app will definitely crash as it is updating the state recursively and indefinitely.
as every time the value of added is updated the useEffect is fired and as the useEffect fires it triggers axios.post which inturn again updates the state added
I'm playing around with a food recognition api.
I have a component with a local state called ingredients.
In the component, I have an input tag that accepts a file image upload and calls cameraHandler method onChange. The method uses FileReader to convert the image into Base64
Once the FileReader is finished encoding the image, the method calls a redux action fetchIngredientsFromImage to post the base64 image into a route to trigger to trigger an API call (to analyze the ingredients in the image).
The response is sent back to the front end, and used to update store.
So basically, the API call is successful, I get the data I need, and store is updated successfully. Great.
But what I also need to do, is update my local ingredients state. But I don't know how to wait for store to be updated before calling setState.
I've tried componentDidUpdate with if(this.props !== prevProps) methodToUpdateLocalState(), but this doesn't work because for some reason the component won't re-render after store is updated.. Turns out that everything inside componentDidUpdate runs first, and store is updated afterwards. I feel like also isn't necessary (probably).
I also tried .then the awaited readers inside cameraHandler, but .then is undefined.
I'd appreciate any input I could get. Really at a loss here, because I have the data, and I just need to somehow grab it so I can setState.
Component
class RecipesSearch extends Component {
state = {
ingredients: [], //need to update this after store is updated, but how?
};
cameraHandler = async (event) => {
const { fetchIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await fetchIngredientsFromImage(imgBase); //.then here is undefined
};
};
render(){
<input
className="form-check-input"
type="file"
name="camera"
accept="image/*"
onChange={this.cameraHandler}
/>
}
Actions
const fetchIngredientsFromImage = (imgBase) => async (dispatch) => {
const { data } = await axios.post(`/api/camera/`, { imgBase });
return dispatch(setIngredientsFromCamera(data)); //successfully updates store
};
as a workaround I made an axios.post call inside cameraHandler. Not proud of it, because I'd like to utilize store and keep it consistent with my other methods, but for the time being it'll do I guess.
cameraHandler = async (event) => {
// const { loadIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await axios
.post(`/api/camera/`, { imgBase })
.then((response) => this.setState({ ingredients: response.data }));
};
};