Formik - Stop field from being validated if it hasn't changed - formik

When Formik runs validation, all my fields are validated.
One of my operations is really expensive (DB lookup).
I'd like to not run this validation if it hasn't changed. How can I achieve this?
I'm using withFormik Higher order component.
One option I considered was to track the current value and the last validated value. If different, then run the validation. Maybe that could be const [lastValidated, setLastValidated] = useState(); in the underlying component. But I'm not sure how to access state on my underlying comopnent.
const Guest = (props) => {
// maybe lastValidated here?
};
const formikEnhancer = withFormik({
validate: (values, props) =>
new Promise((resolve, reject) => {
const errors = {};
// Runs validation on all fields
}
});
export default formikEnhancer(Guest);

You could consider using field-level validation in this case. It could give you more flexibility in checking for changes in this specific input so you can bail before the expensive operation is run.

Related

Retrieve State From Redux Store

I am learning Redux global state store and I created a simple app, when click the "Select" button, the selected item will be added to the SavedCourses list. Then I want to display the SavedCourses list. But right now my code does not display the list. Is there something wrong with my reducer? My sandbox link is at https://ky801.csb.app/#/
It's actually your mapDispatchToProps function which is the problem. addCourse is supposed to take an argument which is the class_nbr. But your this.props.addCourse function ignores its arguments and just calls dispatch(addCourse()).
const mapDispatchToProps = (dispatch) => {
return {
addCourse: () => dispatch(addCourse())
};
};
Since you aren't actually passing the course info, your savedCourses is ending up like [undefined, undefined, undefined].
You could change your mapDispatchToProps so that it takes an argument:
const mapDispatchToProps = (dispatch) => {
return {
addCourse: (class_nbr) => dispatch(addCourse(class_nbr))
};
};
But when all you are doing is dispatching the function with the same arguments then it is not necessary to define mapDispatchToProps as a function. You can just use an object of action creators:
export default connect(mapStateToProps, {addCourse})(Courses);
Redux has gotten a lot easier nowadays with the react-redux hooks and redux-toolkit. You can simplify your code a lot using those.

BehaviorSubject send the same state reference to all subscribers

In our Single Page Application we've developed a centralized store class that uses an RxJS behavior subject to handle the state of our application and all its mutation. Several components in our application are subscribing to our store's behavior subject in order to receive any update to current application state. This state is then bound to UI so that whenever state changes, UI reflect those changes. Whenever a component wants to change a part of the state, we call a function exposed by our store that does the required work and updates the state calling next on the behavior subject. So far nothing special. (We're using Aurelia as a framework which performs 2 way binding)
The issue we are facing is that as soon as a component changes it's local state variable it receives from the store, other components gets updated even if next() wasn't called on the subejct itself.
We also tried to subscribe on an observable version of the subject since observable are supposed to send a different copy of the data to all subscriber but looks like it's not the case.
Looks like all subject subscriber are receiving a reference of the object stored in the behavior subject.
import { BehaviorSubject, of } from 'rxjs';
const initialState = {
data: {
id: 1,
description: 'initial'
}
}
const subject = new BehaviorSubject(initialState);
const observable = subject.asObservable();
let stateFromSubject; //Result after subscription to subject
let stateFromObservable; //Result after subscription to observable
subject.subscribe((val) => {
console.log(`**Received ${val.data.id} from subject`);
stateFromSubject = val;
});
observable.subscribe((val) => {
console.log(`**Received ${val.data.id} from observable`);
stateFromObservable = val;
});
stateFromSubject.data.id = 2;
// Both stateFromObservable and subject.getValue() now have a id of 2.
// next() wasn't called on the subject but its state got changed anyway
stateFromObservable.data.id = 3;
// Since observable aren't bi-directional I thought this would be a possible solution but same applies and all variable now shows 3
I've made a stackblitz with the code above.
https://stackblitz.com/edit/rxjs-bhkd5n
The only workaround we have so far is to clone the sate in some of our subscriber where we support edition through binding like follow:
observable.subscribe((val) => {
stateFromObservable = JSON.parse(JSON.stringify(val));
});
But this feels more like a hack than a real solution. There must be a better way...
Yes, all subscribers receive the same instance of the object in the behavior subject, that is how behavior subjects work. If you are going to mutate the objects you need to clone them.
I use this function to clone my objects I am going to bind to Angular forms
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
So if you have an observable data$ you can create an observable clone$ where subscribers to that observable get a clone that can be mutated without affecting other components.
clone$ = data$.pipe(map(data => clone(data)));
So components that are just displaying data can subscribe to data$ for efficiency and ones that will mutate the data can subscribe to clone$.
Have a read on my library for Angular https://github.com/adriandavidbrand/ngx-rxcache and my article on it https://medium.com/#adrianbrand/angular-state-management-with-rxcache-468a865fc3fb it goes into the need to clone objects so we don't mutate data we bind to forms.
It sounds like the goals of your store are the same as my Angular state management library. It might give you some ideas.
I am not familar with Aurelia or if it has pipes but that clone function is available in the store with exposing my data with a clone$ observable and in the templates with a clone pipe that can be used like
data$ | clone as data
The important part is knowing when to clone and not to clone. You only need to clone if the data is going to be mutated. It would be really inefficient to clone an array of data that is only going to be displayed in a grid.
The only workaround we have so far is to clone the state in some of our subscriber where we support edition through binding like follow:
I don't think I can answer that without rewriting your store.
const initialState = {
data: {
id: 1,
description: 'initial'
}
}
That state object has deeply structured data. Everytime you need to mutate the state the object needs to be reconstructed.
Alternatively,
const initialState = {
1: {id: 1, description: 'initial'},
2: {id: 2, description: 'initial'},
3: {id: 3, description: 'initial'},
_index: [1, 2, 3]
};
That is about as deep of a state object that I would create. Use a key/value pair to map between IDs and the object values. You can now write selectors easily.
function getById(id: number): Observable<any> {
return subject.pipe(
map(state => state[id]),
distinctUntilChanged()
);
}
function getIds(): Observable<number[]> {
return subject.pipe(
map(state => state._index),
distinctUntilChanged()
);
}
When you want change a data object. You have to reconstruct the state and also set the data.
function append(data: Object) {
const state = subject.value;
subject.next({...state, [data.id]: Object.freeze(data), _index: [...state._index, data.id]});
}
function remove(id: number) {
const state = {...subject.value};
delete state[id];
subject.next({...state, _index: state._index.filter(x => x !== id)});
}
Once you have that done. You should freeze downstream consumers of your state object.
const subject = new BehaviorSubject(initialState);
function getStore(): Observable<any> {
return subject.pipe(
map(obj => Object.freeze(obj))
);
}
function getById(id: number): Observable<any> {
return getStore().pipe(
map(state => state[id]),
distinctUntilChanged()
);
}
function getIds(): Observable<number[]> {
return getStore().pipe(
map(state => state._index),
distinctUntilChanged()
);
}
Later when you do something like this:
stateFromSubject.data.id = 2;
You'll get a run-time error.
FYI: The above is written in TypeScript
The big logical issue with your example is that the object forwarded by the subject is actually a single object reference. RxJS doesn't do anything out of the box to create clones for you, and that is fine otherwise it would result in unnecessary operations by default if they aren't needed.
So while you can clone the value received by the subscribers, you're still not save for access of BehaviorSubject.getValue(), which would return the original reference. Besides that having same refs for parts of your state is actually beneficial in lots of ways as e.g arrays can be re-used for multiple displaying components vs having to rebuild them from scratch.
What you want to do instead is to leverage a single-source-of-truth pattern, similar to Redux, where instead of making sure that subscribers get clones, you're treating your state as immutable object. That means every modification results in a new state. That further means you should restrict modifications to actions, (actions + reducers in Redux) which construct a new state of the current plus the necessary changes and return the new copy.
Now all of that might sound like a lot of work but you should take a look at the official Aurelia Store Plugin, which is sharing pretty much the same concept as you have plus making sure that best ideas of Redux are brought over to the world of Aurelia.

RxJS BehaviorSubject with custom create logic

Because BehaviorSubject extends Subject and Subject extends Observable, all of those three have static .create(observer) method to create them using custom values emission logic.
I' able to use with good result Observable.create(observer), for instance:
a = Rx.Observable.create(obs => {
setInterval(() => {
obs.next('tick');
}, 500)
})
s = a.subscribe(v => console.log(v))
Gives me expected output (tick every 500ms)
But when I replace Observable with Subject/BehaviorSubject, it's not so willing to get up and running:
a = Rx.Subject.create(obs => {
setInterval(() => {
obs.next('tick');
}, 500)
})
s = a.subscribe(v => console.log(v)); // Nothing
a.next(5); // Still nothing
Basically, subject seems to work as intended to only if they are created via new operator like below:
a = new Rx.Subject();
s = a.subscribe(v => {console.log(v)});
a.next(5) // Ok, got value here
Even if I try to use non-parametrized create method, which invocation shall boil down to same result as using new:
a = Rx.Subject.create();
I'm still unable to force it to emit values.
I'm aware that subjects are designed to receive values from outside world (not to generate them internally as Observables), thus subject shall be triggered by external code with subject.next('value'), but I was just curios that if they are strictly related to Observables, logic behind create and further behavior shall be same...
Can anyone explain, why usage of create on Subject (even if they are not designed to work this way, but still it shall be possible) does not work as supposed to?

React Redux Action Creator Dispatch Issue with Fetch API and Promise

I am writing action creator in react app. where in when i do some api call i need to show the Progress Loader on screen. So, my action creator looks like this.
export const fetchData = (actionType, param) => (dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
return fetchDataRequest(actionType, param) // Here is Fetch APi Call
.then(responseData => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER));
dispatch(Action(recd(actionType), { data: responseData, receivedAt: Date.now() }));
}).catch((error) => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
};
When i write this piece of code its working as expected, i am dispatching the action as dispatch(fetchData(data)) from component and i am able to show the loader in my Parent Component. What i understand is fetch is returning me the promise. Once the fetch gets completed then i am hiding the loader which is working as expected.
Now, There is scenario where in i need to do some validation where in i don't have to make any api call but all the validation are performed locally.
Here also i want to do the same thing like i need to show loader in my parent component as well when all the validation are done i need to hide the loader.
I have written the same piece of code even actions are getting called but my render function is not getting called.
My Code Looks like:
// This my action creator which will actually do the validation
export const validateAndSaveData = () => {
return ((dispatch, getState) => {
return new Promise((resolve, reject) => {
let saveRecommendDetailsFlag = true;
// here i am dispacthing some action and storing data in my store
saveRecommendDetailsFlag = canSaveData(getState());
if (saveRecommendDetailsFlag) {
resolve('SUCCESS');
} else {
reject('ERROR');
}
});
});
};
And there is one more action creator which i am calling it from from UI Component which will first initiate the show loader action and then perform validation and based on the result of validation i have to hide the loader.
export const saveData = () => {
return ((dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
return dispatch(validateAndSaveData())
.then(() => {
// Here i m dispatching an action to do some more processing.
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
})
.catch(() => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
});
};
Everything is working fine but my loader are not coming on the screen. i am not able to figure it out where am i doing wrong.
Can anyone suggest something how can i solve this issue?
I got some workaround using setTimeout func but i don't think that is right approach.
export const saveData = () => {
return ((dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
setTimeout(()=>return dispatch(validateAndSaveData())
.then(() => {
// Here i m dispatching an action to do some more processing.
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
})
.catch(() => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
},10);
});
};
Your code looks reasonable, my suspicion is that your validateAndSaveData promise finishes so quickly that there is no visible loader on the screen.
In that case, a timeout is totally reasonable. However, in order to do it properly, I would keep a state on if the loading screen is visible + if it's been shown long enough. You can then remove the loading screen once it is both up for long enough, and the actual event expires.
I'm not sure which action package you're using, so I can't post exact code, but the pseudocode would look something like this:
const delay = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds));
let loadingCounter = 0;
const showLoadingScreen = () => (dispatch) => {
const counter = loadingCounter;
loadingCounter++;
delay(5).then(() => {
if (getStore().loadingScreen.counter === counter) {
dispatch(Action(ActionConstants.PROGRESS_LOADER_DELAY_ELAPSED))
}
})
return dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER, counter))
}
Basically, you would keep track of 3 pieces of state for the loader:
{
counter: 0,
taskCompleted: false,
canHide: false,
}
Counter is saved so that you can disambiguate what happens if you get SHOW_PROGRESS_LOADER while an existing SHOW_PROGRESS_LOADER is in progress.
taskCompleted keeps a record of whether the thing you're waiting on is done, and canHide keeps track if the loader has been visible on the screen long enough.
When you dispatch PROGRESS_LOADER_DELAY_ELAPSED it sets canHide to true, and when you dispatch HIDE_PROGRESS_LOADER it sets taskCompleted to true. (although you may want to rename the latter action). When both canHide and taskCompleted are set to true, only then can the loader go away.
This is a pretty common UI pattern - Try to complete a task quickly. If it takes more than a short amount of time, then throw up a loading dialog. However, the loading dialog is guaranteed to stay up a minimum amount of time to prevent flickering. So the more advanced version of this kind of pattern would be to add another state which doesn't show the progress loader at all unless the call takes more than Y milliseconds.
Hope this makes sense, leave a comment if not :-)

redux-observable: Mapping to an action as soon as another was triggered at least once

I have an SPA that is loading some global/shared data (let's call this APP_LOAD_OK) and page-specific data (DASHBOARD_LOAD_OK) from the server. I want to show a loading animation until both APP_LOAD_OK and DASHBOARD_LOAD_OK are dispatched.
Now I have a problem with expressing this in RxJS. What I need is to trigger an action after each DASHBOARD_LOAD_OK, as long as there had been at least one APP_LOAD_OK. Something like this:
action$
.ofType(DASHBOARD_LOAD_OK)
.waitUntil(action$.ofType(APP_LOAD_OK).first())
.mapTo(...)
Does anybody know, how I can express it in valid RxJS?
You can use withLatestFrom since it will wait until both sources emit at least once before emitting. If you use the DASHBOARD_LOAD_OK as the primary source:
action$.ofType(DASHBOARD_LOAD_OK)
.withLatestFrom(action$.ofType(APP_LOAD_OK) /*Optionally*/.take(1))
.mapTo(/*...*/);
This allows you to keep emitting in the case that DASHBOARD_LOAD_OK fires more than once.
I wanted to avoid implementing a new operator, because I thought my RxJS knowledge was not good enough for that, but it turned out to be easier than I thought. I am keeping this open in case somebody has a nicer solution. Below you can find the code.
Observable.prototype.waitUntil = function(trigger) {
const source = this;
let buffer = [];
let completed = false;
return Observable.create(observer => {
trigger.subscribe(
undefined,
undefined,
() => {
buffer.forEach(data => observer.next(data));
buffer = undefined;
completed = true;
});
source.subscribe(
data => {
if (completed) {
observer.next(data);
} else {
buffer.push(data);
}
},
observer.error.bind(observer),
observer.complete.bind(observer)
);
});
};
If you want to receive every DASHBOARD_LOAD_OK after the first APP_LOAD_OK You can simply use skipUntil:
action$ .ofType(DASHBOARD_LOAD_OK)
.skipUntil(action$.ofType(APP_LOAD_OK).Take(1))
.mapTo(...)
This would only start emitting DASHBOARD_LOAD_OK actions after the first APP_LOAD_OK, all actions before are ignored.

Resources