is it fine to call firestore database update inside a redux-toolkit reducer with error handling? - reactjs

I have the following logic for a reducer inside one of my slices, the idea here is to make a call to firebase make an update then if every thing goes smoothly preform a redux state change, I want to guard against errors in api calls so if the call fails it doesn't show on ui, but is it a good idea to put the db logic inside a reducer ? or should instead preform this action inside the component and then make the dispatch ? it's either this or this in my case there is no other choice what would you recommend to do, I want to avoid bad practice yet achieve certain desired outcome ?
setDayType: (state, action: PayloadAction < {
bool: boolean;type: string
} > ) => {
// send the update to data base !!
const set = async() => {
const docRef = doc(db, 'some-collcetion', uid);
await updateDoc(docRef, {
[action.payload.type]: action.payload.bool,
});
};
// if it error out ! the dont preform sate change since copy on db might diffre !!!!
set().catch((error) => {
const errorCode = error.code;
alert('update has failed please try again');
});
// if previous is ok then preform change.
// redux state change
if (action.payload.type === 'day') {
state.day = action.payload.bool;
}
if (action.payload.type === 'week') {
state.week = action.payload.bool;
}
},

Related

RTK Query response state

I'm trying to convert some Axio code to RTK query and having some trouble. The 'data' response from RTK query doesn't seem to act like useState as I thought.
Original axio code:
const [ importantData, setImportantData ] = useState('');
useEffect(() => {
async function axiosCallToFetchData() {
const response = await axiosAPI.post('/endpoint', { payload });
const { importantData } = await response.data;
setImportantData(importantData);
}
axiosCallToFetchData()
.then((res) => res)
.catch((error) => console.log(error));
}, []);
const objectThatNeedsData.data = importantData;
New RTK Query code
const { data, isSuccess } = useGetImportantDataQuery({ payload });
if(isSuccess){
setImportantData(data.importantData);
}
const objectThatNeedsData.data = importantData;
This however is giving me an infinite render loop. Also if I try to treat the 'data' object as a state object and just throw it into my component as:
const objectThatNeedsData.data = data.importantData;
Then I get an undefined error because it's trying to load the importantData before it's completed. I feel like this should be a simple fix but I'm getting stuck. I've gone through the docs but most examples just use the if statement block to check the status. The API calls are being made atleast with RTK and getting proper responses. Any advice?
Your first problem is that you always call setImportantData during render, without checking if it is necessary - and that will always cause a rerender. If you want to do that you need to check if it is even necessary:
if(isSuccess && importantData != data.importantData){
setImportantData(data.importantData);
}
But as you noticed, that is actually not necessary - there is hardly ever any need to copy stuff into local state when you already have access to it in your component.
But if accessing data.importantData, you need to check if data is there in the first place - you forgot to check for isSuccess here.
if (isSuccess) {
objectThatNeedsData.data = data.importantData;
}
All that said, if objectThatNeedsData is not a new local variable that you are declaring during this render, you probably should not just modify that during the render in general.

React: Is useEffect() firing before the response arrives from the function I called?

Does useEffect fire before the response for data arrives, hence the reason I get undefined immediately first before getting the proper data shortly after?
My logic is
LoginScreen and set token (jwt)
Define isLoggedIn state then conditionally render HomeScreen
OnHomeScreen call getUserDetails() using the provided JWT
HomeScreen:
const {token, userInfo} = useContext(LoginContext)
const [stateUserInfo, setStateUserInfo] = userInfo
const [stateToken, setStateToken] = token
async function getUserDetails() {
const data = await axios.get(apiValidate+'&JWT='+stateToken)
setStateUserInfo(data.data.data) //does this line run regardless if the response arrives or not?
}
useEffect(() => {
getUserDetails()
},[])
useEffect(() => {
console.log(stateUserInfo) //returns undefined 1st, then the proper object shortly after
},[stateUserInfo])
I 'fix' my code by doing:
useEffect(() => {
if(stateUserInfo) {
console.log(stateUserInfo) }
},[stateUserInfo])
This works but I think it's ugly?
On a deeper question, I feel like I'm trying to do "synchronous" logic with async data! Is there a more elegant way to do this? Maybe my logic is flawed?
useEffect will run on initial render as well. That means both of your effects will run the first time, but the second one will run when the stateUserInfo updates as well.
You can fix this by storing a boolean that validates if you're on the initial render or on subsequent ones. This way you can combine both effects into a single one.
Also, for the commented question in your code: YES, it will run regardless of the return of the server. You should add that request in a try catch in case it throws an error, and also check the response in case it is unexpected.
const {token, userInfo} = useContext(LoginContext)
const [stateUserInfo, setStateUserInfo] = userInfo
const [stateToken, setStateToken] = token
// store a boolean to determine if you're on initial render
const afterUpdate = useRef(false)
async function getUserDetails() {
const data = await axios.get(apiValidate+'&JWT='+stateToken)
setStateUserInfo(data.data.data) // this line WILL run regardless of the answer of the previous line, so you should validate the response first before assigning it.
}
useEffect(() => {
if (afterUpdate.current) {
console.log(stateUserInfo)
} else {
afterUpdate.current = true
getUserDetails()
}
},[stateUserInfo])

How do I trigger a call to dispatch using React Hooks?

I have a save function in a component that makes some calls to an API and changes my local state based on the response. I then want to dispatch an action once AND ONLY ONCE these calls to setState are complete e.g.
const [myData, setMyData] = useState([{id: 0, name: "Alex"}]);
const save = async () => {
if (someCondition) {
let response = await axios.get("/someroute");
if (response.status === 200) {
setMyData([...myData, response.data])])
}
}
if (someOtherCondition) {
let response2 = await axios.get("/someotherroute");
if (response2.status === 200) {
setMyData([...myData, response2.data])])
}
}
dispatch(myAction(myData));
}
Now I know that useEffect should be the way to go about this. e.g.
useEffect(() => {
dispatch(myAction(myData));
}, [myData]);
however i'm changing myData at other places in the component, and I only want to dispatch the action once save() has been called and the setMyData calls have finished. I can't think of a condition to check for in useEffect() except maybe adding some kind of flag to myData, like an object saying "Ready to save", but it feels like there should be a simpler way.
Massively appreciate any help on this one!
What do you mean by changing myData at other places? Are you changing myData by fetching it from somewhere? Because if you don't, setMyData will do its job pretty straightforward and fast. So your save function won't need to listen it.
If you change myData as a result of some other fetching and save function should wait it. Then story can little bit complicated. You might like to check middlewares and redux sagas.
I have hard time to understand your scenario and use case; but if state overwriting is your concern during consecutive fetch actions then saga middleware can take care of it.
For example you can create some saga middleware with takeLatest so that it will take last action that dispatched and kill the previous one (not waiting) that ongoing; so you would avoid data overwrite that might occur due side effect. Or similarly you might want to use takeEvery, which will queue every action for setting myData and they will wait each other to end.
check more : https://redux-saga.js.org/
and usage example : https://codesandbox.io/s/385lk3x6v1?file=/src/sagas/index.js
It's looks like you need to use useReduce instead and then you are able to detect when any of the props change with the useEffect:
const initialState = {
id: null,
name: null,
...
}
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'CHANGE-NAME':
return { ...state, name: action.value }
default:
throw new Error('Type not defined')
}
}, initialState)
useEffect(() => {
// Notify changes!!
}, [state.name]);
...
const myFun = async () {
// do something
dispatch({ type: 'CHANGE-NAME', value: 'Hello world' })
}
Also you can add other props to your initial state, for example if you need a specific flag to detect an action once :)
Regards

React Hooks: Referencing data that is stored inside context from inside useEffect()

I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!

What is the best approach of writing redux actions that need data from other actions

I have made some research about possible ways to do it, but I can't find one that uses the same architecture like the one in the app I'm working on. For instance, React docs say that we should have a method which makes the HTTP request and then calls actions in different points (when request starts, when response is received, etc). But we have another approach. We use an action which makes the HTTP call and then dispatches the result. To be more precise, my use case is this:
// action to get resource A
getResourceA () {
return dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
};
}
// another action which needs data from resource A
getSomethingElseByIdFromA (aId) {
return async dispatch => {
const result = await axiosClient.get(`someLink/${aId}`);
dispatch({
type: GET_SOMETHING_BY_ID_FROM_A,
payload: result
});
};
}
As stated, the second action needs data from the first one.
Now, I know of two ways of doing this:
return the result from the first action
getResourceA () {
return async dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
return result;
};
}
// and then, when using it, inside a container
async foo () {
const {
// these two props are mapped to the getResourceA and
// getSomethingElseByIdFromA actions
dispatchGetResourceA,
dispatchGetSomethingElseByIdFromA
} = this.props;
const aRes = await dispatchGetResourceA();
// now aRes contains the resource from the server, but it has not
// passed through the redux store yet. It's raw data
dispatchGetSomethingElseByIdFromA(aRes.id);
}
However, the project I'm working on right now wants the data to go through the store first - in case it must be modified - and only after that, it can be used. This brought me to the 2nd way of doing things:
make an "aggregate" service and use the getState method to access the state after the action is completed.
aggregateAction () {
return await (dispatch, getState) => {
await dispatch(getResourceA());
const { aRes } = getState();
dispatch(getSomethingElseByIdFromA(aRes.id));
};
}
And afterward simply call this action in the container.
I am wondering if the second way is all right. I feel it's not nice to have things in the redux store just for the sake of accessing them throughout actions. If that's the case, what would be a better approach for this problem?
I think having/using an Epic from redux-observable would be the best fit for your use case. It would let the actions go throughout your reducers first (unlike the mentioned above approach) before handling them in the SAME logic. Also using a stream of actions will let you manipulate the data throughout its flow and you will not have to store things unnecessary. Reactive programming and the observable pattern itself has some great advantages when it comes to async operations, a better option then redux-thunk, sagas etc imo.
I would take a look at using custom midleware (https://redux.js.org/advanced/middleware). Using middleware can make this kind of thing easier to achieve.
Something like :
import {GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR } from '../actions/actionTypes'
const actionTypes = [GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR ]
export default ({dispatch, getState}) => {
return next => action => {
if (!action.type || !actionTypes.includes(action.type)) {
return next(action)
}
if(action.type === GET_RESOURCE_A){
try{
// here you can getState() to look at current state object
// dispatch multiple actions like GET_RESOURCE_B and/ or
// GET_RESOURCE_A_SUCCESS
// make other api calls etc....
// you don't have to keep stuff in global state you don't
//want to you could have a varaiable here to do it
}
catch (e){
} dispatch({type:GET_RESOURCE_A_ERROR , payload: 'error'})
}
}
}

Resources