How to submit / save form from within the Edit form - react-admin

I would like to submit the <Edit> form from within the form itself, in order to instantly save the form state upon a simple change e.g. a boolean toggle.
I spent hours trying to find a simple way, and it doesnt seem to exist. The handleSubmit[...] function is only passed to the actions toolbar, and not to the input themselves.
In addition, there seems to be no context hook I can invoke that would provide me access to that function.
One solution I can think of is create a hidden <SaveButton /> inside the actions props, with a ref. Then from the input, I can trigger the <SaveButton /> from the Input. However this seems super hacky, and I would love to find a more straightforward solution.
Is there a theoretical reason why inputs cannot access the submit function?

You can use the useSaveContext hook which is not yet documented:
import { useSaveContext } from 'react-admin';
const { save, saving } = useSaveContext();

Related

Access to a function\$refs in another component not in the same level as current one

I've a Vue 2 application, with different components nested.
Their structure is kinda (I'm skipping the not-relevant ones for my question):
<root>
<app>
<component1/>
...
<component3>
<billingAddress>
<customForm/>
</billingAddress>
<shippingAddress>
<customForm/>
</shippingAddress>
</component3>
...
<lastComponent/>
</app>
</root>
The customForm1 and 2 contain a form, validated via vee-validate - so there' a ValidationObserver component.
Those two forms are validated at the relative submit - the submit action is custom, when they are successfully validated, they are hidden - and there is no redirect.
On the "lastComponent" there's a "submit" button whichis disabled until both form are correctly submitted and which does some other logic.
Today I've been asked to change the behaviour of that button: now it may launch the validation and the submit of those two form, skipping so the "manual" submission of the users.
I'm not sure how can handle this, as the ValidationObserver and the custom actions are in components unrelated to the "lastComponent".
For now, I've managed traversing the tree of $refs starting from $root, but I don't really like an approach so fragile.
This is what I'm doing in my "submit" action:
let shippingAddressValid = await this.$root.$children[0].$refs.addresses.$refs.shippingAddress.$refs.addressForm.$refs.shippingAddressForm.validate();
if (shippingAddressValid) {
await this.$root.$children[0].$refs.addresses.$refs.shippingAddress.$refs.addressForm.updateAddress();
}
I'd like to know if there's a better approach.
I've found this anweser that is interesting. However, I haven't found a way to set up a custom event via "global bus" that returns me a value, asynchronously.
I mean, it's mandatory, to get the "valid" state of the form, before calling the submit method.
I can't just call the validation in the method, because I have to do some logic if the form is invalid.

Vue component loses input when asynchronous call updates another property

I have a component that has an <input> with a :value set to a certain property. The component has another property unrelated to this input, which can get updated asynchronously (AJAX call). Whenever you're typing inside the input and the asynchronous call finishes, updating the other property, your typed input is reset.
To recreate this problem I've created a jsfiddle using a setInterval to simulate the async call and increment the other passed property. Try typing in the input, it will get reset every second. If you're quick enough, you can tab out and cause the #change to trigger the actual update.
The question is: why is the update to the other prop invalidating/rerendering the component and how can I work around this?
Note that v-model="person.name" is not a valid solution here - I need to know the old and new value, which is why I'm using a manual :value/#change combo.
Edit: The updateName method also really only needs to be triggered when the user leaves the input field. This is because the code run inside it is relatively CPU intensive and only needs to run when the user is done with the input and leaves it (in my actual code, not the jsfiddle).
Edit2: Is there some way to not let it re-render the entire component, but only the relevant pieces?
Because the parent component is changing a property of the child component, it has to re-render (parts of) the child component. Since you are using #change, instead of #input, your changes are not saved yet to the reactive variable person.name, it only works if you click tab quick enough. One solution would be to change #change to #input (which better resembles v-model):
https://jsfiddle.net/mf67xq1e/
Another (better) option is to use v-model and use a watcher to retrieve both the old and the new value:
watch: {
// whenever person.name changes, this function will run
"person.name": function (newName, oldName) {
console.log("newName:", newName);
console.log("newName:", oldName);
}
}
https://jsfiddle.net/mf67xq1e/1/
EDIT:
As you mentioned you only need to trigger something when you blur/leave the input field, seperate the reactivity of the variable and the triggering of your other method (e.g. updating some other variable or something), in two seperate variables:
https://jsfiddle.net/ta9cgnx0/
EDIT 2: Cleaner option with v-model and a seperate call for your other trigger on blur:
https://jsfiddle.net/ay1g63u8/

Load options on the first open of the Async drop down menu

When I provide loadOptions to an Async control it loads options on mount.
If I pass autoload={false} then it doesn't load options neither on mount nor on open. But it loads options on the first close (or type, or blur).
If I pass onCloseResetsInput={false} then it doesn't load options until I type something. (showing "Type to search" in the menu)
Async provides onOpen handler, but I didn't find the way to use it in this situation. (and react-select#2.0.0-alpha.2 doesn't have it)
So the user needs to type a character, then delete it, to see the full list of options.
How can this be avoided?
Example sandbox: https://codesandbox.io/s/mjkmowr91j
Solution demo: https://codesandbox.io/s/o51yw14l59
I used the Async options loaded externally section from the react-select repo.
We start by loading the options on the Select's onFocus and also set the state to isLoading: true. When we receive the options we save them in the state and render them in the options.
I also keep track of optionsLoaded so that only on the first focus do we trigger the call to get options.
In our use case, we have several of these select inputs on a single page, all async, so the requests to the server will pile up, and are completely unnecessary in a lot of cases (users won't even bother clicking).
I found a workaround for this issue that'll work for my use case on 2.0.0-beta.6:
Include defaultOptions
Add 2 members to your class that will store the resolve/reject methods for the promise.
In your loadOptions function, check if the input is '', if so, create a new promise, and store the values of resolve/reject within your class members, and return that promise. Otherwise, just return the promise normally to get your results.
Add an onFocus handler, and within it call the function to get your results, but also add .then and .catch callbacks passing the resolve and reject functions you stored previously.
Essentially, this makes react-select think you're working on getting the results with a long-running promise, but you don't actually even try to load the values until the field is selected.
I'm not 100% positive there aren't any negative side effects as I just wrote this, but it seems like a good place to start.
Hope this helps someone. I may submit a feature request for this.
In order to load options when user focus first time, set defaultOptions={true}
Thanks, Alexei Darmin for the solution, I was struggling with this... while testing it I converted the solution to a react functional component and added real API fetching.
Here is a working demo, I hope it helps someone

Detecting changed attributes when form save is complete

Ultimately what I'm looking for is an onSaveComplete event.
The problem is that we have plugins that modify entity data when it is saved. For most attributes this is fine, because the data is updated on the form after the save completes. Client scripts are problematic though because (as far as I can tell) there are no events to indicate that a value was updated by a plugin when the entity was saved.
The best solution I have come up with is to hook into the onSave event, prevent the default save action, and use data.save() instead:
function onSave(context) {
var args = context.getEventArgs();
args.preventDefault();
Xrm.Page.data.save().then(function() {
//check for changed attributes here
});
}
(Actual code is a bit more complicated to prevent recursion, etc. but you get the idea)
Ultimately this approach ends up being quite messy, and the more forms I apply it to, the more it feels like a real hack. So my question is - is there a better, more standard way to approach this?
I accomplished something similar to a post-save JS event through a different kind of "hack".
Here's how it works:
Put ModifiedOn field on the form
Subscribe to ModifiedOn's OnChange event
The event you attach to the OnChange will fire every time the form is saved (because when data changes so does ModifiedOn).
It's very likely that ModifiedOn should be made read-only on the form, to avoid clashing scripts.

Communicating with Knockout components

Is there a way to communicate from the parent viewmodel to a KnockoutJS component?
I have a component that contains a bootstrap modal dialog box, to search for customers and return a selected customer. At present, to make the dialog box appear, I pass an observable boolean from the viewmodel in the component's params attribute. To make the dialog appear I set this to true, which invokes the dialog box. I also pass a callback function in params to return the results.
Here is a fiddle demo which shows the concept: http://jsfiddle.net/Quango/5bxbsLt6/
Passing the observable boolean to invoke the dialog doesn't feel right, but it's the only working solution I have. The only other idea I had was to use ko-postbox to create a publish/subscribe function.
It feels like there should be a way to invoke actions, e.g. component.Show() ?
I think the answer here is that there isn't a better way. To communicate from a parent viewmodel to the component, pass an observable value, and then use that directly or subscribe to changes in the component.
I will put a suggestion on the Knockout github project to consider some form of interface.