Socket
Socket
Sign inDemoInstall

formula-one

Package Overview
Dependencies
6
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.0 to 0.2.1

CHANGELOG.md

107

dist/Form.js

@@ -8,7 +8,7 @@ "use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); // strict

@@ -21,6 +21,8 @@ var _react = require("react");

var _formState = require("./formState");
require("./formState");
var _shapedTree = require("./shapedTree");
var _tree = require("./tree");
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

@@ -32,3 +34,3 @@

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // strict
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

@@ -43,11 +45,48 @@ var FormContext = exports.FormContext = React.createContext({

function newFormState(value, serverErrors) {
var cleanState = [value, (0, _shapedTree.treeFromValue)(value, {
errors: _types.cleanErrors,
meta: _types.cleanMeta
})];
if (serverErrors != null) {
return (0, _formState.replaceServerErrors)(serverErrors, cleanState);
function applyServerErrorsToFormState(serverErrors, formState) {
var _formState = _slicedToArray(formState, 2),
value = _formState[0],
oldTree = _formState[1];
var tree = void 0;
if (serverErrors !== null) {
// If keys do not appear, no errors
tree = (0, _shapedTree.mapShapedTree)(function (_ref) {
var errors = _ref.errors,
meta = _ref.meta;
return {
errors: _extends({}, errors, { server: [] }),
meta: meta
};
}, oldTree);
Object.keys(serverErrors).forEach(function (key) {
var newErrors = serverErrors[key];
var path = (0, _shapedTree.shapePath)(value, (0, _tree.pathFromPathString)(key));
if (path != null) {
// TODO(zach): make some helper functions that do this
tree = (0, _shapedTree.updateAtPath)(path, function (_ref2) {
var errors = _ref2.errors,
meta = _ref2.meta;
return {
errors: _extends({}, errors, { server: newErrors }),
meta: meta
};
}, tree);
} else {
console.error("Warning: couldn't match error with path " + key + " to value " + JSON.stringify(value));
}
});
} else {
tree = (0, _shapedTree.mapShapedTree)(function (_ref3) {
var errors = _ref3.errors,
meta = _ref3.meta;
return {
errors: _extends({}, errors, { server: [] }),
meta: meta
};
}, oldTree);
}
return cleanState;
return [value, tree];
}

@@ -81,5 +120,5 @@

if (props.serverErrors !== state.oldServerErrors) {
var serverErrorsTree = Form.makeServerErrorTree(state.formState[0], props.serverErrors);
var newFormState = applyServerErrorsToFormState(props.serverErrors, state.formState);
return {
formState: (0, _formState.replaceServerErrors)(serverErrorsTree, state.formState),
formState: newFormState,
oldServerErrors: props.serverErrors

@@ -90,20 +129,2 @@ };

}
}, {
key: "makeServerErrorTree",
value: function makeServerErrorTree(value, errorsObj) {
if (errorsObj != null) {
try {
var freshTree = (0, _shapedTree.treeFromValue)(value, []);
// TODO(zach): Variance problems $FlowFixMe
return (0, _shapedTree.setFromKeysObj)(errorsObj, freshTree);
} catch (e) {
console.error("Error applying server errors to value!");
console.error("\t" + e.message);
console.error("The server errors will be ignored.");
return (0, _shapedTree.treeFromValue)(value, "unchecked");
}
} else {
return (0, _shapedTree.treeFromValue)(value, "unchecked");
}
}
}]);

@@ -134,5 +155,5 @@

var updater = function updater(newErrors) {
return function (_ref) {
var errors = _ref.errors,
meta = _ref.meta;
return function (_ref4) {
var errors = _ref4.errors,
meta = _ref4.meta;
return {

@@ -146,6 +167,6 @@ errors: _extends({}, errors, { client: newErrors }),

};
_this.setState(function (_ref2) {
var _ref2$formState = _slicedToArray(_ref2.formState, 2),
value = _ref2$formState[0],
tree = _ref2$formState[1];
_this.setState(function (_ref5) {
var _ref5$formState = _slicedToArray(_ref5.formState, 2),
value = _ref5$formState[0],
tree = _ref5$formState[1];

@@ -158,5 +179,9 @@ return {

var serverErrors = Form.makeServerErrorTree(props.initialValue, props.serverErrors);
var freshTree = (0, _shapedTree.treeFromValue)(props.initialValue, {
errors: _types.cleanErrors,
meta: _types.cleanMeta
});
var formState = applyServerErrorsToFormState(props.serverErrors, [props.initialValue, freshTree]);
_this.state = {
formState: newFormState(props.initialValue, serverErrors),
formState: formState,
pristine: true,

@@ -163,0 +188,0 @@ submitted: false,

@@ -218,5 +218,3 @@ "use strict";

function replaceServerErrors(serverErrors, formState) {
return [formState[0], (0, _shapedTree.shapedZipWith)(function (es, oldExtras) {
return replaceServerErrorsExtra(es, oldExtras);
}, serverErrors, formState[1])];
return [formState[0], (0, _shapedTree.shapedZipWith)(replaceServerErrorsExtra, serverErrors, formState[1])];
}

@@ -6,2 +6,3 @@ "use strict";

});
exports.rootPath = undefined;

@@ -11,3 +12,3 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; // strict

exports.treeFromValue = treeFromValue;
exports.setFromKeysObj = setFromKeysObj;
exports.shapePath = shapePath;
exports.updateAtPath = updateAtPath;

@@ -25,2 +26,3 @@ exports.checkShape = checkShape;

exports.shapedZipWith = shapedZipWith;
exports.mapShapedTree = mapShapedTree;

@@ -37,2 +39,4 @@ var _tree = require("./tree");

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }

@@ -42,5 +46,2 @@

// Take shape from value, data from nodeData
// Shape is a phantom type used to track the shape of the Tree

@@ -51,3 +52,9 @@ // eslint-disable-next-line no-unused-vars

// A path on a shaped tree
// TODO(zach): Make this opaque
// eslint-disable-next-line no-unused-vars
var rootPath = exports.rootPath = function rootPath() {
return [];
};
// Take shape from value, data from nodeData
function treeFromValue(value, nodeData) {

@@ -81,50 +88,31 @@ if (Array.isArray(value)) {

function setKey(key, value, tree) {
if (key[0] !== "/") {
throw new Error("Error paths must start with forward-slash");
function shapePath(data, path) {
if (path.length === 0) {
return path;
}
return _setKey(key.slice(1), value, tree);
}
function _setKey(key, value, tree) {
if (key === "") {
return mapRoot(function () {
return value;
}, tree);
}
var _path = _toArray(path),
firstPart = _path[0],
restParts = _path.slice(1);
var _key$split = key.split("/"),
_key$split2 = _toArray(_key$split),
firstPart = _key$split2[0],
restParts = _key$split2.slice(1);
if (tree.type === "leaf") {
throw new Error("Theres more key, but not more Tree to match it against");
if (firstPart.type === "object" && Object.hasOwnProperty.call(data, firstPart.key)) {
// $FlowFixMe: This is safe
var restPath = shapePath(data[firstPart.key], restParts);
if (restPath === null) {
return null;
}
return [firstPart].concat(_toConsumableArray(restPath));
} else if (firstPart.type === "array" && Array.isArray(data) && firstPart.index < data.length) {
var _restPath = shapePath(data[firstPart.index], restParts);
if (_restPath === null) {
return null;
}
return [firstPart].concat(_toConsumableArray(_restPath));
}
if (tree.type === "array") {
var index = Number.parseInt(firstPart);
(0, _invariant2.default)(index.toString() === firstPart, "Key indexing into an array is not a number");
(0, _invariant2.default)(index >= 0, "Key indexing into array is negative");
(0, _invariant2.default)(index < tree.children.length, "Key indexing array is outside array bounds");
var newChild = _setKey(restParts.join("/"), value, tree.children[index]);
// $FlowFixMe(zach): I think this is safe, might need GADTs for the type checker to understand why
return dangerouslyReplaceArrayChild(index, newChild, tree);
}
if (tree.type === "object") {
(0, _invariant2.default)(tree.children.hasOwnProperty(firstPart), "Key indexing into object does not exist");
var _newChild = _setKey(restParts.join("/"), value, tree.children[firstPart]);
// $FlowFixMe(zach): I think this is safe, might need GADTs for the type checker to understand why
return dangerouslyReplaceObjectChild(firstPart, _newChild, tree);
}
throw new Error("unreachable");
return null;
}
function setFromKeysObj(keysObj, tree) {
return Object.keys(keysObj).reduce(function (memo, key) {
return setKey(key, keysObj[key], memo);
}, tree);
}
function updateAtPath(path, updater, tree) {
// console.log("updateAtPath()", path, tree);
if (path.length === 0) {

@@ -151,5 +139,5 @@ if (tree.type === "object") {

var _path = _toArray(path),
firstStep = _path[0],
restStep = _path.slice(1);
var _path2 = _toArray(path),
firstStep = _path2[0],
restStep = _path2.slice(1);

@@ -168,5 +156,5 @@ if (tree.type === "leaf") {

(0, _invariant2.default)(firstStep.type === "object", "Trying to take a non-object path into an object");
var _newChild2 = updateAtPath(restStep, updater, tree.children[firstStep.key]);
var _newChild = updateAtPath(restStep, updater, tree.children[firstStep.key]);
// $FlowFixMe(zach): I think this is safe, might need GADTs for the type checker to understand why
return dangerouslyReplaceObjectChild(firstStep.key, _newChild2, tree);
return dangerouslyReplaceObjectChild(firstStep.key, _newChild, tree);
}

@@ -278,2 +266,7 @@ throw new Error("unreachable");

return (0, _tree.strictZipWith)(f, left, right);
}
// Mapping doesn't change the shape
function mapShapedTree(f, tree) {
return (0, _tree.mapTree)(f, tree);
}

@@ -27,2 +27,6 @@ "use strict";

var _Field = require("../Field");
var _Field2 = _interopRequireDefault(_Field);
var _tools = require("./tools");

@@ -36,6 +40,2 @@

var _makeField = require("../makeField");
var _makeField2 = _interopRequireDefault(_makeField);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -75,3 +75,16 @@

var NaughtyRenderingField = (0, _makeField2.default)(NaughtyRenderingInput);
function NaughtyRenderingField(props) {
return React.createElement(
_Field2.default,
props,
function (value, errors, onChange, onBlur) {
return React.createElement(NaughtyRenderingInput, {
value: value,
errors: errors,
onChange: onChange,
onBlur: onBlur
});
}
);
}

@@ -168,4 +181,67 @@ describe("Form", function () {

xit("??? resets the server errors when they change -- this actually belongs lower?", function () {
throw "TODO";
it("updates the server errors", function () {
var onSubmit = jest.fn();
var renderFn = jest.fn(function () {
return null;
});
var renderer = _reactTestRenderer2.default.create(React.createElement(
_Form2.default,
{
initialValue: {
array: []
},
feedbackStrategy: "OnFirstTouch",
onSubmit: onSubmit,
serverErrors: {
"/array": ["Cannot be empty"]
}
},
function (link) {
return React.createElement(
_ObjectField2.default,
{ link: link },
renderFn
);
}
));
expect(renderFn).toHaveBeenCalled();
var links = renderFn.mock.calls[0][0];
var newFormState = (0, _tools.mockFormState)([1]);
links.array.onChange(newFormState);
var anotherRenderFn = jest.fn();
renderer.update(React.createElement(
_Form2.default,
{
initialValue: {
array: []
},
feedbackStrategy: "OnFirstTouch",
onSubmit: onSubmit,
serverErrors: {
"/array": [],
"/array/0": ["inner error"]
}
},
anotherRenderFn
));
expect(anotherRenderFn).toHaveBeenCalled();
var link = anotherRenderFn.mock.calls[0][0];
var _link$formState3 = _slicedToArray(link.formState, 2),
_ = _link$formState3[0],
tree = _link$formState3[1];
// Cross your fingers
var root = tree;
expect(root.data.errors.server).toEqual([]);
var array = root.children.array;
expect(array.data.errors.server).toEqual([]);
var array0 = array.children[0];
expect(array0.data.errors.server).toEqual(["inner error"]);
});

@@ -172,0 +248,0 @@

@@ -11,3 +11,5 @@ "use strict";

exports.leaf = leaf;
exports.pathFromPathString = pathFromPathString;
exports.strictZipWith = strictZipWith;
exports.mapTree = mapTree;

@@ -31,2 +33,28 @@ var _set = require("./utils/set");

function pathFromPathString(pathString) {
if (pathString[0] !== "/") {
throw new Error("Error paths must start with forward-slash");
}
if (pathString === "/") {
return [];
}
return pathString.slice(1).split("/").map(function (keyPart) {
// This might be dangerous, since it means you can't use numbers as object
// keys. This is acceptable for now.
if (!isNaN(keyPart)) {
return {
type: "array",
index: Number.parseInt(keyPart, 10)
};
} else {
return {
type: "object",
key: keyPart
};
}
});
}
function strictZipWith(f, left, right) {

@@ -71,2 +99,28 @@ if (left.type === "object" && right.type === "object") {

throw new Error("Tried to zip two nodes of different type");
}
// A tree is a functor
function mapTree(f, tree) {
if (tree.type === "object") {
return {
type: "object",
data: f(tree.data),
children: Object.keys(tree.children).reduce(function (memo, key) {
return _extends({}, memo, _defineProperty({}, key, mapTree(f, tree.children[key])));
}, {})
};
} else if (tree.type === "array") {
return {
type: "array",
data: f(tree.data),
children: tree.children.map(function (child) {
return mapTree(f, child);
})
};
} else {
return {
type: "leaf",
data: f(tree.data)
};
}
}
{
"name": "formula-one",
"version": "0.2.0",
"version": "0.2.1",
"description": "Strongly-typed React form state management",

@@ -5,0 +5,0 @@ "author": "Zach Gotsch",

@@ -11,7 +11,6 @@ // @flow strict

FieldLink,
ServerErrors,
ClientErrors,
} from "./types";
import {cleanMeta, cleanErrors} from "./types";
import {type FormState, replaceServerErrors} from "./formState";
import {type FormState} from "./formState";
import {

@@ -21,5 +20,7 @@ type ShapedTree,

treeFromValue,
setFromKeysObj,
shapePath,
updateAtPath,
mapShapedTree,
} from "./shapedTree";
import {pathFromPathString} from "./tree";

@@ -41,17 +42,51 @@ export type FormContextPayload = {

function newFormState<T>(
value: T,
serverErrors: null | ShapedTree<T, ServerErrors>
function applyServerErrorsToFormState<T>(
serverErrors: null | {[path: string]: Array<string>},
formState: FormState<T>
): FormState<T> {
const cleanState = [
value,
treeFromValue(value, {
errors: cleanErrors,
meta: cleanMeta,
}),
];
if (serverErrors != null) {
return replaceServerErrors(serverErrors, cleanState);
const [value, oldTree] = formState;
let tree: ShapedTree<T, Extras>;
if (serverErrors !== null) {
// If keys do not appear, no errors
tree = mapShapedTree(
({errors, meta}) => ({
errors: {...errors, server: []},
meta,
}),
oldTree
);
Object.keys(serverErrors).forEach(key => {
const newErrors: Array<string> = serverErrors[key];
const path = shapePath(value, pathFromPathString(key));
if (path != null) {
// TODO(zach): make some helper functions that do this
tree = updateAtPath(
path,
({errors, meta}) => ({
errors: {...errors, server: newErrors},
meta,
}),
tree
);
} else {
console.error(
`Warning: couldn't match error with path ${key} to value ${JSON.stringify(
value
)}`
);
}
});
} else {
tree = mapShapedTree(
({errors, meta}) => ({
errors: {...errors, server: []},
meta,
}),
oldTree
);
}
return cleanState;
return [value, tree];
}

@@ -97,8 +132,8 @@

if (props.serverErrors !== state.oldServerErrors) {
const serverErrorsTree = Form.makeServerErrorTree(
state.formState[0],
props.serverErrors
const newFormState = applyServerErrorsToFormState(
props.serverErrors,
state.formState
);
return {
formState: replaceServerErrors(serverErrorsTree, state.formState),
formState: newFormState,
oldServerErrors: props.serverErrors,

@@ -110,34 +145,15 @@ };

static makeServerErrorTree<T>(
value: T,
errorsObj: null | {[path: string]: Array<string>}
): ShapedTree<T, ServerErrors> {
if (errorsObj != null) {
try {
const freshTree = treeFromValue(value, []);
// TODO(zach): Variance problems $FlowFixMe
return (setFromKeysObj(errorsObj, freshTree): ShapedTree<
T,
ServerErrors
>);
} catch (e) {
console.error("Error applying server errors to value!");
console.error(`\t${e.message}`);
console.error("The server errors will be ignored.");
return treeFromValue(value, "unchecked");
}
} else {
return treeFromValue(value, "unchecked");
}
}
constructor(props: Props<T>) {
super(props);
const serverErrors = Form.makeServerErrorTree(
const freshTree = treeFromValue(props.initialValue, {
errors: cleanErrors,
meta: cleanMeta,
});
const formState = applyServerErrorsToFormState(props.serverErrors, [
props.initialValue,
props.serverErrors
);
freshTree,
]);
this.state = {
formState: newFormState(props.initialValue, serverErrors),
formState,
pristine: true,

@@ -144,0 +160,0 @@ submitted: false,

@@ -234,8 +234,4 @@ // @flow strict

formState[0],
shapedZipWith(
(es, oldExtras) => replaceServerErrorsExtra(es, oldExtras),
serverErrors,
formState[1]
),
shapedZipWith(replaceServerErrorsExtra, serverErrors, formState[1]),
];
}
// @flow strict
import {type Tree, type Path, leaf, strictZipWith} from "./tree";
import {type Tree, type Path, leaf, strictZipWith, mapTree} from "./tree";
import invariant from "./utils/invariant";

@@ -12,4 +12,6 @@ import {replaceAt} from "./utils/array";

// A path on a shaped tree
// TODO(zach): Make this opaque
// eslint-disable-next-line no-unused-vars
export type ShapedPath<Shape> = Path;
export /* opaque */ type ShapedPath<Shape> = Path;
export const rootPath: <T>() => ShapedPath<T> = () => [];

@@ -50,68 +52,32 @@ // Take shape from value, data from nodeData

function setKey<T, Node>(
key: string,
value: Node,
tree: ShapedTree<T, Node>
): ShapedTree<T, Node> {
if (key[0] !== "/") {
throw new Error("Error paths must start with forward-slash");
export function shapePath<T>(data: T, path: Path): null | ShapedPath<T> {
if (path.length === 0) {
return path;
}
return _setKey(key.slice(1), value, tree);
}
function _setKey<T, Node>(
key: string,
value: Node,
tree: ShapedTree<T, Node>
): ShapedTree<T, Node> {
if (key === "") {
return mapRoot(() => value, tree);
const [firstPart, ...restParts] = path;
if (
firstPart.type === "object" &&
Object.hasOwnProperty.call(data, firstPart.key)
) {
// $FlowFixMe: This is safe
const restPath = shapePath(data[firstPart.key], restParts);
if (restPath === null) {
return null;
}
return [firstPart, ...restPath];
} else if (
firstPart.type === "array" &&
Array.isArray(data) &&
firstPart.index < data.length
) {
const restPath = shapePath(data[firstPart.index], restParts);
if (restPath === null) {
return null;
}
return [firstPart, ...restPath];
}
const [firstPart, ...restParts] = key.split("/");
if (tree.type === "leaf") {
throw new Error("Theres more key, but not more Tree to match it against");
}
if (tree.type === "array") {
const index = Number.parseInt(firstPart);
invariant(
index.toString() === firstPart,
"Key indexing into an array is not a number"
);
invariant(index >= 0, "Key indexing into array is negative");
invariant(
index < tree.children.length,
"Key indexing array is outside array bounds"
);
const newChild = _setKey(restParts.join("/"), value, tree.children[index]);
// $FlowFixMe(zach): I think this is safe, might need GADTs for the type checker to understand why
return dangerouslyReplaceArrayChild(index, newChild, tree);
}
if (tree.type === "object") {
invariant(
tree.children.hasOwnProperty(firstPart),
"Key indexing into object does not exist"
);
const newChild = _setKey(
restParts.join("/"),
value,
tree.children[firstPart]
);
// $FlowFixMe(zach): I think this is safe, might need GADTs for the type checker to understand why
return dangerouslyReplaceObjectChild(firstPart, newChild, tree);
}
throw new Error("unreachable");
return null;
}
export function setFromKeysObj<T, Node>(
keysObj: {[path: string]: Node},
tree: ShapedTree<T, Node>
): ShapedTree<T, Node> {
return Object.keys(keysObj).reduce(
(memo: ShapedTree<T, Node>, key: string) => setKey(key, keysObj[key], memo),
tree
);
}

@@ -123,2 +89,3 @@ export function updateAtPath<T, Node>(

): ShapedTree<T, Node> {
// console.log("updateAtPath()", path, tree);
if (path.length === 0) {

@@ -330,1 +297,9 @@ if (tree.type === "object") {

}
// Mapping doesn't change the shape
export function mapShapedTree<T, A, B>(
f: A => B,
tree: ShapedTree<T, A>
): ShapedTree<T, B> {
return mapTree(f, tree);
}

@@ -8,2 +8,3 @@ // @flow

import ArrayField from "../ArrayField";
import Field from "../Field";

@@ -13,3 +14,2 @@ import {expectLink, mockFormState} from "./tools";

import {forgetShape} from "../shapedTree";
import makeField from "../makeField";

@@ -29,3 +29,16 @@ class NaughtyRenderingInput extends React.Component<{|

}
const NaughtyRenderingField = makeField(NaughtyRenderingInput);
function NaughtyRenderingField(props) {
return (
<Field {...props}>
{(value, errors, onChange, onBlur) => (
<NaughtyRenderingInput
value={value}
errors={errors}
onChange={onChange}
onBlur={onBlur}
/>
)}
</Field>
);
}

@@ -111,4 +124,55 @@ describe("Form", () => {

xit("??? resets the server errors when they change -- this actually belongs lower?", () => {
throw "TODO";
it("updates the server errors", () => {
const onSubmit = jest.fn();
const renderFn = jest.fn(() => null);
const renderer = TestRenderer.create(
<Form
initialValue={{
array: [],
}}
feedbackStrategy="OnFirstTouch"
onSubmit={onSubmit}
serverErrors={{
"/array": ["Cannot be empty"],
}}
>
{link => <ObjectField link={link}>{renderFn}</ObjectField>}
</Form>
);
expect(renderFn).toHaveBeenCalled();
const links = renderFn.mock.calls[0][0];
const newFormState = mockFormState([1]);
links.array.onChange(newFormState);
const anotherRenderFn = jest.fn();
renderer.update(
<Form
initialValue={{
array: [],
}}
feedbackStrategy="OnFirstTouch"
onSubmit={onSubmit}
serverErrors={{
"/array": [],
"/array/0": ["inner error"],
}}
>
{anotherRenderFn}
</Form>
);
expect(anotherRenderFn).toHaveBeenCalled();
const link = anotherRenderFn.mock.calls[0][0];
const [_, tree] = link.formState;
// Cross your fingers
const root: any = tree;
expect(root.data.errors.server).toEqual([]);
const array = root.children.array;
expect(array.data.errors.server).toEqual([]);
const array0 = array.children[0];
expect(array0.data.errors.server).toEqual(["inner error"]);
});

@@ -115,0 +179,0 @@

@@ -38,2 +38,31 @@ // @flow strict

export function pathFromPathString(pathString: string): Path {
if (pathString[0] !== "/") {
throw new Error("Error paths must start with forward-slash");
}
if (pathString === "/") {
return [];
}
return pathString
.slice(1)
.split("/")
.map(keyPart => {
// This might be dangerous, since it means you can't use numbers as object
// keys. This is acceptable for now.
if (!isNaN(keyPart)) {
return {
type: "array",
index: Number.parseInt(keyPart, 10),
};
} else {
return {
type: "object",
key: keyPart,
};
}
});
}
export function strictZipWith<A, B, C>(

@@ -89,1 +118,26 @@ f: (A, B) => C,

}
// A tree is a functor
export function mapTree<A, B>(f: A => B, tree: Tree<A>): Tree<B> {
if (tree.type === "object") {
return {
type: "object",
data: f(tree.data),
children: Object.keys(tree.children).reduce(
(memo, key) => ({...memo, [key]: mapTree(f, tree.children[key])}),
{}
),
};
} else if (tree.type === "array") {
return {
type: "array",
data: f(tree.data),
children: tree.children.map(child => mapTree(f, child)),
};
} else {
return {
type: "leaf",
data: f(tree.data),
};
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc