I'm trying to create a dynamic mock for a javascript object.
I have a react component that receives an object with a dynamic structure (meaning i don't know which properties exist on it beforehand) and each property is either a json with any level of nesting or a function that returns a string.
example object:
const originalObj = {
prop1: {
innerProp2: {
moreProps: () => "some string",
differentProp: () => "some string 2",
anotherProp: () => "something else",
},
funcAtLevel2: () => "jlk"
},
funcAtThisLevel: () => "this returns string too"
}
The component renders the strings that it receives from this object and i'm testing other functionality of the component.
in order for the component to render i need the accessors to work, so if my component looks like this:
function MyComp(props: { content: any }) {
const { content } = props;
const moreProps = content.prop1.innerProp2.moreProps(); // moreProps = "some hard code string"
const differentProps = content.prop1.innerProp2.differentProps(); // differentProps = "some hard code string"
const anotherProp = content.prop1.innerProp2.anotherProp(); // anotherProp = "some hard code string"
const funcAtLevel2 = content.prop1.funcAtLevel2(); // funcAtLevel2 = "some hard code string"
const funcAtThisLevel = content.funcAtThisLevel(); // funcAtThisLevel = "some hard code string"
// other code...
return (
<div>
<span>{moreProps}</span>
<span>{differentProps}</span>
{/* more DOM structure here */}
</div>
);
}
I need the accessors to work dynamically.
I managed to get closer with this:
const handler = {
get(target, prop, receiver) {
return new Proxy({ [prop]: () => "some hard code string" }, handler);
},
};
const CompToRender = <MyComp content={new Proxy({}, handler)} />;
However I couldn't manage to return the last accessed property as method. How can I implement it?
EDIT: what i'm looking for is the magical object that i can pass to the rendered component instead of the one that is passed in production which will return some hard coded string so the component won't fail to render (the string value is of none importance).
const magicalObj = // how to define this without defining each property explicitely? the "content" object can be very large (over 1000 keys)
const CompToRender = <MyComp content={magicalObj} />;
function MyComp(props: { content: any }) {
const { content } = props;
// so moreProps and the variables below will receive some string just so the component will render
// without some accessor the properties will be undefined and it will throw an error
const moreProps = content.prop1.innerProp2.moreProps(); // moreProps = "some hard code string"
const differentProps = content.prop1.innerProp2.differentProps(); // differentProps = "some hard code string"
const anotherProp = content.prop1.innerProp2.anotherProp(); // anotherProp = "some hard code string"
const funcAtLevel2 = content.prop1.funcAtLevel2(); // funcAtLevel2 = "some hard code string"
const funcAtThisLevel = content.funcAtThisLevel(); // funcAtThisLevel = "some hard code string"
// other code...
return (
<div>
<span>{moreProps}</span>
<span>{differentProps}</span>
{/* more DOM structure here */}
</div>
);
}
Your code will work with this handler:
const handler = {
get() {
const f = () => 'value returned from function';
Object.setPrototypeOf(f, new Proxy({}, handler));
return f;
},
};
That is, if you define const content = new Proxy({}, handler);, then content.a.b.c.whatever() will return 'value returned from function'.
The key is returning a function that "extends" a Proxy using setPrototypeOf (I took the idea from invokable)
Related
Is it possible for a JS field decorator to change its value?
A simplified use case would be something like this:
const addItem = (newValue) => {
return function (target) {
target.value.push(newValue);
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]
Using the following experimental decorators:
'#babel/plugin-proposal-decorators',
{
version: '2018-09',
decoratorsBeforeExport: true,
},
End goal is to make a decorator to inject tailwind style sheets into a lit elements static styles. Currently using a mixin for this but just doing this for fun and to learn whats possible with decorators.
Update to Barmars comments
When trying to return a value from the inner function, I end up getting an error:
export const addItem = (value) => {
return function (target) {
return [value];
};
};
Uncaught TypeError: An element descriptor's .kind property must be either "method" or "field", but a decorator created an element descriptor with .kind "undefined"
Looking at the documentation, the variables getting passed to each of these functions doesn't seem to match either.
function logged(value, { kind, name }) {
if (kind === "field") {
return function (initialValue) {
console.log(`initializing ${name} with value ${initialValue}`);
return initialValue;
};
}
}
When running that example, the 2nd parameter to logged() is undefined. "initialValue" also is an object, not the value:
Object { kind: "field", key: "styles", placement: "own", descriptor: {…}, initializer: value(), … }
Nicolo Ribaudo was able to help me over on Babel's discussions. The correct way to do this is to use the initializer function:
const addItem = (newValue) => {
return function (target) {
const { initializer } = target;
target.initializer = function () {
return [
...initializer.call(this),
newValue,
];
};
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]
I'm looking through a CSV file with name/stock symbol objects. When the user inputs a company name and clicks submit I'm searching through the file to locate the company record and extract the symbol.
Within a handleSubmit React function I've got a reduce that keeps failing the try/catch and throwing an error.
Data array looks like this:
[{"Symbol": "A", "Name": "Agilent Technologies Inc. Common Stock"}, {"Symbol": "AA", "Name": "Alcoa Corporation Common Stock"},etc.]
My functional component:
export function ParseAndSearch() {
const [newAnswer, setNewAnswer] = useState({});
// parse csv file here - output is 'data' array like above
const handleSubmit => {
// user input = userCompany (a string)
try {
let output = [];
let tempAnswer =
data.reduce((output, company) => {
if (company.Name.toLowerCase().includes(userCompany.toLowerCase())) {
output.push(company);
};
let dummyAnswer = [...output];
console.log('filtered list is: ' + JSON.stringify(dummyAnswer));
return output;
}, []);
setNewAnswer(tempAnswer);
} catch (err) {
console.log('error block');
}
}
return (
<div>
Company Name: {newAnswer['Symbol']}
</div>
}
The
How is my reduce failing? I'm getting results on "dummyAnswer" with each iteration but then it seems to fail when I get to the end of the reduce or when I try to "setNewAnswer."
I've had success with find (instead of reduce), but that does not help me as I need to return more than one answer for some searches.
If you need to return more than one answer then you'll probably need to change the newAnswer state to an array and use the filter function:
const [newAnswer, setNewAnswer] = useState([]);
// parse csv file here - output is 'data' array like above
const handleSubmit = () => {
// user input = userCompany (a string)
const tempAnswer = data.filter((company) =>
company.Name?.toLowerCase().includes(userCompany.toLowerCase())
);
setNewAnswer(tempAnswer);
};
return (
<div>
{/* Display multiple answers */}
{newAnswer.map((answer, index) => {
return <div key={index}>Company Name: {answer.Symbol}</div>;
})}
</div>
)
I would like to write a proxy object to automatically print errors when calling some property in original object which is not found.
const proxyObjectFn = () => {
const _obj = Object.assign({}, originalObject);
const get = (key) => {
const value = _obj[key];
if (value === undefined) {
console.error(`${key} not found`);
}
return value;
};
return {
get,
};
};
const proxyObject = proxyObjectFn();
export default proxyObject;
// caller
proxyObject.get('someProperty')
This works, but is there any elegant way so that I can call through proxyObject.someProperty instead of proxyObject.get('someProperty')?
Update
Let me make it more specific. Actually I am writing a translation object.
Original object may be from json, like { "HELLO_KEY": "Hello World" }. I am to call like { label: _t.SOME_I18N_KEY } in UI display code, assuming _t is the proxy object above. I can print the warning to tell me there is missing translation.
You can use the Proxy object:
const handler = {
get: (obj, prop) => {
if(!obj.hasOwnProperty(prop)) console.error(`${prop} not found`);
return obj[prop];
}
};
const _t = new Proxy({ "HELLO_KEY": "Hello World" }, handler);
console.log(_t.HELLO_KEY);
console.log(_t.SOME_NONEXISTENT_KEY);
Came across this piece of code recently:
const fetchMock = async (name, type="success") => {
const response = await fetch(someUrl);
let mock = await response.json();
mock = mock[type];
mock.json = () => mock.data;
return mock;
}
I have trouble understanding why, on line 5, we use a function to assign the mock.data to mock.json. Can't we simply write mock.json = mock.data? How is it different?
P. S. I don't have any idea about the kind of data it is receiving.
Your question has nothing to do with the async JSON fetching stuff. In the end, mock is just an object which has a data property. It also needs a json property that yields the data property.
So a reduced code sample would look like this:
const mock = {
data: {
"someKey": "someValue"
}
};
mock.json = () => mock.data;
Assume that mock.json is set exactly once, and mock.data is mutated or updated. Then, mock.json = mock.data will only work correctly if mock.data is an object that stays the same reference.
const mock = {
data: {
"someKey": "someValue"
}
};
mock.json = mock.data;
console.log(mock.json); // { "someKey": "someValue" }, as expected
// …
mock.data.someKey = "someOtherValue"
console.log(mock.json); // { "someKey": "someOtherValue" }, as expected
// Either of these reassignments will break up the connection between `mock.json` and `mock.data`:
mock.data = {"newKey": "something"};
mock.data = "Something else";
console.log(mock.json); // { "someKey": "someOtherValue" }, no longer updated
This doesn’t matter for mock.json = () => mock.data, though, as the function just returns the current value of mock.data.
Given an object that may be null and may have the following properties:
{
templateId: "template1",
templates: {
template1: "hello"
}
}
How would you get the template in a failsafe way? (templateId might not be defined, or the template it reffers might be undefined)
I use ramda and was trying to adapt my naive version of the code to use something like a Maybe adt to avoid explicit null/undefined checks.
I'm failing to come up with an elegant and clean solution.
naive ramda version:
const getTemplate = obj => {
const templateId = obj && prop("templateId", obj);
const template = templateId != null && path(["template", templateId], obj);
return template;
}
this does work but I would like to avoid null checks, as my code has a lot more going on and it would be really nice to become cleaner
Edit
I get from severall answers that the best is to ensure clean data first.
That's not allways possible though.
I also came up with this, which I do like.
const Empty=Symbol("Empty");
const p = R.propOr(Empty);
const getTemplate = R.converge(p,[p("templateId"), p("templates")]);
Would like to get feedback regarding how clean and how readable it is (and if there are edge cases that would wreck it)
As others have told you, ugly data precludes beautiful code. Clean up your nulls or represent them as option types.
That said, ES6 does allow you to handle this with some heavy destructuring assignment
const EmptyTemplate =
Symbol ()
const getTemplate = ({ templateId, templates: { [templateId]: x = EmptyTemplate } }) =>
x
console.log
( getTemplate ({ templateId: "a", templates: { a: "hello" }}) // "hello"
, getTemplate ({ templateId: "b", templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({ templates: { a: "hello" }}) // EmptyTemplate
)
You can continue to make getTemplate even more defensive. For example, below we accept calling our function with an empty object, and even no input at all
const EmptyTemplate =
Symbol ()
const getTemplate =
( { templateId
, templates: { [templateId]: x = EmptyTemplate } = {}
}
= {}
) =>
x
console.log
( getTemplate ({ templateId: "a", templates: { a: "hello" }}) // "hello"
, getTemplate ({ templateId: "b", templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({ templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({}) // EmptyTemplate
, getTemplate () // EmptyTemplate
)
Above, we start to experience a little pain. This signal is important not to ignore as it warns us we're doing something wrong. If you have to support that many null checks, it indicates you need to tighten down the code in other areas of your program. It'd be unwise to copy/paste any one of these answers verbatim and miss the lesson everyone is trying to teach you.
Here is an ADT approach in vanilla Javascript:
// type constructor
const Type = name => {
const Type = tag => Dcons => {
const t = new Tcons();
t[`run${name}`] = Dcons;
t.tag = tag;
return t;
};
const Tcons = Function(`return function ${name}() {}`) ();
return Type;
};
const Maybe = Type("Maybe");
// data constructor
const Just = x =>
Maybe("Just") (cases => cases.Just(x));
const Nothing =
Maybe("Nothing") (cases => cases.Nothing);
// typeclass functions
Maybe.fromNullable = x =>
x === null
? Nothing
: Just(x);
Maybe.map = f => tx =>
tx.runMaybe({Just: x => Just(f(x)), Nothing});
Maybe.chain = ft => tx =>
tx.runMaybe({Just: x => ft(x), Nothing});
Maybe.compk = ft => gt => x =>
gt(x).runMaybe({Just: y => ft(y), Nothing});
// property access
const prop =
k => o => o[k];
const propSafe = k => o =>
k in o
? Just(o[k])
: Nothing;
// auxiliary function
const id = x => x;
// test data
// case 1
const o = {
templateId: "template1",
templates: {
template1: "hello"
}
};
// case 2
const p = {
templateId: null
};
// case 3
const q = {};
// case 4
const r = null; // ignored
// define the action (a function with a side effect)
const getTemplate = o => {
const tx = Maybe.compk(Maybe.fromNullable)
(propSafe("templateId"))
(o);
return Maybe.map(x => prop(x) (o.templates)) (tx);
};
/* run the effect,
that is what it means to compose functions that may not produce a value */
console.log("case 1:",
getTemplate(o).runMaybe({Just: id, Nothing: "N/A"})
);
console.log("case 2:",
getTemplate(p).runMaybe({Just: id, Nothing: "N/A"})
);
console.log("case 3:",
getTemplate(q).runMaybe({Just: id, Nothing: "N/A"})
);
As you can see I use functions to encode ADTs, since Javascript doesn't support them on the language level. This encoding is called Church/Scott encoding. Scott encoding is immutable by design and once you are familiar with it, its handling is a piece of cake.
Both Just values and Nothing are of type Maybe and include a tag property on which you can do pattern matching.
[EDIT]
Since Scott (not the encoding guy from just now) and the OP asked for a more detailed reply I extended my code. I still ignore the case where the object itself is null. You have to take care of this in a preceding step.
You may think this is overengineered - with certainty for this contrived example. But when complexity grows, these functional style can ease the pain. Please also note that we can handle all kinds of effects with this approach, not just null checks.
I am currently building an FRP solution, for instance, which is essentially based on the same building blocks. This repetition of patterns is one of the traits of the functional paradigm I would not want to do without anymore.
You can use R.pathOr. Whenever any part of the path isn't available, a default value is returned. For example:
const EmptyTemplate = Symbol();
const getTemplateOrDefault = obj => R.pathOr(
EmptyTemplate,
[ "templates", obj.templateId ],
obj
);
A collection of tests can be found in this snippet. The example shows that pathOr handles all (?) "wrong" cases quite well:
const tests = [
{ templateId: "a", templates: { "a": 1 } }, // 1
{ templates: { "a": 1 } }, // "empty"
{ templateId: "b", templates: { "a": 1 } }, // "empty"
{ templateId: null, templates: { "a": 1 } }, // "empty"
{ templateId: "a", templates: { } }, // "empty"
{ templateId: "a" } // "empty"
];
Edit: To support null or undefined inputs, you could compose the method with a quick defaultTo:
const templateGetter = compose(
obj => pathOr("empty", [ "templates", obj.templateId ], obj),
defaultTo({})
);
Try this,
const input = {
templateId: "template1",
templates: {
template1: "hello"
}
};
const getTemplate = (obj) => {
const template = obj.templates[obj.templateId] || "any default value / simply remove this or part";
//use below one if you think templates might be undefined too,
//const template = obj.templates && obj.templates[obj.templateId] || "default value"
return template;
}
console.log(getTemplate(input));
You can use a combination of && and || to short-circuit an expression.
Also, use [] (instead of .) with objects to get the value if the key is stored in a variable.
Complete check
const getTemplate = (obj) => {
const template = obj && obj.templateId && obj.templates && obj.templates[obj.templateId] || "default value"
return template;
}