informed
Version:
A lightweight framework and utility for building powerful forms in React applications
339 lines (310 loc) • 12.8 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
var React = require('react');
var useStateWithGetter = require('./useStateWithGetter.js');
var debug = require('../debug.js');
var Context = require('../Context.js');
var utils = require('../utils.js');
var ObjectMap = require('../ObjectMap.js');
var useFieldApi = require('./useFieldApi.js');
var useScope = require('./useScope.js');
var useFormApi = require('./useFormApi.js');
var useFieldSubscription = require('./useFieldSubscription.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var logger = debug.Debug('informed:useArrayField' + '\t');
var useArrayField = function useArrayField(_ref) {
var userName = _ref.name,
initialValue = _ref.initialValue,
defaultValue = _ref.defaultValue,
arrayFieldApiRef = _ref.arrayFieldApiRef;
// Name might be scoped
var name = useScope.useScope(userName);
// Grab the form register context
var formController = React.useContext(Context.FormControllerContext);
// Hook onto the field api
var fieldApi = useFieldApi.useFieldApi(name);
// Gook onto the form api
var formApi = useFormApi.useFormApi();
// For knowing if we are performing reset
var resetRef = React.useRef(false);
// Map will store all fields by name
// Key => name
// Val => fieldMetaRef
// Why? so the array knows about all its field meta
var _useState = React.useState(function () {
return new Map();
}),
_useState2 = _rollupPluginBabelHelpers.slicedToArray(_useState, 1),
fieldsMap = _useState2[0];
var _useStateWithGetter = useStateWithGetter.useStateWithGetter(function () {
// If we already have value i.e "saved"
// use that ( it was not removed on purpose! )
if (formController.getValue(name)) {
return formController.getValue(name);
}
return initialValue || formController.getInitialValue(name) || defaultValue || [];
}),
_useStateWithGetter2 = _rollupPluginBabelHelpers.slicedToArray(_useStateWithGetter, 3),
initialValues = _useStateWithGetter2[0],
setInitialValues = _useStateWithGetter2[1],
getInitialValues = _useStateWithGetter2[2];
// TODO Need to use saved state to initialize ( after being re rendered )
var initialKeys = Array.isArray(initialValues) ? initialValues.map(function () {
return utils.uuidv4();
}) : [];
var _useStateWithGetter3 = useStateWithGetter.useStateWithGetter(initialKeys),
_useStateWithGetter4 = _rollupPluginBabelHelpers.slicedToArray(_useStateWithGetter3, 3),
keys = _useStateWithGetter4[0],
setKeys = _useStateWithGetter4[1],
getKeys = _useStateWithGetter4[2];
var _remove = function remove(i) {
// Always get ref to latest keys
var ks = getKeys();
// Notify form to expect removal on the last field
formController.lockRemoval({
index: ks.length - 1,
name: name
});
// Remove the key
var newKeys = ks.slice(0, i).concat(ks.slice(i + 1, ks.length));
setKeys(newKeys);
// Remove the initial value ( user wanted to get rid of that input )
var initVals = getInitialValues();
var newInitialValues = initVals.slice(0, i).concat(initVals.slice(i + 1, initVals.length));
setInitialValues(newInitialValues);
// We need to manually do a pull from the form state
formController.pullOut("".concat(name, "[").concat(i, "]"));
formApi.setDirt(name, []);
formController.emitter.emit('field-value-set', name);
};
var swap = function swap(a, b) {
logger('Swapping', "".concat(name, "[").concat(a, "] and ").concat(name, "[").concat(b, "]"));
formController.swap(name, a, b);
// Always get ref to latest keys
var ks = getKeys();
// Swap the keys
var newKeys = _rollupPluginBabelHelpers.toConsumableArray(ks);
if (ks[a] && ks[b]) {
newKeys[a] = ks[b];
newKeys[b] = ks[a];
} else {
// eslint-disable-next-line no-console
console.warn("Attempted to swap ".concat(a, " with ").concat(b, " but one of them does not exist :("));
}
setKeys(newKeys);
};
var add = function add() {
var amount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
var ks = getKeys();
// if 'amount' is not defined, run the default behavior to add 1 field
if (typeof amount !== 'number' || !Number(amount) || amount <= 0) {
ks.push(utils.uuidv4());
} else {
for (var i = 0; i < amount; i++) {
ks.push(utils.uuidv4());
}
}
setKeys(_rollupPluginBabelHelpers.toConsumableArray(ks));
// If we added a new field we are no longer pristine
formApi.setPristine(false);
formApi.setDirt(name, []);
formController.emitter.emit('field-value-set', name);
};
var addWithInitialValue = function addWithInitialValue(initialValue) {
var ks = getKeys();
ks.push(utils.uuidv4());
setKeys(_rollupPluginBabelHelpers.toConsumableArray(ks));
var newInitialValues = _rollupPluginBabelHelpers.toConsumableArray(getInitialValues());
newInitialValues[ks.length - 1] = initialValue;
setInitialValues(newInitialValues);
};
var insert = function insert(index, value) {
logger("Inserting at index ".concat(index, " in ").concat(name, " with value"), value);
// Use ObjectMap to insert the value into the form state
formController.insert(name, index, value);
// Always get ref to the latest keys
var ks = getKeys();
// Insert a new key at the specified index
var newKeys = _rollupPluginBabelHelpers.toConsumableArray(ks);
newKeys.splice(index, 0, utils.uuidv4());
setKeys(newKeys);
// Insert the initial value at the specified index
var initVals = getInitialValues();
var newInitialValues = _rollupPluginBabelHelpers.toConsumableArray(initVals);
newInitialValues.splice(index, 0, value);
setInitialValues(newInitialValues);
};
var initialValueRef = React.useRef();
initialValueRef.current = initialValue;
var defaultValueRef = React.useRef();
defaultValueRef.current = defaultValue;
var reset = function reset() {
// First wipe the existing state
// Array fields are unique.. because reset will create new keys every field will get unmounted
// So, we can start by simply wiping out the keys below here ( same thing we do at form level reset )
// this will result in all fields performing their cleanup rutines
logger("------------ ".concat(name, " Array Field Reset Start ------------"));
// Performing reset so we set the flag
resetRef.current = true;
// Remove array field
formController.remove(name);
// Build new initial values
var initVals = initialValueRef.current || formController.getInitialValue(name) || defaultValueRef.current || [];
// Set our initial values back to what the user set at beginning
setInitialValues(initVals);
// Clear out keys ( we wait until all fields have deregistered before resetting )
setKeys([]);
// ---vv Special case when there are no fields so we do it right away vv---
if (!fieldsMap.size && resetRef.current) {
// V important we flag that we are done performing reset as all fields have deregistered
resetRef.current = false;
// For debug logging we show when complete
logger("------------ ".concat(name, " Array Field Reset End ------------"));
var _initVals = getInitialValues();
// Build a new set of keys because everything is new !!!
var resetKeys = _initVals ? _initVals.map(function () {
return utils.uuidv4();
}) : [];
// Finally set that shit !
setKeys(resetKeys);
}
// ---^^ Special case when there are no fields so we do it right away ^^---
};
var clear = function clear() {
formController.remove(name);
setInitialValues([]);
setKeys([]);
};
// Create meta object
var meta = {
name: name,
initialValue: initialValue,
fieldApi: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, fieldApi), {}, {
reset: reset
}),
arrayField: true
};
var metaRef = React.useRef(meta);
metaRef.current = meta;
// Register as if its a field muahhahaha
React.useEffect(function () {
formController.register(name, metaRef);
return function () {
formController.deregister(name);
};
}, [name]);
var fields = keys.map(function (key, i) {
var arrayFieldItemApi = {
remove: function remove() {
return _remove(i);
}
};
var arrayFieldItemState = {
initialValue: initialValues && initialValues[i],
key: key,
name: "".concat(name, "[").concat(i, "]"),
index: i,
parent: name
};
return {
arrayFieldItemApi: arrayFieldItemApi,
arrayFieldItemState: arrayFieldItemState
};
});
var arrayFieldApi = React.useMemo(function () {
return {
add: add,
remove: _remove,
swap: swap,
addWithInitialValue: addWithInitialValue,
insert: insert,
reset: reset,
clear: clear
};
}, []);
if (arrayFieldApiRef) {
arrayFieldApiRef.current = arrayFieldApi;
}
var arrayFieldState = {
fields: fields,
name: name
// hidden
};
// Wrap the updater to update array fields references
var wrappedController = React.useMemo(function () {
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, formController), {}, {
register: function register(n, m) {
fieldsMap.set(n, m);
formController.register(n, m);
},
deregister: function deregister(n) {
formController.deregister(n);
// Remove from our map
fieldsMap["delete"](n);
// On last deregister we finally complete
// console.log(`${name}-WTF1`, fieldsMap.size);
// console.log(`${name}-WTF2`, resetRef.current);
// NOTE: I originally tried to put the below logic inside of remove
// However this cuases issues because deregister is called with the correct name where remove may have old name
// Example [ 0, 1, 2 ] if we remove 1 then 2 moves to 1s place
if (!fieldsMap.size && resetRef.current) {
// V important we flag that we are done performing reset as all fields have deregistered
resetRef.current = false;
// For debug logging we show when complete
logger("------------ ".concat(name, " Array Field Reset End ------------"));
var initVals = getInitialValues();
// Build a new set of keys because everything is new !!!
var resetKeys = initVals ? initVals.map(function () {
return utils.uuidv4();
}) : [];
// Finally set that shit !
setKeys(resetKeys);
}
},
getInitialValue: function getInitialValue(fieldName) {
// If we are getting initial value and its for this field return that
// Case1:
// name = "friends"
// fieldName = "friends[0].name"
//
// Case2:
// name = "friends[0].siblings"
// fieldName = "friends[0].siblings[0].name"
// Use a regex to specifically target the last [0-9]+ and anything that follows
var modifiedFieldName = fieldName.replace(/(\[[0-9]+\])[^[\]]*$/, '');
// Check if they match
if (modifiedFieldName === name) {
var path = fieldName.replace(name, '');
var v = ObjectMap.ObjectMap.get(getInitialValues(), path);
logger("Getting initial value for ".concat(path, " which is ").concat(v));
return v;
}
return formController.getInitialValue(fieldName);
}
});
}, [name]);
useFieldSubscription.useFieldSubscription('clear', [name], function (target) {
logger("clear event triggered for ".concat(name, " because of ").concat(target));
clear();
}, false,
// No scope ( lol ) because we are already scoped
true // Flip order of target comparison
);
var render = function render(children) {
return /*#__PURE__*/React__default["default"].createElement(Context.FormControllerContext.Provider, {
value: wrappedController
}, /*#__PURE__*/React__default["default"].createElement(Context.ArrayFieldApiContext.Provider, {
value: arrayFieldApi
}, /*#__PURE__*/React__default["default"].createElement(Context.ArrayFieldStateContext.Provider, {
value: arrayFieldState
}, children)));
};
return {
render: render,
arrayFieldState: arrayFieldState,
arrayFieldApi: arrayFieldApi
};
};
exports.useArrayField = useArrayField;