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

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

Related

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

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;
}
},

How to chain redux actions using returned result of the previous action?

I'm building an app in React Native, and using Redux with redux-persist to act as on device database.
The crux of the issue is, how do I return the result of a redux action, to then dispatch another action with this data? Read on for more specifics.
The user can create custom habit types. When doing so, I dispatch an action to create a habit type in the store (e.g. "running"). This action generates a new unique UUID for the habit type. I then want to add this newly created habit type to a routine (e.g. "morning routine"), and so I need to receive back the UUID of the habit type and call another dispatch to add it to the routine.
I'm using immer to make manipulating the state in my reducers simpler, and have this code (simplified example):
import produce from "immer";
const userReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_CUSTOM_HABIT_TYPE: {
return produce(state, draftState => {
const newHabitType = {
id: generateUuid(),
name,
};
draftState.customHabitTypes.push(newHabitType);
return draftState;
});
}
}
};
I'm then dispatching it in my component, like so (simplified):
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
name: "running",
});
How can I then say, after creating this new habit type, to dispatch another action and add it to my routine?
I've looked at redux-thunk and redux-saga, and spent hours reading about these and trying to get redux-thunk to work, but to no avail. I'm sure this must be simple, but I'm coming up blank, and so maybe others are too, hence this post.
A very simple solution would be to generate the unique id before dispatching the action.
Example
const newHabitType = {
id: generateUuid(),
name,
};
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
habit: newHabitType,
});
dispatch({
type: ADD_CUSTOM_HABIT_TO_ROUTINE,
habit: newHabitType.id,
});
Pros
You no longer need to chain actions per se, you just need to dispatch them in order.
This preserves one of the most important Redux guidelines: your reducer should not have any side effects (in your case, generating a random id). reference
Cons
If you create the new habits in multiple places, you will have to generate the unique ids in every place where you dispatch the action. This might lead to repeated code. The solution to this would be to encapsulate the whole logic for creating the habits to a single component and then reuse this component everywhere.
Actions do not return data per se, the are simply objects which mutate the store based on the rules defined in the reducer. Two possible solutions:
Option A, create a composite action.
const compositeAction = args => {
return dispatch => {
return someAsyncCall(args).then(response => {
dispatch(addCustomHabitat(response))
dispatch(followUpAction())
}
}
}
const addCustomHabitat = response => {
return {
type: "ADD_CUSTOM_HABIT_TYPE",
data: response
}
}
const followUpAction = () => {
...another action...
}
Option B, connect the results of the first action to the dispatching component through react-redux and pass them to the second action.
import {connect} from 'react-redux';
const MyReactComponent = props => {
dispatch(addCustomHabitatTypeAction());
if(props.customHabitatType !== undefined)
dispatch(followUpAction(props.customHabitatType());
return (
...JSX here...
);
}
const mapStateToProps = state => {
return {
customHabitatType: state.userReducer.customHabitatType
}
}
connect(mapStateToProps)(MyReactComponent);
I hope this helps! Please excuse my abbreviated code and let me know if you have any questions.

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'})
}
}
}

If an action needs to use some current state, who should fetch it from store?

The title may not clear enough, please consider this example:
If I have a data table, which you can select multiple rows, and click action button like delete.
now in my actions.js:
(selectedRows is an array that contains the row indexes, getSelectedPostIds is a selector which will fetch and convert selectedRows to postIds)
import { getSelectedPostIds } from 'selectors'
export const deletePosts = () => (dispatch, getState) => {
// encapsulate the parameter `postIds` in action
const postIds = getSelectedPostIds(getState())
dispatch({ type: 'DELETE' })
deletePostsApi(postIds)
// .then(...)
// .catch(...)
}
is there any problem in this design? Or I should avoid using getState in an action and just pass postIds as a parameter to the action:
export const deletePosts = postIds => dispatch => {
dispatch({ type: 'DELETE' })
deletePostsApi(postIds)
// .then(...)
// .catch(...)
}
The only difference is that who should fetch the state (use the selector) from store, 1. action or 2. the component who will dispatch the action (via mapStateToProps).
I'm not sure about the approach 1, and the approach 2 will make my component contains a lot of props just because some actions need them (or maybe this is totally fine?).
thanks.
This might be a matter of taste. I usually like to access getState directly since, as you point out, avoids passing a lot of props. And by doing that the action is easier to integrate in different components (I just need to call it instead of additionally editing the mapStateToProps). Also, since in the end both ways are accessing the global store, the intended redux data flow is not compromised in any way.
You can use redux-thunk if you want to work with state in your action creators. :)
https://github.com/gaearon/redux-thunk
function yourActionCreator() {
// Redux-thunk will catch all action creators that return functions
return (dispatch, getState) => {
// u can use state here
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
// Dispatch your action creator as you would normally do
dispatch(increment());
};
}

Resources