React-Final-Form: Set initialValues from props, form state resets on props change - react-final-form

I have a component, that takes a balance prop, and this balance prop can change over time.
I then have a React Final Form to send a transaction, with usual fields amount to send, receiver... And in my validate, I just check that the user has enough balance to send the transaction.
However, if my balance changes when the user is inputting something, then the whole form resets. How would you only reset part of the form state?
See this codesandbox for an example: https://codesandbox.io/s/jn69xql7y3:
input something
wait 5s
see that the form state is blank again

I just ran into this issue with react-final-form where the form completely resets when any state change happens in a wrapping component.
The problem is here (from your codesandbox)
<Form
initialValues={{ amount: 0, balance }} <-- creates a new object on every render
The problem is that when initialValues changes the entire form reinitialises. By default, whatever you pass to initialValues is compared against the previous one using shallow equals, i.e. comparing reference.
This means that if you create a new object in the render, even if it's the same, the entire form resets when some state changes, the render function re-runs, and a new object with a new reference is created for initialValues.
To solve the general problem, if you just want to turn off the form resetting, what I've done is just move my initialState, which never changes, out to a variable so that it's the same reference on every render and therefore always appears to be the same to final-form with the default behaviour. I would prefer a configuration to actually completely turn off this reinitialisation behaviour, but I can't find one in the docs.
However if you actually want this, but need to modify it, the comparison behaviour can be configured using the initialValuesEqual prop (docs here), to do a deep comparison on the initialValues object for example.
You can also use the keepDirtyOnReinitialize prop to only reset the parts of your form that haven't been touched.
I would guess some combination of the above might solve your usecase, depending on the exact UX you need.

Adding onto what #davnicwil mentioned, my solution is useMemo() hook in func components:
const initialValues = useMemo(() => ({ amount: 0, balance }), [])
By using useMemo it creates only 1 object during the life of the component and subsequent re-renders don't cause initialValues to overwrite the form values.

Another solution is to use react-final-form Form prop initialValuesEqual={() => true}
<Form initialValues={{ amount: 0, balance }} initialValuesEqual={() => true} .../>
ref: https://github.com/final-form/react-final-form/issues/246

Related

Add Offset selector to Grails Pagination

I have this grails application and I've added a number field and a button which on click passes on the query parameters, specifically the offset value so the user can navigate to a specific page faster, since the pagination has some 2000+ pages on a max=10 basis, you can imagine navigating that. Anyway, so my problem is that I'm handing the offset with jquery and all fine but when I press enter on the number field that triggers the form which is build in combination with the controller and practically filters back to page 1. So I wonder if someone knows how would I add an extra field that will pass in an offset value as well when form with filters is submitted. Sorry no code to post but this application is a monster and I suck at Grails or Spring boot in general. Any support is appreciated.
Do you mean 2000 pages or rows.
Also i think if you share some few codes from your
Controller method
GSP (Thus the .gps file)
Javascript implementation
it will give a lot of people an insight on how to help us all solve the questions
I'll post the answer for those who might look for this.
Grails pagination uses TwitterBootstrapTagLib class, you'll find the pagination logic there. It looks for an 'offset' variable in the session params and if it doesn't find one it creates one.
Now the solution is a bit trickier than setting an 'offset' variable because when you do so you will disable the paging arrows, why? too long to explain, but trust me, you will.
To avoid having to control all the other parts of the pagination which is done perfectly well from this class you can create a new session variable, e.g. _offset, in the controller that calls the data that needs pagination.
def controllerActionX() {
...
if(params.containsKey('offset') && !params.containsKey('_offset')){
params['_offset'] = params.offset
}
...
}
You need to check first because in a second iteration you don't want to reassign offset to _offset because then you'll be stuck in one page. Also notice that offset already exists in the session, assigned by the bootstrap class.
Then you create your fields in the view:
<input type="submit" class="goto-page" id="goto-page" value="Go To Page"/>
<g:field type="number" class="topage-number" name="_offset" min="1" value="${params._offset?:1}"/>
This is self-explanatory, however, the value from the _offset field is a value entered by a human so we still need to calculate the offset based on the max records per page, i.e. in order to get page 2 on a 10 records-per-page basis, our offset has to be between 11-19, 19 preferably because it makes calculation easier.
And last step in the service layer we calculate everything like this:
def get(HttpSession session, Map params, Xclass xUser, String status) {
....
String offset = '0'
if(params.containsKey('_offset')){
if(params['_offset'] != params.offset){
params.offset = ((params._offset as int) * (max as int)) - 1
}else{
params['_offset'] = (params.offset as int) / (max as int) + 1
}
}
if (params.containsKey('offset')) offset = params.offset
...
def result = executeQuery(resultsQuery,mapping, [max: max, offset: offset])
result
That's it. Remember to cast your variables as integers when you calculate; you don't have to convert them to String after that since that is handled by the session.
By the way this is a poorly written application because the service layer should never handle any endpoint transactions, that is only a controller's job if we're following proper SOLID and MVC principles, but I found this application like this so I had to work with it.

How to run a specific method when props changes in Svelte3?

I'm building an autocomplete text field component. We will show popup of items filtered based on what users type. It is going to be async, I will get the details from the server and do some filtering based on the text typed in the field.
So here, I have run this filtering logic whenever I send new data to the component.
I come from angular, there we used to have ngOnChange(). Is there something similar available in svelte3.
Right now, I'm filtering by calling the method from outside by binding bind:this. I don't feel like this is a correct approach.
https://github.com/manojp1988/svelte3-autocomplete/blob/master/dev/App.svelte
Without stores, using a prop
Just using a prop:
export let search = '';
....
$: if (search !== '') { // make it react to changes (in the parent)
doSomeThing(search);
};
Stores
Svelte also has stores. A store is an observable object which can be observed everywhere even beyond you project with RxJS.
Example:
const unsubscribe = search.subscribe(s) => {
doSomeThing(s);
});
onDestroy(unsubscribe);
In another component you can use search.set('Hi');
But looking forward for other solutions to handle these kind of changes in parent <-> child components or calling child Component methods.
From child to parent we can fire events.
But from parent to child ...? we can use a store or Component bind:this or ..? but ....

react-final-form how to load initialValues from ajax call

Looking at the example in https://codesandbox.io/s/km2n35kq3v
The initial values are hard-coded.
<Wizard
initialValues={{ employed: true, stooge: 'larry' }}
However, i want to make an ajax call in ComponentDidMount, fetch the initial values, then (re) set the initialValues when the call completes.
<Wizard
initialValues={this.state.myInitValues}
Nothing happens, when the form re-renders, the initialValues do not change - What am i missing ?
that example only works for hard-coded values
No.
As long as the values are fetched higher in the tree, "above" the <Wizard/> component, they can be passed in as props just fine. React Final Form even has fine grained isEqual() control over determining when the initialValues prop changes (in which case the form would be re-initialized) or not.
Hope this helps...
I figured this out.. that example only works for hard-coded values, I should not have assume otherwise.
It turns out initialValues are being passed down to the Wizard component and set as state within the Wizard component in the constructor.
When the ajax call returns, the constructor does not get executed again.

How to force a fetch in Relay modern

My top-level component includes a settings dialog that includes the user's credentials. When a change is made in that dialog and the dialog is dismissed (state is changed to dialogOpen=false), I want to force a new fetch from the server since the credentials may have changed. In Relay classic, the top-level component includes a Relay.RootContainer and so I just passed forceFetch=true to that RootContainer. In Relay modern, my top-level component includes a QueryRenderer. So how do I force the refetch in this case?
I found this issue, https://github.com/facebook/relay/issues/1684, which seems to indicate that the QueryRenderer always refetches, but this doesn't seem to be the case in my testing. At least, I'm not seeing my fetchQuery get called after the state change/refresh when the settings dialog is closed. I think I'm probably not completely understanding the statements in that issue.
Can anyone clarify?
OK, I think I figured out my disconnect here. In checking the source for QueryRenderer (don't know why I didn't do this in the first place), I saw that a fetch will occur if props.variables changes. So I just defined a boolean instance variable called refetch and flip its value when my dialog is dismissed:
<QueryRenderer
environment={environment}
query={query}
variables={{refetch: this.refetch}}
Since this doesn't seem to well documented, I'll mention here that QueryRenderer will re-fetch when any of the following conditions is true:
current query parameter is not equal to the previous query parameter.
current environment parameter is not equal to the previous environment parameter.
current variables parameter is not equal to the previous variables parameter.
You can use the retry function that's passed to QueryRenderer's render.
<QueryRenderer
environment={environment}
query={graphql`
query MyQuery($exampleUserId: String!) {
user(userId: $exampleUserId) {
favoriteIceCream
}
}
`}
render={({ error, props, retry }) => {
// Here, you could call `retry()` to refetch data
// or pass it as a prop to a child component.
return (
<IceCreamSelectionView refetchData={retry} user={props.user} />
)
}} />

Knockout js registerEvent handler

I'm having a great time playing around with knockout js and have just started to get to grips with adding custom bindingHandlers.
I'm struggling a bit with the update function of a 3rd party jqWidget gauge - I can only get it to animate the first time I update the variable. On each update after that it just sets the value directly.
I don't fully understand ko.utils.registerEventHandler() and what it does although I've seen it in a bunch of other examples. Is this what is causing the animation to break? How do I know which events to register from the 3rd party widget?
For some reason this works fine if I add a jquery ui slider that is also bound to the observable.
You can test this here: set the value a few times to see that it animates the first time and not after that.
http://jsfiddle.net/LkqTU/4531/
When you update the input field, your observable will end up being a string. It looks like the gauge does not like to be updated with a string value, at least after the first time.
So, if you ensure that you are updating it with a number (parseInt, parseFloat, or just + depending on the situation), then it appears to update fine.
Something like:
update: function(element, valueAccessor) {
var gaugeval = parseInt(ko.utils.unwrapObservable(valueAccessor()), 10);
$(element).jqxGauge('value', gaugeval || 0);
}
http://jsfiddle.net/rniemeyer/LkqTU/4532/
You would generally only register event handlers in a scenario like this to react to changes made by a user where you would want to update your view model data. For example, if there was a way for a user to click on the gauge to change the value, then you would want to handle that event and update your view model value accordingly.
I'm answering the
I don't fully understand ko.utils.registerEventHandler() and what it does
part of your question.
registerEventHandler will register your event handler function in a cross-browser compatible way. If you are using jQuery, Knockout will use jQuery's bind function to register the event handler. Otherwise, will use the browser Web API with a consistent behavior across browsers.
You can check it out on the source code.

Resources