useQuery call in onClickHandler with object of array - javascript

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()} ...>

Related

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).

How do I obtain updated props mid-async-function?

This is for an open-source project called react-share, and their ShareButton component has a prop called beforeOnClick that you can pass to it. I'm using beforeOnClick to upload an image to our CDN so that we don't needlessly upload images that don't get shared, which causes the url prop passed to the button to update.
My current problem is, after beforeOnClick runs, the share button currently doesn't handle the updated url prop.
Basically, I have an async function that looks something like this:
const handleClick = async () => {
const { url, disabled, beforeOnClick } = this.props;
// beforeOnClick can cause this.props to change. beforeOnClick can also perform async operations, like making a fetch call
if (beforeOnClick) {
await beforeOnClick();
// call setTimeout to delay the next handleClick call in order to ensure this.props
// properly reflects changes from the parent component
setTimeout(handleClick);
return;
}
// Do stuff with url & disabled
};
I dumbed it down for the sake of keeping the question simple, but if you'd like to view the code I currently have, check out my fork. compare to the original.
Is setTimeout a reliable way to achieve this effect? Or, should I do something like this instead:
this.setState({ __rerender_component: true }, handleClick);
I'm not a huge fan of that solution, as I'd have to manage resetting that flag after the callback is run. Any thoughts are appreciated!
EDIT: Using setTimeout seems to work, but I'm not sure if it's reliable. If it fails 1/100 times, that sucks.
It might be easiest and feels more "reacty" to use setState to have a local copy of the props and let the beforeOnClick function use setState?
eg (beware, I have been using hooks only on my latest projects, so might be off)
const handleClick = async () => {
this.state = this.props; // can all props be changed?
if (beforeOnClick) {
await beforeOnClick(this.setState);
// Do stuff with this.state.url & this.state.disabled };
and beforeOnClick can use setState to change the url and others.
instead of giving full control to setState, you might want to have a different approach:
let newState= await beforeOnClick();
if (newState && newState.url && !newState.url.startsWith("http"))
throw 'url must start with http';
// that might be a wrong assumption, take it as an example
// whatever else you want to check, like disable is a boolean...
this.setState({...state, ...newState});

Query from database on onChange event inside select input in React, Redux-Form and GraphQL

I'm trying to query from database when user select a machine from a select input field (which data is also coming from database). I'm using redux-form. My code is like below:
<div className='universal_form-input'>
<label>Mechine Name</label>
<Field
type='number'
name='machine'
component={this.renderSelect}
/>
</div>
And the select input filled with Machine name and value is the id of the corresponding Machine. Now when user will select a machine it state will be updated with the id value of that machine.
On handleChange I'm trying to invoke a method named queryMachineById which should fetch data by specific machine id. And the query should return noOfDispenser of that machine from database. (BTW this query is working on GraphQL playground)
handleChange = (event, input) => {
event.preventDefault();
const { name, value } = event.target;
input.onChange(value);
this.setState({ [name]: value });
setTimeout(() => {
this.queryMachineById();
}, 1000);
};
queryMachineById method is written like below:
queryMachineById = () => {
const machineId = parseInt(this.state.machine);
console.log(typeof machineId, machineId);
return (
<Query query={MACHINE_BY_ID_QUERY} variables={{ machineId }}>
{({ data, loading, error }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
console.log(data);
return data.machineById.map(
machine => (initialValues.dispenserNo = machine.noOfDispensers)
);
}}
</Query>
);
};
machineId is also updating with valid id from state. But the console.log(data) inside the queryMachineById is not showing anything, not even empty {}
My GraphQL query looks like this:
export const MACHINE_BY_ID_QUERY = gql`
query MACHINE_BY_ID_QUERY($machineId: Int!) {
machineById(machineId: $machineId) {
id
noOfDispensers
}
}
`;
onClick is also not working.
Outdated / deprecated
using <Query /> and <Mutation/> components is a sign of using outdated examples/tutorials/knowledge.
These days you should use useLazyQuery hook.
Of course you can still use <Query/> component or even older methods (compose [multiple] graphql HOCs) as long they are supported.
Use old docs
or you can use skip property to manually firing it ...
or even [simpler] render <Query/> component conditionally.
Newer methods are described in docs.
Bads
In React, you can't render components from event handlers. You can use methods [passed as props] derived from components (f.e. from mutation) but never rendered from handler! - like you can't return anything visible (to be rendered) from event handler (update state instead, render something conditionally) ... it's all data driven, update data > render will update a view.
Update
Component works only in render context/flow - event handler flow is separate, you can't use (as not rendering it) or return component to be rendered (rendering was finished earlier) - this way you can't use it's functionality - querying in this case.
[One of options] You have to render <Query /> component in render flow and use skip property to block it's automatic firing at start. Handler can change blocking (skip) condition (use setState) and it will be fired and rerendered with data results. This way you have querying event driven.

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

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.

What's the best way to replace generated ID with server ID in Redux Application?

I'm playing around with React and Redux and trying to build a simple sample application. I wanna create entities and send the created entities, to the server, then, grab the server response and set correct id from the server in the created entity.
Currently, I generate a fakeId while waiting for the definitive Id, and when the server responds, replace the fakeId with the correct id.
This is a running example, whith the most relevant lines marked:
http://jsbin.com/duyovo/edit?js,console,output#J:L91-101
Is this the correct way of doing this? or is there another way? I'm building this app to learn React and Redux, and I want to learn it the correct way.
The jsbin has an error, and without a working example I'm having trouble parsing this (you may wanna fix that). But yeah, if the server is generating the id it seems like an ok strategy.
I don't like how you reuse case ADD_GROUP based on the "shape" of the response.
Perhaps to neaten things you might split the action into a synchronous and asynchronous sibling using redux-thunk.
WARNING:
This is just pseudocode to give you an idea of what the end product would look like if you used Redux-thunk. It may not be a drastically different strategy, but it might be considered a neater way of doing it.
So you could have an initial action to render a loading item:
var addGroup = (name, fakeId) => ({
type: ADD_GROUP,
name,
fakeId
})
And then twin actions (one sync, the other async) to update from the server.
First the synchronous one, that will (finally) be called once the response is back:
var updateGroupFromServer = (id, name, fakeId) => ({
type: UPDATE_GROUP_FROM_SERVER,
name,
id,
fakeId
})
Second, the ayschronous one, which you attach to your button:
export const updateGroupFromServerAsync = (name, fakeId) => {
// Update state with something temp
store.dispatch(addGroupLoading(name, fakeId));
// Go get the id from server
return dispatch => {
return new Promise(
(done, fail) => {
action
.request
.json()
.then(json => {
mockServer.addGroup(json.name)).then( res => {
dispatch(
/* Now its ready, it calls your async action*/
updateGroupFromServer({ id: res.id, name, fakeId})
);
done();
}
)
}
)
};
};
And then you change your action dispatch to:
<button onClick={() => {
updateGroupFromServerAsync(
this.inputGroup.value,
Date.now()
)}>
Add group
</button>
And then you would need to add reducers to handle them accordingly.
Edit: Added a better solution

Categories