Accessing updated state inside the same function - javascript

I am having trouble accessing state after saving it, when calling the same function. My state is setup like this:
const perLanguageDefault = {
calculated_skill: null,
display_skill: null,
published: false,
premium: false,
id: null,
};
const [perLanguage, setPerLanguage] = useState(perLanguageDefault);
I have a save function inside one of my components as such:
const _savePerLanguageArticle = async () => {
if (perLanguage.id === null) {
await _createPerLanguage();
} else if (perLanguage.id !== null) {
await _updatePerLanguage();
}
if (props.sentences.length !== 0 && perLanguage.id) {
await _iterateSentences();
}
await _saveRulesArticle();
};
If a perLanguage object doesn't exist, I create one in the database, and set it in state as such:
const _createPerLanguage = async () => {
const body = {
published: perLanguage.published,
premium: perLanguage.premium,
article: props.article.id,
language: props.lang_id,
calculated_skill: perLanguage.calculated_skill,
display_skill: perLanguage.display_skill,
};
try {
const response = await api.post("perLanguageCreateAPI", {
body: body,
});
await setPerLanguage(response.data);
} catch (e) {
console.log(e.response);
}
};
_saveRulesArticle above looks like this:
const _saveRulesArticle = async () => {
const newRules = props.rules.map((rule) => {
let ruleobj = {};
ruleobj["rule"] = rule;
ruleobj["per_language"] = perLanguage.id;
return ruleobj;
});
try {
const response = await api.post("ruleMappingCreateAPI", {
body: newRules,
});
} catch (e) {
console.log(e.response);
return;
}
};
However, because it's all within the same top function, _saveRulesArticle doesn't get access to the new perLanguage variable, it isn't updated, even though _createPerLanguage supposedly creates it and sets it.
I suspect there's some relation to this stack overflow page:
useState set method not reflecting change immediately
Most particularly the highest upvoted answer. I understand how that works for useEffect, but how would it work here? How do I update state inside a function, and then later inside that function, access the same updated state? Is this possible? Is this an anti-pattern?

Related

How to set object hook properly in React?

I have these states:
const [maintenanceTypeId, setMaintenanceTypeId] = useState(null);
const [maintenanceId, setMaintenanceId] = useState(null);
const [addedId, setAddedId] = useState({
mainId : null,
mainTypeId : null,
});
I set states in these functions:
//setMaintenanceTypeId
//setAddedId
const addNewMainTypesList = async () => {
try {
const { data } = await axios.post(`${serverBaseUrl}/insertRowtoMainTypes`, {
description: newMaintenanceList.description,
title: newMaintenanceList.title
});
setMaintenanceTypeId(data[0]?.id);
console.log("MaintenanceTypeId", maintenanceTypeId);
//console.log("inserted type id",data[0]?.id);
setAddedId({...addedId, mainTypeId : maintenanceTypeId});
} catch (err) {
throw err;
}
const maintenanceList = await getMainTypes();
// console.log("main list", maintenanceList);
setMaintenanceList(maintenanceList);
};
//setMaintenanceId
//setAddedId
const addNewMainTypes = async () => {
try {
const { data } = await axios.post(`${serverBaseUrl}/insertRowtoMain`, {
nodeid: newMaintenance.nodeid,
maintenancetype: newMaintenance.maintenancetype,
personnel: newMaintenance.personnel,
process: newMaintenance.process,
date: newMaintenance.date,
});
setMaintenanceId(data[0]?.id);
console.log("MaintenanceId", maintenanceId);
setAddedId({...addedId, mainId : maintenanceId});
//console.log("inserted main id",data[0]?.id);
} catch (err) {
throw err;
}
I am console logging the addedId state in a submitHandler.
const submitHandler = (e) => {
e.preventDefault();
addNewMainTypesList();
getMain();
getMainTypes();
addNewMainTypes();
console.log("addedId",addedId);
}
Here is the console. As you can see, I can not get the maintenanceTypeId. How can I fix this?
setState is an asynchronous function. In other words, it won't directly update a given state.
In your case setMaintenanceTypeId is not directly updating maintenanceTypeId.
In order, to make it update directly, use inside of it an anonymous function, like this:
setMaintenanceTypeId(mTypeId => data[0]?.id);
It would be also better if you do the same for setAddedId (though not mandatory):
setAddedId(addedId => {...addedId, mainTypeId : maintenanceTypeId});
Let me know if this still doesn't work.

How do I get user details in Firebase Storage?

I'm a new programmer and very new to firebase and I'm trying to get the current user files info to display on the screen, it seems that my problem is that I can get the URL and the metadata separately, how do I combine them? how can I take everything at once?
I need to show the file name, date, time, link to download.
const getUserFiles = async () => {
if (!userUID) {
return null;
}
let listRef = storageRef.child(userUID);
listRef.listAll().then(res => {
// res.prefixes.forEach((item) => {
// });
res.items.forEach(item => {
item.getMetadata().then(item => {
var file = {
name: item.name.toString(),
timeCreated: item.timeCreated.toString(),
link: '',
};
myFiles.push(file);
});
});
res.items.forEach(item => {
let counter = 0;
item.getDownloadURL().then(url => {
myFiles[counter].link = url.toString();
});
});
});
console.log(myFiles);
};
the current method don't work! and notice that the userUID its only the uid without the user (local state)
Thanks!
The problem is with the asynchronous calls. You're making an async call in forEach and forEach expects a synchronous function.
You can change the logic to use for-of instead.
See below:
const getUserFiles = async () => {
if (!userUID) {
return null;
}
let listRef = storageRef.child(userUID);
const res = await listRef.listAll();
for (const itemRef of res.items) {
const itemMetadata = await itemRef.getMetadata();
const url = await itemRef.getDownloadUrl();
var file = {
name: itemMetadata.name.toString(),
timeCreated: itemMetadata.timeCreated.toString(),
link: url,
};
myFiles.push(file);
}
console.log(myFiles);
}

React useSelector Value Not Changed Inside Async Function

I want to use the useSelector state to send the new bugs state to a server inside an async function, but the value does not change. What am I doing wrong?
When submitting a form I want to update the bugs state and send it to the server
Inside an async function I do dispatch(updateBugs(newBug)) and the state is updated so this works
I get the state with const { bugs } = useSelector((state) => state.bugs); and the state seems to be updated
When I send the updated bugs list with await sendUpdatedBugsToServer(bugs); inside the async function, the bugs state seems to be the old one.
■bug-actions.js
export const sendUpdatedBugsToServer = (newBugsList) => {
const storeData = async (newBugsList) => {
const response = await fetch(`${databaseURL}/bugs.json`, {
method: 'PUT',
body: JSON.stringify(newBugsList),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error('cannot store new bug');
}
};
try {
storeData(newBugsList);
} catch (error) {
console.error(error.message);
}
};
■UpdateBugs.js
const EditBug = () => {
const dispatch = useDispatch();
const { bugs } = useSelector((state) => state.bugs); //new bug state reflected here
const submitUpdatedBugs = async () => {
const newBug = {
title: enteredTitle,
details: enteredDetails,
steps: enteredSteps,
version: enteredVersion,
priority: enteredPriority,
assigned: enteredAssigned,
creator: enteredCreator,
id: enteredId,
};
await dispatch(updateBugs(newBug)); //update bugs state
await sendUpdatedBugsToServer(bugs); //new bug state not reflected here
}
};
const submitUpdatedBugHandler = (e) => {
e.preventDefault();
submitUpdatedBugs();
};

Why can't I access this variable from the state VUEX 4

I of course tried it with out the state.groupCrud.group and still no go. Is my syntax correct. I am using vuejs3 i will provide some screenshots of the object.
This is the State Object:
const state = () => ({
group: {}, //This is the one i am trying to access.
groups: [],
loading: false,
error: null,
success: false,
});
I am getting the data from api like this:
async getGroup({ commit }, id) {
try {
await commit('axiosSingleDataBegin');
const query = await DataService.get(`/groups/unique/${id}`);
await commit('axiosSingleDataSuccess', query.data.data.group);
} catch (err) {
await commit('axiosSingleDataErr', err);
}
},
And I am setting it to the state like this:
axiosSingleDataSuccess(state, data) {
state.loading = false;
state.group = data;
},
This is how i am using the state
setup() {
const { state, dispatch } = useStore();
const group = state.groupCrud.group;
const { params } = useRoute();
onMounted(() => {
dispatch('getGroup', params.id);
console.log(group); //doesnt work
});
// ...
},
This is the console.log(state.groupCrud); Object
This is the console.log(state.groupCrud.group) Object
I am getting a empty Proxy object when i try to use the state.groupCrud.group although the object is not empty

Using reducer state inside useEffect

Hello All 👋🏻 I have a question about our favorite Hooks API!
What am I trying to do?
I am trying to fetch photos from some remote system. I store the blob urls for these photos in my reducer state keyed by an id.
I have a helper function wrapped in the memoized version returned by the useCallback hook. This function is called in the useEffect I have defined.
The Problem ⚠️
My callback a.k.a the helper function depends on part of the reducer state. Which is updated every time a photo is fetched. This causes the component to run the effect in useEffect again and thus causing an infinite loop.
component renders --> useEffect runs ---> `fetchPhotos` runs --> after 1st photo, reducer state is updated --> component updates because `useSelector`'s value changes ---> runs `fetchPhotos` again ---> infinite
const FormViewerContainer = (props) => {
const { completedForm, classes } = props;
const [error, setError] = useState(null);
const dispatch = useDispatch();
const photosState = useSelector(state => state.root.photos);
// helper function which fetches photos and updates the reducer state by dispatching actions
const fetchFormPhotos = React.useCallback(async () => {
try {
if (!completedForm) return;
const { photos: reducerPhotos, loadingPhotoIds } = photosState;
const { photos: completedFormPhotos } = completedForm;
const photoIds = Object.keys(completedFormPhotos || {});
// only fetch photos which aren't in reducer state yet
const photoIdsToFetch = photoIds.filter((pId) => {
const photo = reducerPhotos[pId] || {};
return !loadingPhotoIds.includes(pId) && !photo.blobUrl;
});
dispatch({
type: SET_LOADING_PHOTO_IDS,
payload: { photoIds: photoIdsToFetch } });
if (photoIdsToFetch.length <= 0) {
return;
}
photoIdsToFetch.forEach(async (photoId) => {
if (loadingPhotoIds.includes(photoIds)) return;
dispatch(fetchCompletedFormPhoto({ photoId }));
const thumbnailSize = {
width: 300,
height: 300,
};
const response = await fetchCompletedFormImages(
cformid,
fileId,
thumbnailSize,
)
if (response.status !== 200) {
dispatch(fetchCompletedFormPhotoRollback({ photoId }));
return;
}
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
dispatch(fetchCompletedFormPhotoSuccess({
photoId,
blobUrl,
}));
});
} catch (err) {
setError('Error fetching photos. Please try again.');
}
}, [completedForm, dispatch, photosState]);
// call the fetch form photos function
useEffect(() => {
fetchFormPhotos();
}, [fetchFormPhotos]);
...
...
}
What have I tried?
I found an alternative way to fetch photos a.k.a by dispatching an action and using a worker saga to do all the fetching. This removes all the need for the helper in the component and thus no useCallback and thus no re-renders. The useEffect then only depends on the dispatch which is fine.
Question ?
I am struggling with the mental modal of using the hooks API. I see the obvious problem, but I am not sure how could this be done without using redux middlewares like thunks and sagas.
Edit:
reducer function:
export const initialState = {
photos: {},
loadingPhotoIds: [],
};
export default function photosReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case FETCH_COMPLETED_FORM_PHOTO: {
return {
...state,
photos: {
...state.photos,
[payload.photoId]: {
blobUrl: null,
error: false,
},
},
};
}
case FETCH_COMPLETED_FORM_PHOTO_SUCCESS: {
return {
...state,
photos: {
...state.photos,
[payload.photoId]: {
blobUrl: payload.blobUrl,
error: false,
},
},
loadingPhotoIds: state.loadingPhotoIds.filter(
photoId => photoId !== payload.photoId,
),
};
}
case FETCH_COMPLETED_FORM_PHOTO_ROLLBACK: {
return {
...state,
photos: {
...state.photos,
[payload.photoId]: {
blobUrl: null,
error: true,
},
},
loadingPhotoIds: state.loadingPhotoIds.filter(
photoId => photoId !== payload.photoId,
),
};
}
case SET_LOADING_PHOTO_IDS: {
return {
...state,
loadingPhotoIds: payload.photoIds || [],
};
}
default:
return state;
}
}
You could include the photoIdsToFetch calculation logic into your selector function, to reduce the number of renders caused by state change.
const photoIdsToFetch = useSelector(state => {
const { photos: reducerPhotos, loadingPhotoIds } = state.root.photos;
const { photos: completedFormPhotos } = completedForm;
const photoIds = Object.keys(completedFormPhotos || {});
const photoIdsToFetch = photoIds.filter(pId => {
const photo = reducerPhotos[pId] || {};
return !loadingPhotoIds.includes(pId) && !photo.blobUrl;
});
return photoIdsToFetch;
},
equals
);
However the selector function isn't memoized, it returns a new array object every time, thus object equality will not work here. You will need to provide an isEqual method as a second parameter (that will compare two arrays for value equality) so that the selector will return the same object when the ids are the same. You could write your own or deep-equals library for example:
import equal from 'deep-equal';
fetchFormPhotos will depend only on [photoIdsToFetch, dispatch] this way.
I'm not sure about how your reducer functions mutate the state, so this may require some fine tuning. The idea is: select only the state from store that you depend on, that way other parts of the store will not cause re-renders.

Categories

Resources