Skip to content

Commit

Permalink
Stabilizing fetch options saga (#575)
Browse files Browse the repository at this point in the history
Co-authored-by: Ole Martin Handeland <[email protected]>
  • Loading branch information
olemartinorg and Ole Martin Handeland authored Oct 21, 2022
1 parent 669c351 commit 5b20100
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,8 @@ export function PanelGroupContainer({
state.textResources.resources,
),
);
const repeatingGroups = useAppSelector(
(state) => state.formLayout.uiConfig.repeatingGroups,
);
const repeatingGroups =
useAppSelector((state) => state.formLayout.uiConfig.repeatingGroups) || {};
const { iconUrl, iconAlt } = container.panel;
const fullWidth = !container.baseComponentId;
const repGroupReference = container.panel?.groupReference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('layoutSlice', () => {
}),
);

expect(nextState.uiConfig.repeatingGroups).toEqual({});
expect(nextState.uiConfig.repeatingGroups).toBeNull();
});

it('should reset error if set', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const initialState: ILayoutState = {
focus: null,
hiddenFields: [],
autoSave: null,
repeatingGroups: {},
repeatingGroups: null,
fileUploadersWithTag: {},
currentView: 'FormLayout',
navigationConfig: {},
Expand Down Expand Up @@ -72,7 +72,7 @@ const formLayoutSlice = createSagaSlice(
state.uiConfig.navigationConfig = navigationConfig;
state.uiConfig.tracks.order = Object.keys(layouts);
state.error = null;
state.uiConfig.repeatingGroups = {};
state.uiConfig.repeatingGroups = null;
},
takeLatest: function* () {
yield put(OptionsActions.fetch());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ export function* updateRepeatingGroupEditIndexSaga({
export function* initRepeatingGroupsSaga(): SagaIterator {
const formDataState: IFormDataState = yield select(selectFormData);
const state: IRuntimeState = yield select();
const currentGroups = state.formLayout.uiConfig.repeatingGroups;
const currentGroups = state.formLayout.uiConfig.repeatingGroups || {};
const layouts = yield select(selectFormLayouts);
let newGroups: IRepeatingGroups = {};
Object.keys(layouts).forEach((layoutKey: string) => {
Expand All @@ -640,7 +640,7 @@ export function* initRepeatingGroupsSaga(): SagaIterator {
};
});
// if any groups have been removed as part of calculation we delete the associated validations
const currentGroupKeys = Object.keys(currentGroups || {});
const currentGroupKeys = Object.keys(currentGroups);
const groupsToRemoveValidations = currentGroupKeys.filter((key) => {
return (
currentGroups[key].index > -1 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
watchFetchLanguageSaga,
} from 'src/shared/resources/language/fetch/fetchLanguageSagas';
import { LanguageActions } from 'src/shared/resources/language/languageSlice';
import { waitForFunc } from 'src/utils/sagas';
import { waitFor } from 'src/utils/sagas';

import { getLanguageFromCode } from 'altinn-shared/language';
import * as language from 'altinn-shared/language';
Expand Down Expand Up @@ -60,9 +60,7 @@ describe('fetchLanguageSagas', () => {
expect(generator.next().value).toEqual(
select(makeGetAllowAnonymousSelector()),
);
expect(generator.next().value).toEqual(
call(waitForFunc, expect.anything()),
);
expect(generator.next().value).toEqual(waitFor(expect.anything()));
expect(generator.next().value).toEqual(call(fetchLanguageSaga));
expect(generator.next().value).toEqual(
takeLatest(LanguageActions.updateSelectedAppLanguage, fetchLanguageSaga),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
optionsWithIndexIndicatorsSelector,
repeatingGroupsSelector,
} from 'src/shared/resources/options/fetch/fetchOptionsSagas';
import { selectNotNull } from 'src/utils/sagas';
import type {
ILayouts,
ISelectionComponentProps,
Expand Down Expand Up @@ -136,8 +137,8 @@ describe('fetchOptionsSagas', () => {

return expectSaga(fetchOptionsSaga)
.provide([
[select(formLayoutSelector), formLayoutWithTwoSharedOptionIds],
[select(repeatingGroupsSelector), {}],
[selectNotNull(formLayoutSelector), formLayoutWithTwoSharedOptionIds],
[selectNotNull(repeatingGroupsSelector), {}],
[select(instanceIdSelector), 'someId'],
])
.fork(fetchSpecificOptionSaga, {
Expand Down Expand Up @@ -195,10 +196,10 @@ describe('fetchOptionsSagas', () => {
return expectSaga(fetchOptionsSaga)
.provide([
[
select(formLayoutSelector),
selectNotNull(formLayoutSelector),
formLayoutWithSameOptionIdButDifferentMapping,
],
[select(repeatingGroupsSelector), {}],
[selectNotNull(repeatingGroupsSelector), {}],
[select(instanceIdSelector), 'someId'],
])
.fork(fetchSpecificOptionSaga, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { all, call, fork, put, select, take } from 'redux-saga/effects';
import { call, fork, put, select } from 'redux-saga/effects';
import type { PayloadAction } from '@reduxjs/toolkit';
import type { SagaIterator } from 'redux-saga';

import { FormLayoutActions } from 'src/features/form/layout/formLayoutSlice';
import { appLanguageStateSelector } from 'src/selectors/appLanguageStateSelector';
import { OptionsActions } from 'src/shared/resources/options/optionsSlice';
import { getOptionsUrl } from 'src/utils/appUrlHelper';
Expand All @@ -13,6 +12,7 @@ import {
replaceIndexIndicatorsWithIndexes,
} from 'src/utils/databindings';
import { getOptionLookupKey, getOptionLookupKeys } from 'src/utils/options';
import { selectNotNull } from 'src/utils/sagas';
import type { IFormData } from 'src/features/form/data';
import type { IUpdateFormDataFulfilled } from 'src/features/form/data/formDataTypes';
import type {
Expand All @@ -31,7 +31,7 @@ import type {
import { get } from 'altinn-shared/utils';

export const formLayoutSelector = (state: IRuntimeState): ILayouts =>
state.formLayout.layouts;
state.formLayout?.layouts;
export const formDataSelector = (state: IRuntimeState) =>
state.formData.formData;
export const optionsSelector = (state: IRuntimeState): IOptions =>
Expand All @@ -40,13 +40,12 @@ export const optionsWithIndexIndicatorsSelector = (state: IRuntimeState) =>
state.optionState.optionsWithIndexIndicators;
export const instanceIdSelector = (state: IRuntimeState): string =>
state.instanceData.instance?.id;
export const repeatingGroupsSelector = (
state: IRuntimeState,
): IRepeatingGroups => state.formLayout.uiConfig.repeatingGroups;
export const repeatingGroupsSelector = (state: IRuntimeState) =>
state.formLayout?.uiConfig.repeatingGroups;

export function* fetchOptionsSaga(): SagaIterator {
const layouts: ILayouts = yield select(formLayoutSelector);
const repeatingGroups: IRepeatingGroups = yield select(
const layouts: ILayouts = yield selectNotNull(formLayoutSelector);
const repeatingGroups: IRepeatingGroups = yield selectNotNull(
repeatingGroupsSelector,
);

Expand Down Expand Up @@ -95,16 +94,6 @@ export function* fetchOptionsSaga(): SagaIterator {
);
}

export function* watchFetchOptionsSaga(): SagaIterator {
while (true) {
yield all([
take(FormLayoutActions.updateRepeatingGroupsFulfilled),
take(OptionsActions.fetch),
]);
yield call(fetchOptionsSaga);
}
}

export function* fetchSpecificOptionSaga({
optionsId,
dataMapping,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { watchFetchOptionsSaga } from 'src/shared/resources/options/fetch/fetchOptionsSagas';
import { fetchOptionsSaga } from 'src/shared/resources/options/fetch/fetchOptionsSagas';
import { createSagaSlice } from 'src/shared/resources/utils/sagaSlice';
import type {
IFetchingOptionsAction,
Expand All @@ -22,7 +22,7 @@ const optionsSlice = createSagaSlice(
initialState,
actions: {
fetch: mkAction<void>({
saga: () => watchFetchOptionsSaga,
takeEvery: fetchOptionsSaga,
}),
fetchFulfilled: mkAction<IFetchOptionsFulfilledAction>({
reducer: (state, action) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { TextResourcesActions } from 'src/shared/resources/textResources/textResourcesSlice';
import { textResourcesUrl } from 'src/utils/appUrlHelper';
import { get } from 'src/utils/networking';
import { waitForFunc } from 'src/utils/sagas';
import { waitFor } from 'src/utils/sagas';

import type { IProfile } from 'altinn-shared/types';

Expand All @@ -31,9 +31,7 @@ describe('fetchTextResourcesSagas', () => {
expect(generator.next().value).toEqual(
select(makeGetAllowAnonymousSelector()),
);
expect(generator.next().value).toEqual(
call(waitForFunc, expect.anything()),
);
expect(generator.next().value).toEqual(waitFor(expect.anything()));
expect(generator.next().value).toEqual(call(fetchTextResources));
expect(generator.next().value).toEqual(
takeLatest(TextResourcesActions.fetch, fetchTextResources),
Expand Down
20 changes: 19 additions & 1 deletion src/altinn-app-frontend/src/utils/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { SagaIterator } from 'redux-saga';

import type { IRuntimeState } from 'src/types';

export function* waitForFunc(
function* waitForFunc(
selector: (state: IRuntimeState) => boolean,
): SagaIterator {
if (yield select(selector)) {
Expand All @@ -25,3 +25,21 @@ export function* waitForFunc(
*/
export const waitFor = (selector: (state: IRuntimeState) => boolean) =>
call(waitForFunc, selector);

function* selectNotNullFunc<T>(selector: (state: IRuntimeState) => T): any {
let result = null;
yield waitFor((state) => {
result = selector(state);
return result !== null && result !== undefined;
});

return result;
}

/**
* This builds on the select() saga effect, but will waitFor() your selected state to not be null (or undefined).
* This lets you easily select a state from redux without having to know which action needs to fulfill in order to
* populate the data you need.
*/
export const selectNotNull = <T>(selector: (state: IRuntimeState) => T) =>
call(selectNotNullFunc, selector);
2 changes: 1 addition & 1 deletion src/altinn-app-frontend/src/utils/validation/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1620,7 +1620,7 @@ export function validateGroup(
const textResources = state.textResources.resources;
const hiddenFields = state.formLayout.uiConfig.hiddenFields;
const attachments = state.attachments.attachments;
const repeatingGroups = state.formLayout.uiConfig.repeatingGroups;
const repeatingGroups = state.formLayout.uiConfig.repeatingGroups || {};
const formData = state.formData.formData;
const jsonFormData = convertDataBindingToModel(formData);
const currentView = state.formLayout.uiConfig.currentView;
Expand Down

0 comments on commit 5b20100

Please sign in to comment.