I am creating a validator for a field and triggering validation on update. I only care about the validation result of the current state. So if another update is triggered before the validation completes, I don't want the validation success to occur for the old piece of data.
I have read and believe that accessing the store in an action creator is somewhat of an anti-pattern in https://stackoverflow.com/a/35674575/816584.
I currently have the following:
export const USERNAME_VALIDATION_PENDING = 'USERNAME_VALIDATION_PENDING';
export const USERNAME_VALIDATION_SUCCESS = 'USERNAME_VALIDATION_SUCCESS';
export const USERNAME_VALIDATION_FAILURE = 'USERNAME_VALIDATION_FAILURE';
export const USERNAME_VALIDATION_ABORTED = 'USERNAME_VALIDATION_PENDING';
export interface Validator {
validate(subject: string): Promise<void, Error>;
cancel();
}
const cache = new WeakMap();
function memoize(obj: {}, subject: string, result: Error | true) {
const map: {[subject: string]: Error | true} = cache.has(obj) ?
cache.get(obj) : {};
// TODO: Do consider caching just the message so the stack can get GC
map[subject] = result;
cache.set(obj, map);
}
function resultCacheP(obj: {}, subject: string) {
const map: {[subject: string]: Error | true} = cache.has(obj) ?
cache.get(obj) : {};
return Object.keys(map).indexOf(subject) !== -1;
}
function resultCache(obj: {}, subject: string) {
const map: {[subject: string]: Error | true} = cache.has(obj) ?
cache.get(obj) : {};
return map[subject];
}
export function requestUsernameValidation(username: string, validator: Validator) {
return {
type: USERNAME_VALIDATION_PENDING,
isValidating: true,
isValid: false,
username,
validator,
}
}
export function receiveUsernameValidationSuccess() {
return {
type: USERNAME_VALIDATION_SUCCESS,
isValidating: false,
isValid: true,
}
}
export function receiveUsernameValidationFailure(error: string) {
return {
type: USERNAME_VALIDATION_FAILURE,
isValidating: false,
isValid: false,
error,
}
}
export function validateUsername(username: string, validator: Validator) {
return (dispatch) => {
dispatch(requestUsernameValidation(username, validator));
if (resultCacheP(validator, username)) {
const result = resultCache(validator, username);
if (result instanceof Error) {
dispatch(receiveUsernameValidationSuccess());
} else {
dispatch(receiveUsernameValidationFailure(result.message));
}
} else {
validator.validate(username)
.then(() => {
memoize(validator, username, true);
dispatch(receiveUsernameValidationSuccess());
})
.catch((err: Error) => {
memoize(validator, username, err);
dispatch(receiveUsernameValidationFailure(err.message));
});
}
};
}
I would like for the requestUsernameValidation
to call cancel on the validator from the previous action if my state is currently validating to avoid a race condition where the previous validator finishes later and incorrectly updates the state.
I think I need to change requestUsernameValidation
to a thunk to get the current state.
This is my first attempt at using Redux. Most tutorials I've read have really lean action creators, but a lot of real world projects I've seen have a lot of complex logic in them. Is this approach the correct way to solve this?