Initial draft of array behaviors
This commit is contained in:
parent
8c19355486
commit
065126c761
|
@ -2,14 +2,17 @@
|
||||||
//==============================
|
//==============================
|
||||||
// Flow imports
|
// Flow imports
|
||||||
//==============================
|
//==============================
|
||||||
import type { ObjectReducer, ObjectAction, ObjectSelector } from './reducers/objectReducer';
|
import type {
|
||||||
|
ObjectReducer,
|
||||||
import {
|
ObjectAction,
|
||||||
PROP_TYPES,
|
ObjectSelector,
|
||||||
} from './structure';
|
ObjectReducerBehaviorsConfig,
|
||||||
import { compose } from 'ramda';
|
ObjectReducerBehaviors,
|
||||||
import { createObjectReducer } from './reducers/objectReducer';
|
} from './reducers/objectReducer';
|
||||||
|
|
||||||
|
//==============================
|
||||||
|
// Flow types
|
||||||
|
//==============================
|
||||||
export type Selectors = ObjectSelector;
|
export type Selectors = ObjectSelector;
|
||||||
export type Actions = ObjectAction;
|
export type Actions = ObjectAction;
|
||||||
export type Reducers = ObjectReducer;
|
export type Reducers = ObjectReducer;
|
||||||
|
@ -18,6 +21,16 @@ export type PartialReducer = {
|
||||||
actionsObject: { [key: string]: Actions },
|
actionsObject: { [key: string]: Actions },
|
||||||
selectorsObject?: { [key: string]: Selectors },
|
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, {
|
function determineReducerType(reducerDescriptor, {
|
||||||
locationString,
|
locationString,
|
||||||
|
@ -41,3 +54,11 @@ function callReducer({ reducerFn, reducerStructureDescriptor, locationString } =
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createReducer = compose(callReducer, determineReducerType);
|
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 { reduce } from 'lodash';
|
||||||
import { validateObject } from '../validatePayload';
|
import { validateObject } from '../validatePayload';
|
||||||
|
import { createReducerBehaviors } from '../reducers';
|
||||||
|
|
||||||
const DEFAULT_OBJECT_BEHAVIORS: ObjectReducerBehaviorsConfig = {
|
const DEFAULT_OBJECT_BEHAVIORS: ObjectReducerBehaviorsConfig = {
|
||||||
update: {
|
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