@hookstate/core
Advanced tools
Comparing version 1.8.7 to 1.8.8
@@ -0,1 +1,5 @@ | ||
# 1.8.X | ||
### Preparation for version 2 interface | ||
# 1.7.0 | ||
@@ -2,0 +6,0 @@ |
@@ -129,2 +129,3 @@ import React from 'react'; | ||
function useState(source) { | ||
var sourceIsInitialValue = true; | ||
if (typeof source === 'object' && source !== null) { | ||
@@ -135,6 +136,7 @@ var sl = source[StateMarkerID]; | ||
source = sl; // get underlying StateLink | ||
sourceIsInitialValue = false; | ||
} | ||
} | ||
var statelink = useStateLink(source); | ||
if (useState[DevToolsID]) { | ||
if (sourceIsInitialValue && useState[DevToolsID]) { | ||
statelink.attach(useState[DevToolsID]); | ||
@@ -236,28 +238,37 @@ } | ||
}; | ||
var ErrorId; | ||
(function (ErrorId) { | ||
ErrorId[ErrorId["InitStateToValueFromState"] = 101] = "InitStateToValueFromState"; | ||
ErrorId[ErrorId["SetStateToValueFromState"] = 102] = "SetStateToValueFromState"; | ||
ErrorId[ErrorId["GetStateWhenPromised"] = 103] = "GetStateWhenPromised"; | ||
ErrorId[ErrorId["SetStateWhenPromised"] = 104] = "SetStateWhenPromised"; | ||
ErrorId[ErrorId["SetStateNestedToPromised"] = 105] = "SetStateNestedToPromised"; | ||
ErrorId[ErrorId["SetStateWhenDestroyed"] = 106] = "SetStateWhenDestroyed"; | ||
ErrorId[ErrorId["GetStatePropertyWhenPrimitive"] = 107] = "GetStatePropertyWhenPrimitive"; | ||
ErrorId[ErrorId["ToJson_Value"] = 108] = "ToJson_Value"; | ||
ErrorId[ErrorId["ToJson_State"] = 109] = "ToJson_State"; | ||
ErrorId[ErrorId["GetUnknownPlugin"] = 120] = "GetUnknownPlugin"; | ||
ErrorId[ErrorId["SetProperty_State"] = 201] = "SetProperty_State"; | ||
ErrorId[ErrorId["SetProperty_Value"] = 202] = "SetProperty_Value"; | ||
ErrorId[ErrorId["SetPrototypeOf_State"] = 203] = "SetPrototypeOf_State"; | ||
ErrorId[ErrorId["SetPrototypeOf_Value"] = 204] = "SetPrototypeOf_Value"; | ||
ErrorId[ErrorId["PreventExtensions_State"] = 205] = "PreventExtensions_State"; | ||
ErrorId[ErrorId["PreventExtensions_Value"] = 206] = "PreventExtensions_Value"; | ||
ErrorId[ErrorId["DefineProperty_State"] = 207] = "DefineProperty_State"; | ||
ErrorId[ErrorId["DefineProperty_Value"] = 208] = "DefineProperty_Value"; | ||
ErrorId[ErrorId["DeleteProperty_State"] = 209] = "DeleteProperty_State"; | ||
ErrorId[ErrorId["DeleteProperty_Value"] = 210] = "DeleteProperty_Value"; | ||
ErrorId[ErrorId["Construct_State"] = 211] = "Construct_State"; | ||
ErrorId[ErrorId["Construct_Value"] = 212] = "Construct_Value"; | ||
ErrorId[ErrorId["Apply_State"] = 213] = "Apply_State"; | ||
ErrorId[ErrorId["Apply_Value"] = 214] = "Apply_Value"; | ||
})(ErrorId || (ErrorId = {})); | ||
var StateLinkInvalidUsageError = /** @class */ (function (_super) { | ||
__extends(StateLinkInvalidUsageError, _super); | ||
function StateLinkInvalidUsageError(op, path, hint) { | ||
return _super.call(this, "StateLink is used incorrectly. Attempted '" + op + "' at '/" + path.join('/') + "'" + | ||
(hint ? ". Hint: " + hint : '')) || this; | ||
function StateLinkInvalidUsageError(path, id, details) { | ||
return _super.call(this, "Error: HOOKSTATE-" + id + " [path: /" + path.join('/') + (details ? ", details: " + details : '') + "]. " + | ||
("See https://hookstate.js.org/docs/exceptions#hookstate-" + id)) || this; | ||
} | ||
return StateLinkInvalidUsageError; | ||
}(Error)); | ||
function extractSymbol(s) { | ||
var result = s.toString(); | ||
var symstr = 'Symbol('; | ||
if (result.startsWith(symstr) && result.endsWith(')')) { | ||
result = result.substring(symstr.length, result.length - 1); | ||
} | ||
return result; | ||
} | ||
// TODO replace by StateLinkInvalidUsageError | ||
var PluginUnknownError = /** @class */ (function (_super) { | ||
__extends(PluginUnknownError, _super); | ||
function PluginUnknownError(s) { | ||
return _super.call(this, "Plugin '" + extractSymbol(s) + "' has not been attached to the StateInf or StateLink. " + | ||
"Hint: you might need to register the required plugin using 'with' method. " + | ||
"See https://github.com/avkonst/hookstate#plugins for more details") || this; | ||
} | ||
return PluginUnknownError; | ||
}(Error)); | ||
var DowngradedID = Symbol('Downgraded'); | ||
@@ -345,6 +356,3 @@ var StateMemoID = Symbol('StateMemo'); | ||
if (this._edition < 0) { | ||
// TODO convert to warning | ||
throw new StateLinkInvalidUsageError("set state for the destroyed state", path, 'make sure all asynchronous operations are cancelled (unsubscribed) when the state is destroyed. ' + | ||
'Global state is explicitly destroyed at \'StateInf.destroy()\'. ' + | ||
'Local state is automatically destroyed when a component is unmounted.'); | ||
throw new StateLinkInvalidUsageError(path, ErrorId.SetStateWhenDestroyed); | ||
} | ||
@@ -372,4 +380,3 @@ if (path.length === 0) { | ||
else if (this._promised && !this._promised.resolver) { | ||
// TODO add hint | ||
throw new StateLinkInvalidUsageError("write promised state", path, ''); | ||
throw new StateLinkInvalidUsageError(path, ErrorId.SetStateWhenPromised); | ||
} | ||
@@ -389,5 +396,3 @@ var prevValue = this._value; | ||
if (typeof value === 'object' && Promise.resolve(value) === value) { | ||
throw new StateLinkInvalidUsageError( | ||
// TODO add hint | ||
'set promise for nested property', path, ''); | ||
throw new StateLinkInvalidUsageError(path, ErrorId.SetStateNestedToPromised); | ||
} | ||
@@ -547,3 +552,3 @@ var target = this._value; | ||
Store.prototype.toJSON = function () { | ||
throw new StateLinkInvalidUsageError('toJSON()', RootPath, 'did you mean to use JSON.stringify(state.get()) instead of JSON.stringify(state)?'); | ||
throw new StateLinkInvalidUsageError(RootPath, ErrorId.ToJson_Value); | ||
}; | ||
@@ -647,4 +652,3 @@ return Store; | ||
} | ||
// TODO add hint | ||
throw new StateLinkInvalidUsageError('read promised state', this.path); | ||
throw new StateLinkInvalidUsageError(this.path, ErrorId.GetStateWhenPromised); | ||
} | ||
@@ -708,3 +712,3 @@ return this.valueSource; | ||
if (typeof newValue === 'object' && newValue !== null && newValue[ProxyMarkerID]) { | ||
throw new StateLinkInvalidUsageError("set(state.get() at '/" + newValue[ProxyMarkerID].path.join('/') + "')", this.path, 'did you mean to use state.set(lodash.cloneDeep(value)) instead of state.set(value)?'); | ||
throw new StateLinkInvalidUsageError(this.path, ErrorId.SetStateToValueFromState); | ||
} | ||
@@ -832,3 +836,3 @@ return this.state.set(this.path, newValue, mergeValue); | ||
} | ||
throw new PluginUnknownError(plugin); | ||
throw new StateLinkInvalidUsageError(this.path, ErrorId.GetUnknownPlugin, plugin.toString()); | ||
} | ||
@@ -998,3 +1002,3 @@ }; | ||
} | ||
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state.nested[" + key + "].set(value)' instead of 'state[" + key + "] = value'?"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.SetProperty_Value); | ||
}); | ||
@@ -1043,3 +1047,3 @@ }; | ||
} | ||
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state.nested." + key + ".set(value)' instead of 'state." + key + " = value'?"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.SetProperty_Value); | ||
}); | ||
@@ -1055,3 +1059,3 @@ }; | ||
var onInvalidUsage = function (op) { | ||
throw new StateLinkInvalidUsageError(op, _this.path); | ||
throw new StateLinkInvalidUsageError(_this.path, op); | ||
}; | ||
@@ -1063,3 +1067,3 @@ return new Proxy(objectToWrap, { | ||
setPrototypeOf: function (target, v) { | ||
return onInvalidUsage('setPrototypeOf'); | ||
return onInvalidUsage(ErrorId.SetPrototypeOf_Value); | ||
}, | ||
@@ -1073,3 +1077,3 @@ isExtensible: function (target) { | ||
preventExtensions: function (target) { | ||
return onInvalidUsage('preventExtensions'); | ||
return onInvalidUsage(ErrorId.PreventExtensions_Value); | ||
}, | ||
@@ -1096,9 +1100,9 @@ getOwnPropertyDescriptor: function (target, p) { | ||
set: setter || (function (target, p, value, receiver) { | ||
return onInvalidUsage('set'); | ||
return onInvalidUsage(ErrorId.SetProperty_Value); | ||
}), | ||
deleteProperty: function (target, p) { | ||
return onInvalidUsage('deleteProperty'); | ||
return onInvalidUsage(ErrorId.DeleteProperty_Value); | ||
}, | ||
defineProperty: function (target, p, attributes) { | ||
return onInvalidUsage('defineProperty'); | ||
return onInvalidUsage(ErrorId.DefineProperty_Value); | ||
}, | ||
@@ -1118,6 +1122,6 @@ enumerate: function (target) { | ||
apply: function (target, thisArg, argArray) { | ||
return onInvalidUsage('apply'); | ||
return onInvalidUsage(ErrorId.Apply_Value); | ||
}, | ||
construct: function (target, argArray, newTarget) { | ||
return onInvalidUsage('construct'); | ||
return onInvalidUsage(ErrorId.Construct_Value); | ||
} | ||
@@ -1148,3 +1152,3 @@ }); | ||
if (key === 'toJSON') { | ||
throw new StateLinkInvalidUsageError('toJSON()', _this.path, 'did you mean to use JSON.stringify(state.get()) instead of JSON.stringify(state)?'); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.ToJson_State); | ||
} | ||
@@ -1182,3 +1186,3 @@ var currentValue = _this.getUntracked(true); | ||
_this.get(); // mark used | ||
throw new StateLinkInvalidUsageError('get', _this.path, "target value is not an object to contain properties"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.GetStatePropertyWhenPrimitive); | ||
} | ||
@@ -1199,9 +1203,7 @@ } | ||
return _this.child(index)[self]; | ||
// return (this.nested)![index][self]; | ||
} | ||
return _this.child(key.toString())[self]; | ||
// return (this.nested)![key.toString()][self]; | ||
} | ||
}, function (_, key, value) { | ||
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state." + String(key) + "[self].set(value)' instead of 'state." + String(key) + " = value'?"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.SetProperty_State); | ||
}); | ||
@@ -1280,3 +1282,3 @@ }, | ||
var capturedThis_1 = this; | ||
return [instance || (new PluginUnknownError(p)), | ||
return [instance || (new StateLinkInvalidUsageError(this.path, ErrorId.GetUnknownPlugin, p.toString())), | ||
// TODO need to create an instance until version 2 | ||
@@ -1313,3 +1315,3 @@ // because of the incompatible return types from methods | ||
var onInvalidUsage = function (op) { | ||
throw new StateLinkInvalidUsageError(op, path); | ||
throw new StateLinkInvalidUsageError(path, op); | ||
}; | ||
@@ -1330,3 +1332,3 @@ if (typeof targetBootstrap !== 'object' || targetBootstrap === null) { | ||
setPrototypeOf: function (target, v) { | ||
return onInvalidUsage('setPrototypeOf'); | ||
return onInvalidUsage(ErrorId.SetPrototypeOf_State); | ||
}, | ||
@@ -1340,3 +1342,3 @@ isExtensible: function (target) { | ||
preventExtensions: function (target) { | ||
return onInvalidUsage('preventExtensions'); | ||
return onInvalidUsage(ErrorId.PreventExtensions_State); | ||
}, | ||
@@ -1372,6 +1374,6 @@ getOwnPropertyDescriptor: function (target, p) { | ||
deleteProperty: function (target, p) { | ||
return onInvalidUsage('deleteProperty'); | ||
return onInvalidUsage(ErrorId.DeleteProperty_State); | ||
}, | ||
defineProperty: function (target, p, attributes) { | ||
return onInvalidUsage('defineProperty'); | ||
return onInvalidUsage(ErrorId.DefineProperty_State); | ||
}, | ||
@@ -1399,6 +1401,6 @@ enumerate: function (target) { | ||
apply: function (target, thisArg, argArray) { | ||
return onInvalidUsage('apply'); | ||
return onInvalidUsage(ErrorId.Apply_State); | ||
}, | ||
construct: function (target, argArray, newTarget) { | ||
return onInvalidUsage('construct'); | ||
return onInvalidUsage(ErrorId.Construct_State); | ||
} | ||
@@ -1413,4 +1415,3 @@ }); | ||
if (typeof initialValue === 'object' && initialValue !== null && initialValue[ProxyMarkerID]) { | ||
throw new StateLinkInvalidUsageError("create/useStateLink(state.get() at '/" + initialValue[ProxyMarkerID].path.join('/') + "')", RootPath, 'did you mean to use create/useStateLink(state) OR ' + | ||
'create/useStateLink(lodash.cloneDeep(state.get())) instead of create/useStateLink(state.get())?'); | ||
throw new StateLinkInvalidUsageError(RootPath, ErrorId.InitStateToValueFromState); | ||
} | ||
@@ -1417,0 +1418,0 @@ return new Store(initialValue); |
@@ -135,2 +135,3 @@ 'use strict'; | ||
function useState(source) { | ||
var sourceIsInitialValue = true; | ||
if (typeof source === 'object' && source !== null) { | ||
@@ -141,6 +142,7 @@ var sl = source[StateMarkerID]; | ||
source = sl; // get underlying StateLink | ||
sourceIsInitialValue = false; | ||
} | ||
} | ||
var statelink = useStateLink(source); | ||
if (useState[DevToolsID]) { | ||
if (sourceIsInitialValue && useState[DevToolsID]) { | ||
statelink.attach(useState[DevToolsID]); | ||
@@ -242,28 +244,37 @@ } | ||
}; | ||
var ErrorId; | ||
(function (ErrorId) { | ||
ErrorId[ErrorId["InitStateToValueFromState"] = 101] = "InitStateToValueFromState"; | ||
ErrorId[ErrorId["SetStateToValueFromState"] = 102] = "SetStateToValueFromState"; | ||
ErrorId[ErrorId["GetStateWhenPromised"] = 103] = "GetStateWhenPromised"; | ||
ErrorId[ErrorId["SetStateWhenPromised"] = 104] = "SetStateWhenPromised"; | ||
ErrorId[ErrorId["SetStateNestedToPromised"] = 105] = "SetStateNestedToPromised"; | ||
ErrorId[ErrorId["SetStateWhenDestroyed"] = 106] = "SetStateWhenDestroyed"; | ||
ErrorId[ErrorId["GetStatePropertyWhenPrimitive"] = 107] = "GetStatePropertyWhenPrimitive"; | ||
ErrorId[ErrorId["ToJson_Value"] = 108] = "ToJson_Value"; | ||
ErrorId[ErrorId["ToJson_State"] = 109] = "ToJson_State"; | ||
ErrorId[ErrorId["GetUnknownPlugin"] = 120] = "GetUnknownPlugin"; | ||
ErrorId[ErrorId["SetProperty_State"] = 201] = "SetProperty_State"; | ||
ErrorId[ErrorId["SetProperty_Value"] = 202] = "SetProperty_Value"; | ||
ErrorId[ErrorId["SetPrototypeOf_State"] = 203] = "SetPrototypeOf_State"; | ||
ErrorId[ErrorId["SetPrototypeOf_Value"] = 204] = "SetPrototypeOf_Value"; | ||
ErrorId[ErrorId["PreventExtensions_State"] = 205] = "PreventExtensions_State"; | ||
ErrorId[ErrorId["PreventExtensions_Value"] = 206] = "PreventExtensions_Value"; | ||
ErrorId[ErrorId["DefineProperty_State"] = 207] = "DefineProperty_State"; | ||
ErrorId[ErrorId["DefineProperty_Value"] = 208] = "DefineProperty_Value"; | ||
ErrorId[ErrorId["DeleteProperty_State"] = 209] = "DeleteProperty_State"; | ||
ErrorId[ErrorId["DeleteProperty_Value"] = 210] = "DeleteProperty_Value"; | ||
ErrorId[ErrorId["Construct_State"] = 211] = "Construct_State"; | ||
ErrorId[ErrorId["Construct_Value"] = 212] = "Construct_Value"; | ||
ErrorId[ErrorId["Apply_State"] = 213] = "Apply_State"; | ||
ErrorId[ErrorId["Apply_Value"] = 214] = "Apply_Value"; | ||
})(ErrorId || (ErrorId = {})); | ||
var StateLinkInvalidUsageError = /** @class */ (function (_super) { | ||
__extends(StateLinkInvalidUsageError, _super); | ||
function StateLinkInvalidUsageError(op, path, hint) { | ||
return _super.call(this, "StateLink is used incorrectly. Attempted '" + op + "' at '/" + path.join('/') + "'" + | ||
(hint ? ". Hint: " + hint : '')) || this; | ||
function StateLinkInvalidUsageError(path, id, details) { | ||
return _super.call(this, "Error: HOOKSTATE-" + id + " [path: /" + path.join('/') + (details ? ", details: " + details : '') + "]. " + | ||
("See https://hookstate.js.org/docs/exceptions#hookstate-" + id)) || this; | ||
} | ||
return StateLinkInvalidUsageError; | ||
}(Error)); | ||
function extractSymbol(s) { | ||
var result = s.toString(); | ||
var symstr = 'Symbol('; | ||
if (result.startsWith(symstr) && result.endsWith(')')) { | ||
result = result.substring(symstr.length, result.length - 1); | ||
} | ||
return result; | ||
} | ||
// TODO replace by StateLinkInvalidUsageError | ||
var PluginUnknownError = /** @class */ (function (_super) { | ||
__extends(PluginUnknownError, _super); | ||
function PluginUnknownError(s) { | ||
return _super.call(this, "Plugin '" + extractSymbol(s) + "' has not been attached to the StateInf or StateLink. " + | ||
"Hint: you might need to register the required plugin using 'with' method. " + | ||
"See https://github.com/avkonst/hookstate#plugins for more details") || this; | ||
} | ||
return PluginUnknownError; | ||
}(Error)); | ||
var DowngradedID = Symbol('Downgraded'); | ||
@@ -351,6 +362,3 @@ var StateMemoID = Symbol('StateMemo'); | ||
if (this._edition < 0) { | ||
// TODO convert to warning | ||
throw new StateLinkInvalidUsageError("set state for the destroyed state", path, 'make sure all asynchronous operations are cancelled (unsubscribed) when the state is destroyed. ' + | ||
'Global state is explicitly destroyed at \'StateInf.destroy()\'. ' + | ||
'Local state is automatically destroyed when a component is unmounted.'); | ||
throw new StateLinkInvalidUsageError(path, ErrorId.SetStateWhenDestroyed); | ||
} | ||
@@ -378,4 +386,3 @@ if (path.length === 0) { | ||
else if (this._promised && !this._promised.resolver) { | ||
// TODO add hint | ||
throw new StateLinkInvalidUsageError("write promised state", path, ''); | ||
throw new StateLinkInvalidUsageError(path, ErrorId.SetStateWhenPromised); | ||
} | ||
@@ -395,5 +402,3 @@ var prevValue = this._value; | ||
if (typeof value === 'object' && Promise.resolve(value) === value) { | ||
throw new StateLinkInvalidUsageError( | ||
// TODO add hint | ||
'set promise for nested property', path, ''); | ||
throw new StateLinkInvalidUsageError(path, ErrorId.SetStateNestedToPromised); | ||
} | ||
@@ -553,3 +558,3 @@ var target = this._value; | ||
Store.prototype.toJSON = function () { | ||
throw new StateLinkInvalidUsageError('toJSON()', RootPath, 'did you mean to use JSON.stringify(state.get()) instead of JSON.stringify(state)?'); | ||
throw new StateLinkInvalidUsageError(RootPath, ErrorId.ToJson_Value); | ||
}; | ||
@@ -653,4 +658,3 @@ return Store; | ||
} | ||
// TODO add hint | ||
throw new StateLinkInvalidUsageError('read promised state', this.path); | ||
throw new StateLinkInvalidUsageError(this.path, ErrorId.GetStateWhenPromised); | ||
} | ||
@@ -714,3 +718,3 @@ return this.valueSource; | ||
if (typeof newValue === 'object' && newValue !== null && newValue[ProxyMarkerID]) { | ||
throw new StateLinkInvalidUsageError("set(state.get() at '/" + newValue[ProxyMarkerID].path.join('/') + "')", this.path, 'did you mean to use state.set(lodash.cloneDeep(value)) instead of state.set(value)?'); | ||
throw new StateLinkInvalidUsageError(this.path, ErrorId.SetStateToValueFromState); | ||
} | ||
@@ -838,3 +842,3 @@ return this.state.set(this.path, newValue, mergeValue); | ||
} | ||
throw new PluginUnknownError(plugin); | ||
throw new StateLinkInvalidUsageError(this.path, ErrorId.GetUnknownPlugin, plugin.toString()); | ||
} | ||
@@ -1004,3 +1008,3 @@ }; | ||
} | ||
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state.nested[" + key + "].set(value)' instead of 'state[" + key + "] = value'?"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.SetProperty_Value); | ||
}); | ||
@@ -1049,3 +1053,3 @@ }; | ||
} | ||
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state.nested." + key + ".set(value)' instead of 'state." + key + " = value'?"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.SetProperty_Value); | ||
}); | ||
@@ -1061,3 +1065,3 @@ }; | ||
var onInvalidUsage = function (op) { | ||
throw new StateLinkInvalidUsageError(op, _this.path); | ||
throw new StateLinkInvalidUsageError(_this.path, op); | ||
}; | ||
@@ -1069,3 +1073,3 @@ return new Proxy(objectToWrap, { | ||
setPrototypeOf: function (target, v) { | ||
return onInvalidUsage('setPrototypeOf'); | ||
return onInvalidUsage(ErrorId.SetPrototypeOf_Value); | ||
}, | ||
@@ -1079,3 +1083,3 @@ isExtensible: function (target) { | ||
preventExtensions: function (target) { | ||
return onInvalidUsage('preventExtensions'); | ||
return onInvalidUsage(ErrorId.PreventExtensions_Value); | ||
}, | ||
@@ -1102,9 +1106,9 @@ getOwnPropertyDescriptor: function (target, p) { | ||
set: setter || (function (target, p, value, receiver) { | ||
return onInvalidUsage('set'); | ||
return onInvalidUsage(ErrorId.SetProperty_Value); | ||
}), | ||
deleteProperty: function (target, p) { | ||
return onInvalidUsage('deleteProperty'); | ||
return onInvalidUsage(ErrorId.DeleteProperty_Value); | ||
}, | ||
defineProperty: function (target, p, attributes) { | ||
return onInvalidUsage('defineProperty'); | ||
return onInvalidUsage(ErrorId.DefineProperty_Value); | ||
}, | ||
@@ -1124,6 +1128,6 @@ enumerate: function (target) { | ||
apply: function (target, thisArg, argArray) { | ||
return onInvalidUsage('apply'); | ||
return onInvalidUsage(ErrorId.Apply_Value); | ||
}, | ||
construct: function (target, argArray, newTarget) { | ||
return onInvalidUsage('construct'); | ||
return onInvalidUsage(ErrorId.Construct_Value); | ||
} | ||
@@ -1154,3 +1158,3 @@ }); | ||
if (key === 'toJSON') { | ||
throw new StateLinkInvalidUsageError('toJSON()', _this.path, 'did you mean to use JSON.stringify(state.get()) instead of JSON.stringify(state)?'); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.ToJson_State); | ||
} | ||
@@ -1188,3 +1192,3 @@ var currentValue = _this.getUntracked(true); | ||
_this.get(); // mark used | ||
throw new StateLinkInvalidUsageError('get', _this.path, "target value is not an object to contain properties"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.GetStatePropertyWhenPrimitive); | ||
} | ||
@@ -1205,9 +1209,7 @@ } | ||
return _this.child(index)[self]; | ||
// return (this.nested)![index][self]; | ||
} | ||
return _this.child(key.toString())[self]; | ||
// return (this.nested)![key.toString()][self]; | ||
} | ||
}, function (_, key, value) { | ||
throw new StateLinkInvalidUsageError('set', _this.path, "did you mean to use 'state." + String(key) + "[self].set(value)' instead of 'state." + String(key) + " = value'?"); | ||
throw new StateLinkInvalidUsageError(_this.path, ErrorId.SetProperty_State); | ||
}); | ||
@@ -1286,3 +1288,3 @@ }, | ||
var capturedThis_1 = this; | ||
return [instance || (new PluginUnknownError(p)), | ||
return [instance || (new StateLinkInvalidUsageError(this.path, ErrorId.GetUnknownPlugin, p.toString())), | ||
// TODO need to create an instance until version 2 | ||
@@ -1319,3 +1321,3 @@ // because of the incompatible return types from methods | ||
var onInvalidUsage = function (op) { | ||
throw new StateLinkInvalidUsageError(op, path); | ||
throw new StateLinkInvalidUsageError(path, op); | ||
}; | ||
@@ -1336,3 +1338,3 @@ if (typeof targetBootstrap !== 'object' || targetBootstrap === null) { | ||
setPrototypeOf: function (target, v) { | ||
return onInvalidUsage('setPrototypeOf'); | ||
return onInvalidUsage(ErrorId.SetPrototypeOf_State); | ||
}, | ||
@@ -1346,3 +1348,3 @@ isExtensible: function (target) { | ||
preventExtensions: function (target) { | ||
return onInvalidUsage('preventExtensions'); | ||
return onInvalidUsage(ErrorId.PreventExtensions_State); | ||
}, | ||
@@ -1378,6 +1380,6 @@ getOwnPropertyDescriptor: function (target, p) { | ||
deleteProperty: function (target, p) { | ||
return onInvalidUsage('deleteProperty'); | ||
return onInvalidUsage(ErrorId.DeleteProperty_State); | ||
}, | ||
defineProperty: function (target, p, attributes) { | ||
return onInvalidUsage('defineProperty'); | ||
return onInvalidUsage(ErrorId.DefineProperty_State); | ||
}, | ||
@@ -1405,6 +1407,6 @@ enumerate: function (target) { | ||
apply: function (target, thisArg, argArray) { | ||
return onInvalidUsage('apply'); | ||
return onInvalidUsage(ErrorId.Apply_State); | ||
}, | ||
construct: function (target, argArray, newTarget) { | ||
return onInvalidUsage('construct'); | ||
return onInvalidUsage(ErrorId.Construct_State); | ||
} | ||
@@ -1419,4 +1421,3 @@ }); | ||
if (typeof initialValue === 'object' && initialValue !== null && initialValue[ProxyMarkerID]) { | ||
throw new StateLinkInvalidUsageError("create/useStateLink(state.get() at '/" + initialValue[ProxyMarkerID].path.join('/') + "')", RootPath, 'did you mean to use create/useStateLink(state) OR ' + | ||
'create/useStateLink(lodash.cloneDeep(state.get())) instead of create/useStateLink(state.get())?'); | ||
throw new StateLinkInvalidUsageError(RootPath, ErrorId.InitStateToValueFromState); | ||
} | ||
@@ -1423,0 +1424,0 @@ return new Store(initialValue); |
@@ -516,28 +516,2 @@ import React from 'react'; | ||
* | ||
* For example the following 3 code samples are equivivalent: | ||
* | ||
* ```tsx | ||
* const globalState = createState(''); | ||
* | ||
* const MyComponent = () => { | ||
* const state = useState(globalState); | ||
* return <input value={state[self].value} | ||
* onChange={e => state[self].set(e.target.value)} />; | ||
* } | ||
* | ||
* const MyComponent = () => <StateFragment state={globalState}>{ | ||
* state => <input value={state[self].value} | ||
* onChange={e => state[self].set(e.target.value)}> | ||
* }</StateFragment> | ||
* | ||
* class MyComponent extends React.Component { | ||
* render() { | ||
* return <StateFragment state={globalState}>{ | ||
* state => <input value={state[self].value} | ||
* onChange={e => state[self].set(e.target.value)}> | ||
* }</StateFragment> | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* @typeparam S Type of a value of a state | ||
@@ -544,0 +518,0 @@ */ |
{ | ||
"name": "@hookstate/core", | ||
"version": "1.8.7", | ||
"version": "1.8.8", | ||
"description": "The flexible, fast and extendable state management for React that is based on hooks and state usage tracking.", | ||
@@ -34,3 +34,3 @@ "license": "MIT", | ||
"apply-updates": "ncu -u", | ||
"build:docs": "typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --tsconfig ./tsconfig.typedoc.json --exclude \"dist/**.js\" --gitRevision master --includeDeclarations --excludeExternals --excludeNotExported --categorizeByGroup false --readme none --hideGenerator --mode file && concat-md --decrease-title-levels --dir-name-as-title docs > dist/typedoc.md && rimraf docs && replace '></a>' '/>' dist/typedoc.md && replace 'Ƭ [*][*]([A-Za-z0-9]+)[*][*]: [*](.*)[*]' 'Ƭ **$1**: *`$2`*' dist/typedoc.md && replace 'Ƭ [*][*]State[*][*]: [*](.*)[*]' 'Ƭ **State**: *[StateMixin](#interfacesstatemixinmd) & `S extends object` ? `{ readonly [K in keyof Required<S>]: State<S[K]>` : [StateMethods](#interfacesstatemethodsmd)*' dist/typedoc.md && replace '[(]statemethods.md#\\[self\\][)]' '(#self)' dist/typedoc.md && replace '[(]statemixin.md#\\[self\\][)]' '(#self-1)' dist/typedoc.md && replace '[(]statemixindestroy.md#\\[self\\][)]' '(#self-2)' dist/typedoc.md && replace '# @hookstate/core' '' dist/typedoc.md && replace '<a name=\"readmemd\"/>' '\n---\nid: typedoc-hookstate-core\ntitle: API @hookstate/core\n---' dist/typedoc.md && replace '\n\n(---)' '$1' dist/typedoc.md && mv dist/typedoc.md hookstate.js.org/docs2/docs/typedoc-hookstate-core.md", | ||
"build:docs": "typedoc --plugin typedoc-plugin-markdown --hideBreadcrumbs --tsconfig ./tsconfig.typedoc.json --exclude \"dist/**.js\" --gitRevision master --includeDeclarations --excludeExternals --excludeNotExported --categorizeByGroup false --readme none --hideGenerator --mode file && concat-md --decrease-title-levels --dir-name-as-title docs > dist/typedoc.md && rimraf docs && replace '></a>' '/>' dist/typedoc.md && replace 'Ƭ [*][*]([A-Za-z0-9]+)[*][*]: [*](.*)[*]' 'Ƭ **$1**: *`$2`*' dist/typedoc.md && replace 'Ƭ [*][*]State[*][*]: [*](.*)[*]' 'Ƭ **State**: *[StateMixin](#interfacesstatemixinmd) & `S extends object` ? `{ readonly [K in keyof Required<S>]: State<S[K]>` : [StateMethods](#interfacesstatemethodsmd)*' dist/typedoc.md && replace '[(]statemethods.md#\\[self\\][)]' '(#self)' dist/typedoc.md && replace '[(]statemixin.md#\\[self\\][)]' '(#self-1)' dist/typedoc.md && replace '[(]statemixindestroy.md#\\[self\\][)]' '(#self-2)' dist/typedoc.md && replace '# @hookstate/core' '' dist/typedoc.md && replace '<a name=\"readmemd\"/>' '\n---\nid: typedoc-hookstate-core\ntitle: API @hookstate/core\n---' dist/typedoc.md && replace '\n\n(---)' '$1' dist/typedoc.md && mv dist/typedoc.md docs/index/docs/typedoc-hookstate-core.md", | ||
"prepare": "yarn build" | ||
@@ -37,0 +37,0 @@ }, |
317
README.md
@@ -11,6 +11,6 @@ <h1 align="center"> | ||
<p align="center"> | ||
<a href="#why-hookstate">Why?</a> • | ||
<a href="#quick-start">Demos / Examples</a> • | ||
<a href="#api-documentation">Documentation</a> • | ||
<a href="#plugins">Plugins</a> | ||
<a href="https://hookstate.js.org">Why?</a> • | ||
<a href="https://hookstate.js.org/docs/getting-started">Docs / Samples</a> • | ||
<a href="https://hookstate.js.org/demo-todolist">Demo application</a> • | ||
<a href="https://hookstate.js.org/docs/extensions-overview">Plugins</a> | ||
<a href="./CHANGELOG.md">Changelog</a> | ||
@@ -47,311 +47,30 @@ </p> | ||
Any questions? Just ask by raising a github ticket. | ||
**Any questions? Just ask by raising a github ticket.** | ||
## Why Hookstate | ||
- Concise, pragmatic but flexible API. Very easy to learn. See ["Quick Start"](#quick-start) section. | ||
- Incredible performance based on unique method for tracking of used/rendered and updated state segments. See the performance demos [with huge table state](https://hookstate.js.org/performance-demo-large-table) and [with huge form state](https://hookstate.js.org/performance-demo-large-form). | ||
- First-class Typescript support. Complete type inferrence for any complexity of structures of managed state data. Full intellisense support tested in VS Code. | ||
- [Used in production](#used-by). Code simplification and incredible performance boost are proven in a number of real cases. | ||
- Plugin system enables custom extensions, with several [standard plugins](#plugins) available. | ||
- Tiny footprint: **2.8KB** gziped by create-react-app. No external dependencies, except React. | ||
- Support for older browsers, for example IE11, via [the polyfill plugin](#plugins). | ||
- Runs with [Preact](https://preactjs.com/) without polyfills. | ||
[hookstate.js.org](https://hookstate.js.org) | ||
## Quick start | ||
## Migrating to version 2 | ||
See the code samples below and other demos [running online](https://hookstate.js.org). You will learn how to use Hookstate and what it can do in few minutes. | ||
[hookstate.js.org/docs/migrating-to-v2](https://hookstate.js.org/docs/migrating-to-v2) | ||
For more detailed explanation read the [API documentation](#api-documentation). | ||
## Documentation / Code samples | ||
For the complete example application built with Hookstate, check out [this demo](https://hookstate-example-app.netlify.com/) and it's [source code](https://github.com/avkonst/hookstate-example-app). | ||
[hookstate.js.org/docs/getting-started](https://hookstate.js.org/docs/getting-started) | ||
## Demo application | ||
### Global state | ||
[hookstate.js.org/demo-todolist](https://hookstate.js.org/demo-todolist) | ||
> Create the state: | ||
```tsx | ||
const stateLink = createStateLink(0); | ||
``` | ||
> and use it *within* a React component: | ||
```tsx | ||
export function ExampleComponent() { | ||
const state = useStateLink(stateLink); | ||
return <p>State value: {state.get()} | ||
<button onClick={() => state.set(p => p + 1)}>Increment</button></p> | ||
} | ||
``` | ||
> and *outside* of a React component: | ||
```tsx | ||
setInterval(() => stateLink.set(p => p + 1), 3000) | ||
``` | ||
## Development tools | ||
### Local state | ||
[hookstate.js.org/docs/devtools](https://hookstate.js.org/docs/devtools) | ||
> create local state and use *within* a React component: | ||
```tsx | ||
export function ExampleComponent() { | ||
const state = useStateLink(0); | ||
return <p>State value: {state.get()} | ||
<button onClick={() => state.set(p => p + 1)}>Increment</button></p> | ||
} | ||
``` | ||
## Plugins / Extensions | ||
## Used By | ||
[hookstate.js.org/docs/extensions-overview](https://hookstate.js.org/docs/extensions-overview) | ||
Known applications, which use Hookstate in production: | ||
## API reference | ||
- [MoonPiano](https://moonpiano.praisethemoon.org) - read [the success story](https://praisethemoon.org/hookstate-how-one-small-react-library-saved-moonpiano/) of moving from Redux to Hookstate. | ||
- [Nextfinal](https://nextfinal.com/) | ||
> Submit pull request to include yours. | ||
## Installation | ||
```bash | ||
npm install --save @hookstate/core | ||
# OR | ||
yarn add @hookstate/core | ||
``` | ||
## Browser support | ||
It supports all recent browsers and works where React works. If you need to polyfill, for example for IE11, you need to make sure the following is supported by the target environment: | ||
- ES5, `Map` and `Set` (All are available long time ago, including IE11) | ||
- `Symbol` (You likely already have got one from the [`react-app-polyfill`](https://www.npmjs.com/package/react-app-polyfill). If you do not import [`react-app-polyfill`](https://www.npmjs.com/package/react-app-polyfill), you can get the standalone [`es6-symbol`](https://www.npmjs.com/package/es6-symbol)) | ||
- `Number.isInteger` (Polyfill is available from [`core-js/features/number/is-integer`](https://www.npmjs.com/package/core-js)) | ||
- `Proxy` (Sufficient for the library polyfill is available from [`@hookstate/proxy-polyfill`](#plugins)) | ||
## API Documentation | ||
### `createStateLink` | ||
This function creates a reference to a **global** state. The first argument of generic type `S` is the initial value to assign to the state. The return type is [`StateInf<R>`](#stateinf). By default, generic `R` type is a [`StateLink<S>`](#statelink). You can wrap the state reference by your custom state access interface using the second [`transform` argument](#transform-argument). In this case `R` type is the result type of the `transform` function. | ||
For example ([see it running](https://hookstate.js.org/global-complex-from-documentation)): | ||
```tsx | ||
interface Task { name: string; priority?: number } | ||
const initialValue: Task[] = [{ name: 'First Task' }]; | ||
const stateLink = createStateLink(initialValue); | ||
``` | ||
### `StateInf` | ||
The type of an object returned by `createStateLink`. The `StateInf` variable has got the following methods and properties: | ||
- `with(...)` - attaches various [plugins](#plugins) to extend the functionality of the state | ||
- `wrap(...)` - transforms state access interface similarly to [`transform` argument](#transform-argument) for the `createStateLink` | ||
- `destroy()` - destroys the state, so the resources (if any allocated by custom plugins) can be released, and the state can be closed. | ||
- `access()` - this function opens access to the state. It **can** be used outside of a React component to read and update the state object. For example ([see it running](https://hookstate.js.org/global-complex-from-documentation)): | ||
```tsx | ||
setTimeout(() => stateLink.access() | ||
.set(tasks => tasks.concat([{ name: 'Second task by timeout', priority: 1 }])) | ||
, 5000) // adds new task 5 seconds after website load | ||
``` | ||
### `useStateLink` | ||
This function opens access to the state. It **must** be used within a functional React component. The first argument should be one of the following: | ||
- **global state**: a result of the [`createStateLink`](#createstatelink) function. For example ([see it running](https://hookstate.js.org/global-complex-from-documentation)): | ||
```tsx | ||
export const ExampleComponent = () => { | ||
const state = useStateLink(stateLink); | ||
return <button onClick={() => state.set(tasks => tasks.concat([{ name: 'Untitled' }]))} > | ||
Add task | ||
</button> | ||
} | ||
``` | ||
- **local state**: initial variable to assign to the local (per component) state. It similar to the original `React.useState`, but the result [`StateLink`](#statelink) variable has got more features. For example ([see it running](https://hookstate.js.org/local-complex-from-documentation)): | ||
```tsx | ||
export const ExampleComponent = () => { | ||
const state = useStateLink([{ name: 'First Task' }]); | ||
return <button onClick={() => state.set(tasks => tasks.concat([{ name: 'Untitled' }]))}> | ||
Add task | ||
</button> | ||
} | ||
``` | ||
- **scoped state**: a result of the [`useStateLink`](#usestatelink) function, called by a parent component either for a **global state**, for **local state** or it's parent **scoped state**. This is discussed in more details below. For example ([see it running for global state](https://hookstate.js.org/global-complex-from-documentation) or for [local state](https://hookstate.js.org/local-complex-from-documentation)): | ||
```tsx | ||
const TaskViewer = (props: { taskState: StateLink<Task> }) => { | ||
const taskState = useStateLink(props.taskState); | ||
return <p>Task state: {JSON.stringify(taskState.get())}</p> | ||
} | ||
``` | ||
The `useStateLink` forces a component to rerender everytime when any segment/part of the state data is changed **AND only if** this segement was used by the component. | ||
A segment/part of the state is considered as **not used** by a parent's state link, if it is only used by a **scoped state** link. This gives great rendering performance of nested components for large data sets. It is demonstrated in [this example for global state](https://hookstate.js.org/global-complex-from-documentation), [this example for local state](https://hookstate.js.org/local-complex-from-documentation), [this performance demo with large table state](https://hookstate.js.org/performance-demo-large-table) | ||
and [this performance demo with large form state](https://hookstate.js.org/performance-demo-large-form). | ||
The **global state** can be consumed by: | ||
- multiple components as demonstrated in [this example](https://hookstate.js.org/global-multiple-consumers) | ||
- or by a 'parent' component passing `nested` links to it's children as demonstarted in [this example](https://hookstate.js.org/global-multiple-consumers-from-root) or [this example](https://hookstate.js.org/global-complex-from-documentation) | ||
- or any combination of the above two options | ||
The result of `useStateLink` is of type [`StateLink`](#statelink). | ||
The result state link inherits all the plugins attached to the provided state link (**global state** mode) or to the parent component state link (**scoped state** mode). | ||
You can attach more [plugins](#plugins) using `with` method of the state link. | ||
You can also wrap the [state link](#statelink) by your custom state access interface using the second [`transform` argument](#transform-argument). | ||
You can also use the state (**global**, **local** or **scoped**) via `StateFragment` React component. It is particularly useful for creating **scoped state** links, as demonstrated in [this](https://hookstate.js.org/global-multiple-consumers-statefragment) and [this](https://hookstate.js.org/plugin-initial-statefragment) examples. | ||
### `StateLink` | ||
The `StateLink` variable has got the following methods and properties: | ||
- `get()` or `value` - returns the instance of data in the state | ||
- `set(...)` or `set((prevState) => ...)` - function which allows to mutate the state value. If `path === []`, it is similar to the `setState` variable returned by `React.useState` hook. If `path !== []`, it sets only the segment of the state value, pointed out by the path. The `set` function will not accept partial updates. It can be done by combining `set` with `nested`. There is the `Mutate` [plugin](#plugins), which adds helpful methods to mutate arrays and objects. | ||
- `merge(...)` or `merge((prevState) => ...)` - similarly to `set` method updates the state. If target current state value is an object, it does partial update for the object. If state value is an array and the `merge` argument is an array too, it concatenates the current value with `merge` value and sets it to the state. If state value is an array and the `merge` argument is an object, it does partial update for the current array value. If target current state value is a string, it concatenates the current state value string with the `merge` argument converted to string and sets the result to the state. | ||
- `path` 'Javascript' object 'path' to an element relative to the root object in the state. For example: | ||
```tsx | ||
const state = useStateLink([{ name: 'First Task' }]) | ||
state.path IS [] | ||
state.nested[0].path IS [0] | ||
state.nested[0].nested.name.path IS [0, 'name'] | ||
``` | ||
- `nested` 'converts' a `StateLink` of an object to an object of nested `StateLink`s OR a `StateLink` of an array to an array of nested `StateLink` elements. | ||
This allows to 'walk' the tree and access/mutate nested compex data in very convenient way. The result of `nested` for primitive values is `undefined`. The typescript support for `nested` will handle correctly any complexy of the state structure. The result of `Object.keys(state.nested)` is the same as `Object.keys(state.get())`. However, nested state links object will have ANY property defined (although not every will pass Typescript compiler check). It is very convenient to create 'editor-like' components for properties, which can be undefined. For example: | ||
```tsx | ||
const PriorityEditor = (props: { priorityState: StateLink<number | undefined> }) => { | ||
return <p>Current priority: {priorityState.get() === undefined ? 'unknown' : priority.get()} | ||
<button onClick={() => priorityState.set(prevPriority => | ||
(prevPriority || 0) + 1 // here the value might be not defined, but we can set it! | ||
)}> | ||
Increase Priority</button> | ||
</p> | ||
} | ||
const ExampleComponent = () => { | ||
const taskState: StateLink<{ name: string, priority?: number }> = | ||
useStateLink({ name: 'Task name is defined but priority is not' }); | ||
return <PriorityEditor priorityState={ | ||
taskState.nested.priority // it will be always defined, but it's value might be not defined | ||
} /> | ||
} | ||
``` | ||
### `Transform` argument | ||
`createStateLink`, `useStateLink` and `StateLink.wrap()` functions accept the last argument, which allows to wrap the state link by custom state access interface. The transform argument is a callback which receives the original [state link](#statelink) variable and should return any custom state access instance. | ||
Examples for all possible combinations: | ||
- **global state**, `createStateLink` with transform: | ||
```tsx | ||
const stateLink = createStateLink(initialValue, s => ({ | ||
addTask = (t: Task) => s.set(tasks => tasks.concat([t])) | ||
})); | ||
export const getTaskStore = () => stateLink.access() | ||
export const useTaskStore = () => useStateLink(stateLink) | ||
getTaskStore().addTask({ name: 'Untitled' }) | ||
export const ExampleComponent = () => { | ||
const state = useTasksStore(); | ||
return <button onClick={() => state.addTask({ name: 'Untitled' })}> | ||
Add task | ||
</button> | ||
} | ||
``` | ||
- **global state**, `StateLink` with transform: | ||
```tsx | ||
const stateLink = createStateLink(initialValue); | ||
const transform = (s: StateLink<Task[]>) => ({ | ||
addTask = (t: Task) => s.set(tasks => tasks.concat([t])) | ||
}) | ||
stateLink.wrap(transform).addTask({ name: 'Untitled' }) | ||
export const ExampleComponent = () => { | ||
const state = useStateLink(stateLink.wrap(transform)); | ||
return <button onClick={() => state.addTask({ name: 'Untitled' })}> | ||
Add task | ||
</button> | ||
} | ||
``` | ||
- **local state**: `useStateLink` with transform: | ||
```tsx | ||
export const ExampleComponent = () => { | ||
const state = useStateLink([{ name: 'First Task' }], s => ({ | ||
addTask = (t: Task) => s.set(tasks => tasks.concat([t])) | ||
})); | ||
return <button onClick={() => state.addTask({ name: 'Untitled' })}> | ||
Add task | ||
</button> | ||
} | ||
``` | ||
- **scoped state**: `useStateLink` with transform: | ||
```tsx | ||
const TaskViewer = (props: { taskState: StateLink<Task> }) => { | ||
const taskState = useStateLink(props.taskState, s =>({ | ||
getName = () => s.nested.name.get(), | ||
setName = (n: string) => s.nested.name.set(n) | ||
})); | ||
return <input value={state.getName()} onChange={e => state.setName(e.target.value)} /> | ||
} | ||
``` | ||
### `Transform` argument with `StateMemo` | ||
You can apply the transform argument to reduce the the state value down to an aggregated value. It works for local, global and scoped states. For example: | ||
```tsx | ||
const TotalHighestPriorityTasksComponent = (props: { tasksState: StateLink<Task[]> }) => { | ||
const totalTasksWithZeroPriority = useStateLink(props.tasksState, s => { | ||
return s.get().filter(t => t.priority === undefined || t.priority === 0).length; | ||
}) | ||
return <p>Total zero priority tasks: {totalTasksWithZeroPriority}</p> | ||
} | ||
``` | ||
The above will rerender when any task changes a priority or when tasks are added or removed. However, because there is no point to rerender this component when it's aggregated result in the transformation is not changed, we can optimize it: | ||
```tsx | ||
import { StateLink, StateMemo, useStateLink } from '@hookstate/core'; | ||
const TotalHighestPriorityTasksComponent = (props: { tasksState: StateLink<Task[]> }) => { | ||
const totalTasksWithZeroPriority = useStateLink(props.tasksState, StateMemo(s => { | ||
return s.get().filter(t => t.priority === undefined || t.priority === 0).length; | ||
})) | ||
return <p>Total zero priority tasks: {totalTasksWithZeroPriority}</p> | ||
} | ||
``` | ||
The above will rerender only when the result of the aggregation is changed. This allows to achieve advanced optimizations for rendering of various aggregated views. | ||
`StateMemo` usage is demonstarted in [this](https://hookstate.js.org/plugin-initial), [this](https://hookstate.js.org/plugin-initial-statefragment) and [this](https://hookstate.js.org/plugin-touched) examples. | ||
`StateMemo` can be invoked with the second argument, which is equality operator used to compare the new and the previous results of the `transform` callback. By default, tripple equality (===) is used. If new and previous are equal, Hookstate will skip rerendering the component on state chage. | ||
The second argument of the `transform` callback is defined and equals to the result of the last transform call, when the `transform` is called by Hookstate to check if the component should rerender. If the core `StateMemo` plugin is used with default equality operator and the result of the transform is the same as the last result, Hookstate will skip rerendering the component. | ||
## Plugins | ||
> Please, submit pull request if you would like yours plugin included in the list. | ||
Plugin | Description | Example | Package | Version | ||
-|-|-|-|- | ||
Labelled | Allows to assign unique human readable label to a state. | | `@hookstate/labelled` | [![npm version](https://img.shields.io/npm/v/@hookstate/labelled.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/labelled) | ||
Initial | Enables access to an initial value of a [`StateLink`](#statelink) and allows to check if the current value of the [`StateLink`](#statelink) is modified (compares with the initial value). Helps with tracking of *modified* form field(s). | [Demo](https://hookstate.js.org/plugin-initial) | `@hookstate/initial` | [![npm version](https://img.shields.io/npm/v/@hookstate/initial.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/initial) | ||
Touched | Helps with tracking of *touched* form field(s). | [Demo](https://hookstate.js.org/plugin-touched) | `@hookstate/touched` | [![npm version](https://img.shields.io/npm/v/@hookstate/touched.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/touched) | ||
Validation | Enables validation and error / warning messages for a state. Usefull for validation of form fields and form states. | [Demo](https://hookstate.js.org/plugin-validation) | `@hookstate/validation` | [![npm version](https://img.shields.io/npm/v/@hookstate/validation.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/validation) | ||
Persistence | Enables persistence of managed states to browser's local storage. | [Demo](https://hookstate.js.org/plugin-persistence) | `@hookstate/persistence` | [![npm version](https://img.shields.io/npm/v/@hookstate/persistence.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/persistence) | ||
Logger | Logs state updates and current value of a [`StateLink`](#statelink) to the development console. | [Demo](https://hookstate.js.org/plugin-logger) | `@hookstate/logger` | [![npm version](https://img.shields.io/npm/v/@hookstate/logger.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/logger) | ||
DevTools | Development tools for Hookstate. Install [Chrome Redux browser's extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en) and [activate the plugin in your app](https://github.com/avkonst/hookstate/tree/master/plugins/DevTools#how-to-use) | [Demo](https://hookstate.js.org/demo-todolist) | `@hookstate/devtools` | [![npm version](https://img.shields.io/npm/v/@hookstate/devtools.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/devtools) | ||
Untracked | Enables access to `StateLink`'s `get` and `set` methods which do not track usage or state update. It means these operations do not influence rendering at all. Applicable in specific usecases. You should understand what you are doing when you use it. | [Demo](https://hookstate.js.org/plugin-untracked) | `@hookstate/untracked` | [![npm version](https://img.shields.io/npm/v/@hookstate/untracked.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/untracked) | ||
Downgraded | Turns off optimizations for a StateLink by stopping tracking of it's value usage and assuming the entire state is *used* if StateLink's value is accessed at least once. | | `@hookstate/core` | [![npm version](https://img.shields.io/npm/v/@hookstate/core.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/core) | ||
Proxy Polyfill | Makes the Hookstate working in older browsers, for example IE11. All features are supported with two known differences in polyfilled behaviour: 1) `StateLink.nested[key]` will return `undefined` if `StateLink.get()[key]` is also `undefined` property. 2) `StateLink.get()[key] = 'some new value'` will not throw but will mutate the object in the state without notifying any of rendered components or plugins. | [Demo](https://github.com/avkonst/hookstate/tree/master/experimental/ie11) | `@hookstate/proxy-polyfill` | [![npm version](https://img.shields.io/npm/v/@hookstate/proxy-polyfill.svg?maxAge=300&label=version&colorB=007ec6)](https://www.npmjs.com/package/@hookstate/proxy-polyfill) | ||
[hookstate.js.org/docs/typedoc-hookstate-core](https://hookstate.js.org/docs/typedoc-hookstate-core) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
420540
3745
75