Initial draft of array behaviors
This commit is contained in:
parent
8c19355486
commit
065126c761
|
@ -2,14 +2,17 @@
|
|||
//==============================
|
||||
// Flow imports
|
||||
//==============================
|
||||
import type { ObjectReducer, ObjectAction, ObjectSelector } from './reducers/objectReducer';
|
||||
|
||||
import {
|
||||
PROP_TYPES,
|
||||
} from './structure';
|
||||
import { compose } from 'ramda';
|
||||
import { createObjectReducer } from './reducers/objectReducer';
|
||||
import type {
|
||||
ObjectReducer,
|
||||
ObjectAction,
|
||||
ObjectSelector,
|
||||
ObjectReducerBehaviorsConfig,
|
||||
ObjectReducerBehaviors,
|
||||
} from './reducers/objectReducer';
|
||||
|
||||
//==============================
|
||||
// Flow types
|
||||
//==============================
|
||||
export type Selectors = ObjectSelector;
|
||||
export type Actions = ObjectAction;
|
||||
export type Reducers = ObjectReducer;
|
||||
|
@ -18,6 +21,16 @@ export type PartialReducer = {
|
|||
actionsObject: { [key: string]: Actions },
|
||||
selectorsObject?: { [key: string]: Selectors },
|
||||
};
|
||||
type ReducerBehaviorsConfig = ObjectReducerBehaviorsConfig;
|
||||
type ReducerBehaviors = ObjectReducerBehaviors;
|
||||
|
||||
import {
|
||||
PROP_TYPES,
|
||||
} from './structure';
|
||||
import { compose } from 'ramda';
|
||||
import { reduce } from 'lodash';
|
||||
import { createObjectReducer } from './reducers/objectReducer';
|
||||
|
||||
|
||||
function determineReducerType(reducerDescriptor, {
|
||||
locationString,
|
||||
|
@ -41,3 +54,11 @@ function callReducer({ reducerFn, reducerStructureDescriptor, locationString } =
|
|||
}
|
||||
|
||||
export const createReducer = compose(callReducer, determineReducerType);
|
||||
|
||||
export function createReducerBehaviors(behaviorsConfig: ReducerBehaviorsConfig, locationString: string): ReducerBehaviors {
|
||||
//Take a reducer behavior config object, and create the reducer behaviors using the location string
|
||||
return reduce(behaviorsConfig, (memo, behavior, name) => ({
|
||||
...memo,
|
||||
[`${locationString}.${name}`]: behavior.reducer,
|
||||
}), {});
|
||||
}
|
||||
|
|
|
@ -1 +1,134 @@
|
|||
//@flow
|
||||
//==============================
|
||||
// Flow imports
|
||||
//==============================
|
||||
import type { ShapeStructure, StructureType } from '../structure';
|
||||
|
||||
//==============================
|
||||
// Flow types
|
||||
//==============================
|
||||
export type ArrayReducerAction = {
|
||||
type: string,
|
||||
payload: any,
|
||||
};
|
||||
export type ArrayReducer = (state: Array<any>, action: ArrayReducerAction) => Array<any>;
|
||||
export type ArrayReducerBehavior = (state: Array<any>, payload: any, initialState: Array<any>, index: number | void) => Array<any>;
|
||||
export type ArrayReducerBehaviorsConfig = {
|
||||
[key: string]: {
|
||||
action?: (value: any) => any,
|
||||
reducer: ArrayReducerBehavior,
|
||||
}
|
||||
};
|
||||
export type ArrayReducerBehaviors = {
|
||||
[key: string]: ArrayReducerBehavior,
|
||||
};
|
||||
export type ArrayAction = (value: Array<any>) => { type: string, payload: any, index?: number };
|
||||
export type ArrayActions = {
|
||||
[key: string]: Array<any>
|
||||
};
|
||||
export type ArrayReducerOptions = {
|
||||
behaviorsConfig: ArrayReducerBehaviorsConfig,
|
||||
locationString: string,
|
||||
};
|
||||
export type ArraySelector = (state: Object) => Array<any>;
|
||||
|
||||
//==============================
|
||||
// JS imports
|
||||
//==============================
|
||||
import { reduce, isArray, isObject, isNull } from 'lodash';
|
||||
import { validateArray } from '../validatePayload';
|
||||
import { createReducerBehaviors } from '../reducers';
|
||||
import { updateAtIndex, removeAtIndex } from '../utils/arrayUtils';
|
||||
|
||||
function checkIndex(state: Object, payload: any, behaviorName: string): boolean {
|
||||
if (!isNumber(index)) {
|
||||
throw new Error(`Index not passed to ${behaviorName} for payload ${payload}.`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const DEFAULT_OBJECT_BEHAVIORS: ArrayReducerBehaviorsConfig = {
|
||||
updateAtIndex: {
|
||||
action(value) { return value },
|
||||
reducer(state, payload, initialState, index) {
|
||||
checkIndex(index, payload, 'updateOne');
|
||||
if (isArray(payload) || isObject(payload)) return updateAtIndex(state, { ...state[index], ...payload }, index);
|
||||
return updateAtIndex(state, payload, index);
|
||||
}
|
||||
},
|
||||
resetAtIndex: {
|
||||
reducer(state, payload, initialState, index) {
|
||||
checkIndex(index, payload, 'updateOne');
|
||||
return updateAtIndex(state, initialState, index);
|
||||
}
|
||||
},
|
||||
removeAtIndex: {
|
||||
reducer(state, payload, initialState, index) {
|
||||
checkIndex(index, payload, 'updateOne');
|
||||
return removeAtIndex(state, index);
|
||||
}
|
||||
},
|
||||
replaceAtIndex: {
|
||||
reducer(state, payload, initialState, index) {
|
||||
checkIndex(index, payload, 'updateOne');
|
||||
return updateAtIndex(state, payload, index);
|
||||
}
|
||||
},
|
||||
replace: {
|
||||
action(value) {
|
||||
if(!isArray(value)) throw new Error('An array must be provided when replacing an array');
|
||||
},
|
||||
reducer(state, payload) {
|
||||
return payload;
|
||||
}
|
||||
},
|
||||
reset: {
|
||||
reducer(state, payload, initialState) {
|
||||
return initialState;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
//TODO: All the array functionality!
|
||||
|
||||
export function createArrayReducer(reducerShape: StructureType, {
|
||||
locationString
|
||||
}: ArrayReducerOptions = {}) {
|
||||
return {
|
||||
reducers: createReducer(reducerShape, createReducerBehaviors(DEFAULT_OBJECT_BEHAVIORS, locationString)),
|
||||
actionsObject: createActions(DEFAULT_OBJECT_BEHAVIORS, locationString, {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function calculateDefaults(reducerStructure) {
|
||||
return reduce(reducerStructure, (memo, propValue, propName) => ({
|
||||
...memo,
|
||||
[propName]: propValue().defaultValue,
|
||||
}), {});
|
||||
}
|
||||
|
||||
|
||||
function createReducer(arrayStructure: StructureType, behaviors: ArrayReducerBehaviors): ArrayReducer {
|
||||
const initialState = calculateDefaults(arrayStructure().structure);
|
||||
return (state = initialState, { type, payload }: ArrayReducerAction) => {
|
||||
//If the action type does not match any of the specified behaviors, just return the current state.
|
||||
if (!behaviors[type]) return state;
|
||||
|
||||
//Sanitize the payload using the reducer shape, then apply the sanitized
|
||||
//payload to the state using the behavior linked to this action type.
|
||||
return behaviors[type](state, validateArray(arrayStructure, payload), initialState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createActions(behaviorsConfig: ArrayReducerBehaviorsConfig, locationString: string, defaultPayload: any): ArrayActions {
|
||||
//Take a reducer behavior config object, and create actions using the location string
|
||||
return reduce(behaviorsConfig, (memo, behavior, name) => ({
|
||||
...memo,
|
||||
[name]: (value: Object) => ({
|
||||
type: `${locationString}.${name}`,
|
||||
payload: (behavior.action || (() => defaultPayload))(value) || {}
|
||||
})
|
||||
}), {});
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ export type ObjectSelector = (state: Object) => Object;
|
|||
//==============================
|
||||
import { reduce } from 'lodash';
|
||||
import { validateObject } from '../validatePayload';
|
||||
import { createReducerBehaviors } from '../reducers';
|
||||
|
||||
const DEFAULT_OBJECT_BEHAVIORS: ObjectReducerBehaviorsConfig = {
|
||||
update: {
|
||||
|
@ -102,12 +103,3 @@ function createActions(behaviorsConfig: ObjectReducerBehaviorsConfig, locationSt
|
|||
}
|
||||
|
||||
|
||||
function createReducerBehaviors(behaviorsConfig: ObjectReducerBehaviorsConfig, locationString: string): ObjectReducerBehaviors {
|
||||
//Take a reducer behavior config object, and create the reducer behaviors using the location string
|
||||
return reduce(behaviorsConfig, (memo, behavior, name) => ({
|
||||
...memo,
|
||||
[`${locationString}.${name}`]: behavior.reducer,
|
||||
}), {});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
//@flow
|
||||
export function updateAtIndex(array: Array<any>, value: any, index: number): Array<any> {
|
||||
return [
|
||||
...array.slice(0,index),
|
||||
value,
|
||||
...array.slice(index + 1),
|
||||
];
|
||||
}
|
||||
|
||||
export function removeAtIndex(array: Array<any>, index: number): Array<any> {
|
||||
return [
|
||||
...array.slice(0, index),
|
||||
...array.slice(index + 1),
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue