How can I make an action be reusable? ReactJS with Redux - javascript

I need to do a dynamic action. In other words, it can be reused for differents actions.
I tried to create a function that loads type and payload, but an error appears.
I'm trying make this function works:
export function getData(url, type) {
const request = Server.get(url)
return (dispatch) =>
request.then((response) => {
dispatch({
type: type,
payload: response.data
})
}).catch(function (error) {
console.log(error)
});
}
But I got an error when I call this function this way:
export function getClientes() {
Actions.getData('ClientesEFornecedores', GET_CLIENTES)
}
It's showing:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I'm Calling the getClientes() function this way:
function ClientesTable(props)
{
const dispatch = useDispatch();
const clientes = useSelector(({erpCliente}) => erpCliente.clientes.data);
useEffect(() => {
dispatch(Actions.getClientes());
}, [dispatch]);
How can I make an action be reusable?

Try something like this
export const getData=(url, type) =>async dispatch=>{
try{
const response = await Server.get(url);
dispatch({ type: type,payload: response.data })
} catch(err){
console.log(err)
}
}
getClientes function
export const getClientes=() => dbActions.getData('ClientesEFornecedores', GET_CLIENTES);

In fact I had almost succeeded.
All that remained was to return the function call.
This is the way that works:
export function getClientes() {
return dbActions.getData('ClientesEFornecedores', GET_CLIENTES)
}

Related

How to use custom react query hook twice in the same component?

I have a custom hook like so for getting data using useQuery. The hook works fine, no problem there.
const getData = async (url) => {
try{
return await axios(url)
} catch(error){
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData', () => getData(url), {onSuccess})
}
However, if I call this hook twice in my component it will only fetch data from the first call even with a different URL. (Ignore the comments typo, that's intentional)
The call in my component:
const { data: commentss, isLoading: commentsIsLoading } = useGetData(`/comments/${params.id}`)
const { data: forumPost, isLoading: forumPostIsLoading } = useGetData(`/forum_posts/${params.id}`)
When I console.log forumPost in this case, it is the array of comments and not the forum post even though I am passing in a different endpoint.
How can I use this hook twice to get different data? Is it possible? I know I can just call parallel queries but I would like to use my hook if possible.
Since useQuery caches based on the queryKey, use the URL in that name
const getData = async(url) => {
try {
return await axios(url)
} catch (error) {
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData' + url, () => getData(url), {
onSuccess
})
}
//........
const {
data: commentss,
isLoading: commentsIsLoading
} = useGetData(`/comments/${params.id}`)
const {
data: forumPost,
isLoading: forumPostIsLoading
} = useGetData(`/forum_posts/${params.id}`)

Async does wait for data to be returned in a redux-thunk function

I've being trying populate my redux store with data that comes from my mongo-db realm database.
Whenever I run the function below it will execute fine but the problem is data will be delayed and ends up not reaching my redux store.
My thunk function:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
return await Realm.open(config).then(async (projectRealm) => {
let syncItems = await projectRealm.objects("Item");
await syncItems.addListener((x, changes) => {
x.map(async (b) => {
console.log(b);
return b;
});
});
});
} catch (error) {
console.log(error);
throw error;
}
}
);
and my redux reducer:
extraReducers: (builder) => {
builder.addCase(addItemsTest.fulfilled, (state, { payload }: any) => {
try {
console.log("from add Items");
console.log(payload);
state.push(payload);
} catch (error) {
console.log(error)
}
});
}
Expected Results:
My redux store should have these data once addItemsTest return something:
[{
itemCode: 1,
itemDescription: 'Soccer Ball',
itemPrice: '35',
partition: 'partitionValue',
},
{
itemCode: 2,
itemDescription: 'Base Ball',
itemPrice: '60',
partition: 'partitionValue',
}
]
Actual Results:
Mixed Syntaxes
You are combining await/async and Promise.then() syntax in a very confusing way. It is not an error to mix the two syntaxes, but I do not recommend it. Stick to just await/async
Void Return
Your action actually does not return any value right now because your inner then function doesn't return anything. The only return is inside of the then is in the x.map callback. await syncItems is the returned value for the mapper, not for your function.
Right now, here's what your thunk does:
open a connection
get items from realm
add a listener to those items which logs the changes
returns a Promise which resolves to void
Solution
I believe what you want is this:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
const projectRealm = await Realm.open(config);
const syncItems = await projectRealm.objects("Item");
console.log(syncItems);
return syncItems;
} catch (error) {
console.log(error);
throw error;
}
}
);
Without the logging, it can be simplified to:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
const projectRealm = await Realm.open(config);
return await projectRealm.objects("Item");
}
);
You don't need to catch errors because the createAsyncThunk will handle errors by dispatching an error action.
Edit: Listening To Changes
It seems that your intention is to sync your redux store with changes in your Realm collection. So you want to add a listener to the collection that calls dispatch with some action to process the changes.
Here I am assuming that this action takes an array with all of the items in your collection. Something like this:
const processItems = createAction("processItems", (items: Item[]) => ({
payload: items
}));
Replacing the entire array in your state is the easiest approach. It will lead to some unnecessary re-renders when you replace item objects with identical versions, but that's not a big deal.
Alternatively, you could pass specific properties of the changes such as insertions and handle them in your reducer on a case-by-case basis.
In order to add a listener that dispatches processItems, we need access to two variables: the realm config and the redux dispatch. You can do this in your component or by calling an "init" action. I don't think there's really much difference. You could do something in your reducer in response to the "init" action if you wanted.
Here's a function to add the listener. The Realm.Results object is "array-like" but not exactly an array so we use [...x] to cast it to an array.
FYI this function may throw errors. This is good if using in createAsyncThunk, but in a component we would want to catch those errors.
const loadCollection = async (config: Realm.Configuration, dispatch: Dispatch): Promise<void> => {
const projectRealm = await Realm.open(config);
const collection = await projectRealm.objects<Item>("Item");
collection.addListener((x, changes) => {
dispatch(processItems([...x]));
});
}
Adding the listener through an intermediate addListener action creator:
export const addListener = createAsyncThunk(
"init",
async (config: Realm.Configuration, { dispatch }) => {
return await loadCollection(config, dispatch);
}
);
// is config a prop or an imported global variable?
const InitComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
dispatch(addListener(config));
}, [config, dispatch]);
/* ... */
}
Adding the listener directly:
const EffectComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
// async action in a useEffect need to be defined and then called
const addListener = async () => {
try {
loadCollection(config, dispatch);
} catch (e) {
console.error(e);
}
}
addListener();
}, [config, dispatch]);
/* ... */
}

async function not waiting for the await to end

I'm trying to add an async/await in my code to have the app wait for the execution of a function to invoke an other one, but I can't seem to be able to figure out where my code is wrong.
I have an API call in a redux action, this is the action
export const editSecondaryCategory = (text) => {
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
I want, in my component, to wait after this action is completed to call an other function to update the state. Ideally, it should be something like this (I guess?):
async updateCategory() {
// do the thing I have to wait for
const propsUpdate = await this.props.editSecondaryCategory(text);
// if you've done the thing then call the function to update the state
if (updatedCat) {
this.updateStateArray();
}
}
And I'd call this.updateCategory() inside my component after the user is done editing the information.
Clearly, this code does not work. I know it's wrong, I just don't know why. Basically I have no clue what to write inside updateCategory() to make this work.
Please help lol
You need to rewrite editSecondaryCategory function to make it async.
export async function editSecondaryCategory(text){
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
Currently, your function is not an async function do the above changes and check.

Action must be plain object. Use custom middleware

What would be the problem?
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
Configure Store:
export default configureStore = () => {
let store = compose(applyMiddleware(ReduxThunk))(createStore)(reducers);
return store;
}
Action
export const menulist = async ({ navigation }) => {
return async dispatch => {
try {
dispatch({ type: types.MENULIST_REQUEST_START })
let response = await menuListByCategories();
dispatch({ type: types.MENULIST_SUCCESS })
} catch (error) {
dispatch({ type: types.MENULIST_FAILED })
}
}
}
You are using it the wrong way,
in Redux every action must return an object, and this is a must!
so, your dispatch, which is a function, should be called this way.
Besides you only need to declare async the function which returns dispatch. The async keyword determines that the following function will return a promise. As your first function (menulist) is returning the promise returned by the second function (dispatch one) you don't have to specify it.
export const menulist = ({ navigation }) => async (dispatch) => {
try {
dispatch({ type: types.MENULIST_REQUEST_START })
let response = await menuListByCategories();
dispatch({ type: types.MENULIST_SUCCESS })
} catch (error) {
dispatch({ type: types.MENULIST_FAILED })
}
}
}

Unit testing Redux async actions

I am trying to add unit test cases to my redux actions.
I have tried this, this & this
I am using thunk, promise-middleware in my actions
one of my action is like this
export function deleteCommand(id) {
return (dispatch) => {
dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
}
unit test for this is
import configureMockStore from "redux-mock-store"
const middlewares = [thunk, promiseMiddleware()];
const mockStore = configureMockStore(middlewares)
it('creates DELETE_COMMAND_FULFILLED after deleting entry', (done) => {
nock('http://localhost:8081/')
.post('/delete',{
_id: 1
})
.reply(200, {})
const expectedActions = [{
type: 'DELETE_COMMAND_FULFILLED',
payload: {}
}]
const store = mockStore({
command: {
commands: []
}
})
store.dispatch(actions.deleteCommand({_id: 1}).then(function () {
expect(store.getActions())
.toEqual(expectedActions)
done();
})
})
I am using nock,redux-mock-store configured with thunk, promise middleware
It gives then of undefined
then I changed the action to return the promise, then I got unhandled promise reject exception, I added a catch to the action dispatch call.
Now I am getting Network Error because nock is not mocking the call.
Also tried moxios, changed axios to isomorphic-fetch, whatwg-fetch. Doesn't seem to be working
Where am I doing it wrong?.
I know It's from 2017,
But maybe someone will have the same issue.
So the problem is that the arrow function inside deleteCommand returns undefined.
That's why you get
It gives then of undefined
TLDR:
if you using redux-thunk with redux-promise-middleware
You must return the inner dispatch
here is the official redux-promise-middleware docs
The solution is very simple :
Option 1.
Add return inside the arrow function
export function deleteCommand(id) {
return (dispatch) => {
return dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
}
Option 2.
Remove the curly braces from the arrow function
export function deleteCommand(id) {
return (dispatch) =>
dispatch({
type: 'DELETE_COMMAND',
payload: axios.post(`${Config.apiUrl}delete`, { _id: id })
})
}
Now you can do
store.deleteCommand(<some id>).then(...)
In general arrow functions and return
Return a plain object
// option 1
const arrowFuncOne = () => {
return {...someProps}
}
// option 2
const arrowFuncTwo = () => ({
...someProps
})
// This means that you returning an expression
//same as
// return a result of other function
const arrowFuncCallFunOne = () => callSomeOtherFunc()
// or
const arrowCallFunkTwo = () => {return callSomeOtherFunc()}
// However
// wrong
const arrowCallFunkNoReturn = () => {callSomeOtherFunc()}
// in this case the curly braces are just the body of the function
// and inside this body there is no return at all

Categories