I learn javascript (yes, I know there are type- and coffeescript.). In an application which requires a function func(a, b) = () => {"a": a, "b": ...b }, I wish to be able to NOT spread b in case it is not spreadable. Is there some property flag that allows this kind of check? I know Python has mutable and non-mutable types, but I have never seen such isMutable(arg) as well. :-/
Application: Jest matcher toHaveBeenNthCalledWith accepts arguments (nthCall, arg1, arg2, ....) such that nthCall is compulsory and ...args are variadic, and allows the function below:
export const expectToHaveBeenNthCalledWith = (functionHandler, expectation) => {
expect(functionHandler).toHaveBeenNthCalledWith(
expectation.callIndex,
...expectation.args
);
};
The unit-test of such function is somewhat given below. It works, fine! But... I wish to to able NOT to embrace the arguments on an array. :-/
describe("expectTo", () => {
...
it(
'assert Nth call with argument',
() => {
const functionCaller = (functionCall, arg_1, arg_2) => {
functionCall(arg_1);
functionCall(arg_2);
}
const string_1 = "ackbar";
const string_2 = "Boba Fett";
const expectation_1 = {"callIndex": 1, "args": [string_1]};
const expectation_2 = {"callIndex": 2, "args": [string_2]};
const f = jest.fn();
functionCaller(f, string_1, string_2);
expectToHaveBeenNthCalledWith(f, expectation_1);
expectToHaveBeenNthCalledWith(f, expectation_2);
}
);
...
})
Related
I found here a script. It works fine. But now, I want to use a Variable instead of single values.
Here the original script:
const customData = {
"func":"bri",
"oid":"ID",
"onVal":1,
"offVal":0,
"...":"..."
}
const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});
const Light.bri = getSubset(customData, "oid", "onVal", "offVal");
Result (OK):
bri: {
offVal: 0,
oid: "objekt-ID",
onVal: 1
},
Now I want to do define the keys in a variable, ideally as a object. But this do not work.
const params = {bri: "oid, onVal, offVal"};
const Light.bri = getSubset(customData, params.bri);
Result (NOK):
bri: {
oid, onVal, offVal: undefined
},
description: "Deckenspots"
}
what changes do I have to make?
Define the bri property as an array of strings. That way you can use the spread syntax (...) to pass the strings as individual arguments.
const params = {bri: ["oid", "onVal", "offVal"]}; // bri is now an array.
const Light.bri = getSubset(customData, ...params.bri); // Notice the ... before params.bri
useEffect(() => {
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map(param => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(param => document.getElementById(param[0][0]).value);
let result = runAlgo(...params); //this runs the emscripten stuff
results.push(...(JSON.parse(result)));
setGlobalState('dataOutput', results);
}
let newMethodsToRun = methodsToRun.slice(1);
if (methodsToRun.length>0) {
setGlobalState('methodsToRun', newMethodsToRun);
}
} , [dataOutput, methodsToRun]);
Hello, I am working on a ReactJS app that uses Webassembly with Emscripten.
I need to run a series of algorithms from Emscripten in my Js and I need to show the results as they come, not altogether at the end. Something like this:
Run first algo
Show results on screen form first algo
Run second algo AFTER the results from the first algo are rendedred on the screen (so that user can keep checking them)
Show results on screen form second algo.....
and so on
I already tried a loop that updates a global state on each iteration with timeouts etc, but no luck, so I tried this approach using useEffect, I tried various things but the results keep coming altogether and not one by one. Any clue?
I tried to mock your example HERE:
import React, { useState, useEffect } from 'react';
import './style.css';
export default function App() {
const [methodsToRun, setMethodsToRun] = useState([method1, method2]);
const [globalState, setGlobalState] = useState([]);
useEffect(() => {
if (methodsToRun.length <= 0) return;
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = runAlgo(...params); //this runs the emscripten stuff
results.push(...JSON.parse(result));
setGlobalState((global) => [...global, results]);
}
setMethodsToRun((methods) => methods.slice(1));
}, [methodsToRun]);
console.log('METHODS TO RUN', methodsToRun);
console.log('GLOBAL RESULT', globalState);
return (
<div>
<div> Global state 1: {globalState[0] && globalState[0][0]}</div>
<div> Global state 2: {globalState[1] && globalState[1][0]}</div>
<div id="1">DIV 1 </div>
<div id="4">DIV 2</div>
<div id="7">DIV 3</div>
</div>
);
}
const method1 = [
'1param0',
[
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
],
];
const method2 = [
'2param0',
[
['11', '223', '3'],
['44', '55', '66'],
['77', '88', '99'],
],
];
//mock wasm
window.wasm = {
cwrap:
(...args) =>
() =>
JSON.stringify(args),
};
This example assumes your wasm methods are synchronous, your DOM will update and rerender with a new globalState after each method is executed, the cycle is ensured by the fact you are setting a new methods array sliced by one in the state, that will trigger a rerender and a new execution of the useEffect since it has the methodsToRun array in the deps array. Infinite render loop is prevented by the check if (methodsToRun.length <= 0) return; .
The drawback of this approach though is that if your wasms methods are synchronous and heavy, your UI will be frozen until it returns, and that's a terrible user experience for the end user. I'm not sure what kind of task you are trying to perform there, if it can be made async and awaited ( the logic would be the same ) or if you have to move that on a service worker.
So from what you've explained here, I think the issue can be resolved in 2 ways:
1 is with synchronous requests/process.
Assuming you have 2 functions (AlgoX and AlgoY) you want to run one after the other, you can return the expected response. e.g.
return AlgoX();
return AlgoY();
The return keyword will block the running process from continuing until you have result.
Another alternative I will suggest will be to use Self-Invoking-Expression:
useEffect(() => {
(
async function AlgoHandler(){
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map(param => param[0][2]);
let runAlgo = await window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(param => document.getElementById(param[0][0]).value);
let result = await runAlgo(...params); //this runs the emscripten stuff
results.push(...(JSON.parse(result)));
setGlobalState('dataOutput', results);
}
let newMethodsToRun = methodsToRun.slice(1);
if (methodsToRun.length>0) {
setGlobalState('methodsToRun', newMethodsToRun);
}
}
)();
} , [dataOutput, methodsToRun]);
Notice where I used async and await in the second solution. Let me know if its helpful.
I'm not sure if this answers your question, but I wrote some example code that may help you fulfill your needs. It will need to be tweaked to fit your use case though, as this should be considered pseudo code:
const initialState = {
count: 0,
results: [],
};
function reducer(state, action) {
switch (action.type) {
case 'addResult':
return {
...state,
count: state.count + 1,
results: [...state.results, action.result],
};
default:
return state;
}
}
function Example({ methodsToRun }) {
const [{ count, results }, dispatch] = useReducer(reducer, initialState);
const next = useCallback(async() => {
if(methodsToRun.length > count) {
const result = await methodsToRun[count]();
dispatch({ type: 'addResult', result });
}
}, [count, methodsToRun]);
useEffect(() => {
next();
}, [next]);
return (
<div>
<p>Results:</p>
<ol>
{results.map(result => (
<li key={result/* choose a unique key from result here, do not use index */}>{result}</li>
))}
</ol>
</div>
);
}
So basically, my idea is to create a callback function, which at first run will be the method at index 0 in your array of methods. When the result arrives, it will update state with the result, as well as update the counter.
The updated count will cause useCallback to change to the next method in the array.
When the callback changes, it will trigger the useEffect, and actually call the function. And so on, until there are no more methods to run.
I have to admit I haven't tried to run this code, as I lack the context. But I think it should work.
At the end of the day, this solution looks like is working for me:
useEffect(() => {
(
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();}, [methodsToRun]);
I want to use a Global variable inside my Cypress test file but its value isn't changing as expected despite adding waits.
const builder = {
stepsArr: []
};
describe('my test', () => {
beforeEach(() => {
cy.intercept('/graphql', (req) => {
req.continue((res) => {
if (res.body.data?.steps) {
builder.stepsArr = res.body.data.steps.steps;
console.log({ stepsArr: builder.stepsArr }); // logs correctly!
}
});
}).as('graphqlRequest');
});
it.only('should check global var', () => {
cy.waitFor('steps');
cy.wrap({ getStepByTitle: pathwayBuilder.getStepByTitle })
.invoke('getStepByTitle',
'some title',
builder.stepsArr // always empty array!
)
.then((stepObj) => {
cy.log(stepObj);
});
});
});
The order of execution is correct but the value of Global variable isn't updating. Its showing empty array when I invoke my function despite retrying for like 100 times. What could be wrong?
cy.waitFor('steps'); is from a command in support/commands.js file
Cypress.Commands.add('waitFor', operationName => {
cy.wait('#graphqlRequest').then(({ request }) => {
if (request.body.operationName !== operationName) {
cy.log('Waiting for:', operationName)
return cy.waitFor(operationName)
}
return null
})
})
The function just logs the parameters on console
exports.pathwayBuilder = {
getStepByTitle: (title, steps) => {
console.log("Search", title);
console.log("Steps", steps); // empty!
}
}
I think the .invoke() args are wrong, see this example invoke() - functions with arguments
const fn = (a, b, c) => {
return a + b + c
}
cy.wrap({ sum: fn })
.invoke('sum', 2, 4, 6)
.should('be.gt', 10) // true
.and('be.lt', 20) // true
...the function takes three arguments and they are passed in comma-separated.
You getStepByTitle accepts two arguments, but you pass in one - an object containing the second argument
so
.invoke('getStepByTitle', {
steps: builder.stepsArr // always empty array!
})
should be
.invoke('getStepByTitle', 'aTitle', builder.stepsArr )
Some more things I found in running the test
getStepByTitle() needs to return something, otherwise stepObj (the result of the .invoke()) will be undefined
the cy.wait() does not succeed (for me) inside the custom command, but it does work if I inline the code of the custom command in the test (for ref Cypress 7.1.0)
the cy.wrap({ getStepByTitle }).invoke(... is evaluating before the cy.wait() finishes. Looks like some sort of optimization, Cypress is invoking when the command is added to the queue.
Substitute
.then(obj => obj.getStepByTitle('some title', builder.stepsArr))`
for
.invoke('getStepByTitle', 'some title', builder.stepsArr )
The following code works fine.
function getProducts(params) {
return params.productQuantities
.map(prod => ({
purchaseOrderLine: null,
haulerCostCode: getOrderLine(params, prod).haulCostCode,
productCostCode: getOrderLine(params, prod).productCostCode,
typeOfWork: getOrderLine(params, prod).productCostCode,
}))
.reduce((accumulator, currentValue) => {
accumulator.push(currentValue);
return accumulator;
}, []);
}
function getOrderLine(params, ticketLine) {
return params.orderDetail.order.orderLineItems
.find(orderLine => orderLine.id == ticketLine.id);
}
My question is how do I avoid multiple calls to getOrderLine()?
use a function body instead of a function expression:
.map(prod => {
const o = getOrderLine(params, prod);
return {
purchaseOrderLine: null,
haulerCostCode: o.haulCostCode,
productCostCode: o.productCostCode,
typeOfWork: o.productCostCode,
}
})
You could use function composition -
const comp = (f, g) =>
x => f(g(x))
const getOrderLine = params => ticketLine =>
params.orderDetail.order.orderLineItems
.find(orderLine => orderLine.id == ticketLine.id)
const makeProduct = orderLine =>
( { purchaseOrderLine: null
, haulerCostCode: orderLine.haulCostCode
, productCostCode: orderLine.productCostCode
, typeOfWork: orderLine.productCostCode
}
)
const getProducts = params =>
params.productQuantities
.map(comp(makeProduct, getOrderLine(params)))
I removed the reduce bit because it's doesn't make any sense. map already creates a new array.
There's other serious problems here though. These functions are digging into object properties sometimes three levels deep. This creates a tight coupling in you code base. See Law of Demeter
Is there an easy way to determine which variable in a useEffect's dependency array triggers a function re-fire?
Simply logging out each variable can be misleading, if a is a function and b is an object they may appear the same when logged but actually be different and causing useEffect fires.
For example:
React.useEffect(() => {
// which variable triggered this re-fire?
console.log('---useEffect---')
}, [a, b, c, d])
My current method has been removing dependency variables one by one until I notice the behavior that causes excessive useEffect calls, but there must be a better way to narrow this down.
I ended up taking a little bit from various answers to make my own hook for this. I wanted the ability to just drop something in place of useEffect for quickly debugging what dependency was triggering useEffect.
const usePrevious = (value, initialValue) => {
const ref = useRef(initialValue);
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
const previousDeps = usePrevious(dependencies, []);
const changedDeps = dependencies.reduce((accum, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index;
return {
...accum,
[keyName]: {
before: previousDeps[index],
after: dependency
}
};
}
return accum;
}, {});
if (Object.keys(changedDeps).length) {
console.log('[use-effect-debugger] ', changedDeps);
}
useEffect(effectHook, dependencies);
};
Below are two examples. For each example, I assume that dep2 changes from 'foo' to 'bar'. Example 1 shows the output without passing dependencyNames and Example 2 shows an example with dependencyNames.
Example 1
Before:
useEffect(() => {
// useEffect code here...
}, [dep1, dep2])
After:
useEffectDebugger(() => {
// useEffect code here...
}, [dep1, dep2])
Console output:
{
1: {
before: 'foo',
after: 'bar'
}
}
The object key '1' represents the index of the dependency that changed. Here, dep2 changed as it is the 2nd item in the dependency, or index 1.
Example 2
Before:
useEffect(() => {
// useEffect code here...
}, [dep1, dep2])
After:
useEffectDebugger(() => {
// useEffect code here...
}, [dep1, dep2], ['dep1', 'dep2'])
Console output:
{
dep2: {
before: 'foo',
after: 'bar'
}
}
#simbathesailor/use-what-changed works like a charm!
Install with npm/yarn and --dev or --no-save
Add import:
import { useWhatChanged } from '#simbathesailor/use-what-changed';
Call it:
// (guarantee useEffect deps are in sync with useWhatChanged)
let deps = [a, b, c, d]
useWhatChanged(deps, 'a, b, c, d');
useEffect(() => {
// your effect
}, deps);
Creates this nice chart in the console:
There are two common culprits:
Some Object being pass in like this:
// Being used like:
export function App() {
return <MyComponent fetchOptions={{
urlThing: '/foo',
headerThing: 'FOO-BAR'
})
}
export const MyComponent = ({fetchOptions}) => {
const [someData, setSomeData] = useState()
useEffect(() => {
window.fetch(fetchOptions).then((data) => {
setSomeData(data)
})
}, [fetchOptions])
return <div>hello {someData.firstName}</div>
}
The fix in the object case, if you can, break-out a static object outside the component render:
const fetchSomeDataOptions = {
urlThing: '/foo',
headerThing: 'FOO-BAR'
}
export function App() {
return <MyComponent fetchOptions={fetchSomeDataOptions} />
}
You can also wrap in useMemo:
export function App() {
return <MyComponent fetchOptions={
useMemo(
() => {
return {
urlThing: '/foo',
headerThing: 'FOO-BAR',
variableThing: hash(someTimestamp)
}
},
[hash, someTimestamp]
)
} />
}
The same concept applies to functions to an extent, except you can end up with stale closures.
UPDATE
After a little real-world use, I so far like the following solution which borrows some aspects of Retsam's solution:
const compareInputs = (inputKeys, oldInputs, newInputs) => {
inputKeys.forEach(key => {
const oldInput = oldInputs[key];
const newInput = newInputs[key];
if (oldInput !== newInput) {
console.log("change detected", key, "old:", oldInput, "new:", newInput);
}
});
};
const useDependenciesDebugger = inputs => {
const oldInputsRef = useRef(inputs);
const inputValuesArray = Object.values(inputs);
const inputKeysArray = Object.keys(inputs);
useMemo(() => {
const oldInputs = oldInputsRef.current;
compareInputs(inputKeysArray, oldInputs, inputs);
oldInputsRef.current = inputs;
}, inputValuesArray); // eslint-disable-line react-hooks/exhaustive-deps
};
This can then be used by copying a dependency array literal and just changing it to be an object literal:
useDependenciesDebugger({ state1, state2 });
This allows the logging to know the names of the variables without any separate parameter for that purpose.
As far as I know, there's no really easy way to do this out of the box, but you could drop in a custom hook that keeps track of its dependencies and logs which one changed:
// Same arguments as useEffect, but with an optional string for logging purposes
const useEffectDebugger = (func, inputs, prefix = "useEffect") => {
// Using a ref to hold the inputs from the previous run (or same run for initial run
const oldInputsRef = useRef(inputs);
useEffect(() => {
// Get the old inputs
const oldInputs = oldInputsRef.current;
// Compare the old inputs to the current inputs
compareInputs(oldInputs, inputs, prefix)
// Save the current inputs
oldInputsRef.current = inputs;
// Execute wrapped effect
func()
}, inputs);
};
The compareInputs bit could look something like this:
const compareInputs = (oldInputs, newInputs, prefix) => {
// Edge-case: different array lengths
if(oldInputs.length !== newInputs.length) {
// Not helpful to compare item by item, so just output the whole array
console.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs)
console.log("Old inputs:", oldInputs)
console.log("New inputs:", newInputs)
return;
}
// Compare individual items
oldInputs.forEach((oldInput, index) => {
const newInput = newInputs[index];
if(oldInput !== newInput) {
console.log(`${prefix} - The input changed in position ${index}`);
console.log("Old value:", oldInput)
console.log("New value:", newInput)
}
})
}
You could use this like this:
useEffectDebugger(() => {
// which variable triggered this re-fire?
console.log('---useEffect---')
}, [a, b, c, d], 'Effect Name')
And you would get output like:
Effect Name - The input changed in position 2
Old value: "Previous value"
New value: "New value"
There’s another stack overflow thread stating you can use useRef to see a previous value.
https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
The React beta docs suggest these steps:
Log your dependency array with console.log:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
Right-click on the arrays from different re-renders in the console and select “Store as a global variable” for both of them. It may be important not to compare two sequential ones if you are in strict mode, I'm not sure.
Compare each of the dependencies:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
This question was answered with several good and working answers, but I just didn't like the DX of any of them.
so I wrote a library which logs the dependencies that changed in the easiest way to use + added a function to log a deep comparison between 2 objects, so you can know what exactly changed inside your object.
I called it: react-what-changed
The readme has all of the examples you need.
The usage is very straight forward:
npm install react-what-changed --save-dev
import { reactWhatChanged as RWC } from 'react-what-changed';
function MyComponent(props) {
useEffect(() => {
someLogic();
}, RWC([somePrimitive, someArray, someObject]));
}
In this package you will also find 2 useful functions for printing deep comparison (diffs only) between objects. for example:
import { reactWhatDiff as RWD } from 'react-what-changed';
function MyComponent(props) {
useEffect(() => {
someLogic();
}, [somePrimitive, someArray, someObject]);
RWD(someArray);
}