React/Redux: is it okay to access action payload in mapDispatchToProps? - javascript

I've got this mapDispatchToProps function for a MediaUpload component. Upon adding a file, the onChange handler is triggered. The handler dispatches two actions: first it creates new media entries for files and returns an array of media objects. Then it updates the form data in the state with an array of media ids.
My question is: is it okay to read the action data in this position or do we preferably write to state via a reducer first?
const mapDispatchToProps = (dispatch, { form, name, multiple }) => ({
onChange: files => {
if (isEmpty(files)) return;
return dispatch(createMedia(files)).then(
media => {
// Get created media ids from action payload. Is this correct?
const mediaIds = media.map(item => item.payload.id);
return dispatch(updateFormData({
form,
fields: [ {
name: name,
value: multiple ? mediaIds : mediaIds[0]
} ]
}));
}
);
}
});

I'm a Redux maintainer. Seems fine to me, assuming that createMedia() is itself a thunk action creator that returns a promise.
That said, I also recommend moving that function definition to be standalone, and using the "object shorthand" form of mapDispatch rather than the function form.

Related

Showing Uncaught Error: Actions must be plain objects, when trying to dispatch data to store in redux

I am trying to dispatch an action with data in its parameter. Action is receiving the data but the reducer is not able to fetch that. I am using the useDispatch hook in this case. And I am using node v16.15.1 and npm v8.12.2
I have shortened the code for a better understanding
Component
const data = {...}
dispatch(sendData(data)); // the dispatch function will call onClick of a button
Action
export const sendData(data) => {
type:ADD_DATA, //ADD_DATA is a constant imported for using as type
data
}
Reducer
const dataReducer = (state = [], action) => {
switch(action.type) {
case ADD_DATA:
return {
...state,
data: action.data
}
default:
return state;
}
}
combined it with combineReducers() function
store created using configureStore()
the app is wrapped on <Provider store={store}></Provider>
Another Component
const data = useSelector(state=> state.dataReducer);
console.log(data);
In console.log empty state is displayed when rendered the component. But won't update onClick and gives the uncaught error Actions must be plain objects
Thank You...
Your sendData action creator is wrong:
export const sendData(data) => {
type:ADD_DATA, //ADD_DATA is a constant imported for using as type
data
}
This isn't actually returning anything. Because you have a => and a {, it's a function body, not an object. So, this is actually:
a function body
a label named type
and a reference to data
instead of an object like {type, data}
You could put parentheses around the curly braces to make it an implicitly returned object instead: => ({type, data}).
However, the better answer here is to switch to using our official Redux Toolkit package to write your Redux logic. Redux Toolkit is the the right way to use Redux today, and RTK's createSlice API will automatically generate action creators for you.

Can't find the way to useSelector from redux-toolkit within event handler and pass params to it

There is an event handler for click and when it triggered i want to pull specific data from redux using selector where all logic many-to-many is implemented. I need to pass id to it in order to receive its individual data. Based on rules of the react the hooks can be called in function that is neither a React function component nor a custom React Hook function.
So what is the way to solve my problem ?
const handleMediaItemClick = (media: any): void => {
// For example i check media type and use this selector to pull redux data by id
const data = useSelector(playlistWithMediaSelector(imedia.id));
};
As stated in the error message, you cannot call hooks inside functions. You call a hook inside a functional component and use that value inside the function. The useSelector hook updates the variable each time the state changes and renders that component.
Also, when you get data with useSelector, you should write the reducer name you need from the redux state.
const CustomComponent = () => {
// The data will be updated on each state change and the component will be rendered
const data = useSelector((state) => state.REDUCER_NAME);
const handleMediaItemClick = () => {
console.log(data);
};
}
You can check this page for more information.https://react-redux.js.org/api/hooks#useselector
You should probably use local state value to track that.
const Component = () => {
const [imediaId, setImediaId] = useState(null);
const data = useSelector(playlistWithMediaSelector(imediaId));
function handleMediaClick(id) {
setImediaId(id)
}
useEffect(() => {
// do something on data
}, [imediaId, data])
return <div>...</div>
}
Does that help?
EDIT: I gather that what you want to do is to be able to call the selector where you need. Something like (considering the code above) data(id) in handleMediaClick. I'd bet you gotta return a curried function from useSelector, rather than value. Then you would call it. Alas, I haven't figured out how to that, if it's at all possible and whether it's an acceptable pattern or not.

useQuery call in onClickHandler with object of array

I'm using react query.
There is an array of documents (just describing data, no real blobs) with an option to download. They have an onClick handler which calls a function which handles the download.
And in case the user clicks on the download button there is a http request getDocumentById to get the document you want to download.
The Problem is, I can not call my custom query hook in that handleDownload function. But the only place I have my specific document Id is in that function.
const parseDocuments = () => {
return documents.map(document => ({
...document,
name: document.fileName,
date: formatDateForUI(new Date(document.created)),
downloadDocumentHandler: () => downloadFileClickHandler(document),
read: document.read
}));
}
function handleDownload(document) {
<here I need to call the getDocumentById>
}
return (
<DocumentsTable documents={parseDocuments()} headers={headers} accountId={accountId} />
)
How is the best way to handle this?
I've got a problem I don't know the answer.
Then you've come to the right place.
I'm using react query.
That is very good 👍
How is the best way to handle this?
I see two options:
you disable the query and imperatively trigger it with refetch:
const { data, refetch } = useQuery(key, fn, { enabled: false })
<button onClick={() => refetch()} ...>
you don't use a query, but a mutation for it. I think that's the better approach, given that the backend likely creates that document when you click on the button. You also don't want to run it automatically, and you don't want to refetch it with the various refetch options that react-query has, there is also no invalidation necessary, so I'd do:
const { mutate } = useMutation(() => downloadTheDocuments())
<button onClick={() => mutate()} ...>

Returning a value from an Async Function. AWS/React

I'm trying to build a component that retrieves a full list of users from Amazon AWS/Amplify, and displays said results in a table via a map function. All good so far.
However, for the 4th column, I need to call a second function to check if the user is part of any groups. I've tested the function as a button/onClick event - and it works (console.logging the output). But calling it directly when rendering the table data doesn't return anything.
Here is what I've included in my return statement (within the map function)
<td>={getUserGroups(user.email)}</td>
Which then calls this function:
const getUserGroups = async (user) => {
const userGroup = await cognitoIdentityServiceProvider.adminListGroupsForUser(
{
UserPoolId: '**Removed**',
Username: user,
},
(err, data) => {
if (!data.Groups.length) {
return 'No';
} else {
return 'Yes';
}
}
);
};
Can anyone advise? Many thanks in advance if so!
Because you should never do that! Check this React doc for better understanding of how and where you should make AJAX calls.
There are multiple ways, how you can solve your issue. For instance, add user groups (or whatever you need to get from the backend) as a state, and then call the backend and then update that state with a response and then React will re-render your component accordingly.
Example with hooks, but it's just to explain the idea:
const [groups, setGroups] = useState(null); // here you will keep what "await cognitoIdentityServiceProvider.adminListGroupsForUser()" returns
useEffect(() => {}, [
// here you will call the backend and when you have the response
// you set it as a state for this component
setGroups(/* data from response */);
]);
And your component (column, whatever) should use groups:
<td>{/* here you will do whatever you need to do with groups */}</td>
For class components you will use lifecycle methods to achieve this (it's all in the documentation - link above).

Getting real-time data from firestore using React Context Hook API

I would like to have real-time connection with firebase firestore database and update UI after I add or remove some data from collection. I already tried using onSnapshot function but the thing is, when I go to a diffrent route and load other component, all data that was previously in database get change.type === "added" again even if they existed before. So when I get this data its repeating in the context and I'm getting doubled data on the screen, also map function throws an error bcs of redundant key attribute value. What should I do to avoid this? I'm using context hook API + reducers and action creators, no redux!
export const getProjects = (dispatch) => {
firestore.collection('projects').onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === "added"){
dispatch({type:'SET_PROJECTS', data: {
title: change.doc.data().title,
content: change.doc.data().content,
id: change.doc.id
}})
}
})
})
}
export const projectReducer = (state, action) => {
switch(action.type){
case 'CREATE_PROJECT_ERR':
console.log('Create project error', action.err.message);
return state
case 'SET_PROJECTS':
return [...state,{
title: action.data.title,
content: action.data.content,
id: action.data.id
}]
default:
return state
}
}
useEffect(() => {
getProjects(dispatch);
}, [dispatch])
That's the code that connects with firestore and get data and also reducer that set's it into the context
onSnapshot always calls the dataCallback at least once, and then again on any changes. Your reducer is simply appending the results to the existing state, so that likely explains why you see it twice - you need to be a tad more subtle to replace existing entries.
I usually work with Redux (rather than Context), but I have a number of instances where my Listeners update Redux state, and the existing Redux connectors update React. And yes,my reducers take care of duplicates.

Categories