Its possible use List.fold_left to returns a Js.Obj? - ffi

I'm trying to do this in ReasonML without success.
The problem is that I don't know the object keys.
const items = {
foo: () => 'ok',
bar: () => 'ok2'
};
const result = Object.keys(items).reduce((acc, key) => ({
...acc, [key]: items[key]()
}), {});
console.log(result);

It is possible, but I don't see why List.fold_left should be a requirement. Js.Dict.map is much more appropriate:
let items = Js.Dict.fromList([
("foo", () => "ok"),
("bar", () => "ok2")
]);
let result = items |> Js.Dict.map([#bs] f => f());

Related

Asserting set of arrays. But 1st set of array only compare the first one

Hi i have this code and i want to assert the emailDataVal to equal to obfuscateEmailText but the emailDataVal only comparing the first result in it's array. It goes like this
Expected result:
emailDataVal = [email1, email2, email3, email4]
obfuscateEmailText = [email1, email2, email3, email4]
Actual result:
emailDataVal = [email1, email1, email1, email1]
obfuscateEmailText = [email1, email2, email3, email4]
it('test', () => {
cy.visit('/landing');
cy.get('.mailto-obfuscated-email').each((emailVal) => {
const emailDataVal = emailVal.data('value')
cy.log(emailDataVal)
cy.get('.amailto-obfuscated-email').each((emailContent) => {
const emailContentText = emailContent.text()
const obfuscateEmailText = emailContentText
.replaceAll('.', '(dotted)')
.replaceAll('#', '(at)')
cy.log(obfuscateEmailText)
// ASSERTION
expect(emailDataVal).to.deep.equal(obfuscateEmailText)
});
});
});
});
You can store the values of all the first iterations in an array. Then for the second iteration compare the values from he array, something like this:
it('test', () => {
cy.visit('/landing')
var emailDataVal = []
cy.get('.mailto-obfuscated-email')
.each((emailVal) => {
emailDataVal.push(emailVal.data('value'))
})
.then(() => {
cy.get('.amailto-obfuscated-email').each((emailContent, index) => {
const emailContentText = emailContent.text()
const obfuscateEmailText = emailContentText
.replaceAll('.', '(dotted)')
.replaceAll('#', '(at)')
// ASSERTION
expect(emailDataVal[index]).to.equal(obfuscateEmailText)
})
})
})
It's easier to use .map() than to use .each() for this:
const obfusticate = ($el) => {
return $el.text()
.replaceAll('.', '(dotted)')
.replaceAll('#', '(at)')
}
cy.get('.mailto-obfuscated-email')
.then($els => Cypress.$.map($els, ($el) => $el.data('value')) )
.then(emailDataVals => {
cy.get('.amailto-obfuscated-email')
.then($els => Cypress.$.map($els, ($el) => obfusticate($el)) )
.then(obfusticated => {
expect(emailDataVals).to.deep.equal(obfusticated)
})
})

Delete task crud in react

I manage to delete the tasks (you can see it in the console.log) but I don't know how to render the result. I really appreciate your help. Link CodeSanbox: https://codesandbox.io/s/trello-task-forked-2xsh8?file=/src/App.js
const addItem = (e) => {
e.preventDefault();
const item = { id: uuidv4(), content: text };
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items, item]
}
});
setText("");
};
const deleteItem = id => {
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
const arrFiltered = column.items.filter(item => item.id !== id)
console.log('arrFiltered', arrFiltered)
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items]
}
});
}
Here is a straight forward solution. You did it everything correctly but you missed something. Just update your delete function to the following
const deleteItem = (id) => {
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items.filter((item) => item.id !== id)]
}
});
};
Your mistake is that you're filtering the array upon deletion. But you're not updating the main item. So I solved it by adding the array filter in to the main item and removing your filter. Just like this.
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items.filter((item) => item.id !== id)]
}
});

Functions in a jest test only work when launched alone, but not at the same time

I have a custom hook that updates a state. The state is made with immer thanks to useImmer().
I have written the tests with Jest & "testing-library" - which allows to test hooks -.
All the functions work when launched alone. But when I launch them all in the same time, only the first one succeed. How so?
Here is the hook: (simplified for the sake of clarity):
export default function useSettingsModaleEditor(draftPage) {
const [settings, setSettings] = useImmer(draftPage);
const enablePeriodSelector = (enable: boolean) => {
return setSettings((draftSettings) => {
draftSettings.periodSelector = enable;
});
};
const enableDynamicFilter = (enable: boolean) => {
return setSettings((draftSettings) => {
draftSettings.filters.dynamic = enable;
});
};
const resetState = () => {
return setSettings((draftSettings) => {
draftSettings.filters.dynamic = draftPage.filters.dynamic;
draftSettings.periodSelector = draftPage.periodSelector;
draftSettings.filters.static = draftPage.filters.static;
});
};
return {
settings,
enablePeriodSelector,
enableDynamicFilter,
resetState,
};
}
And the test:
describe("enablePeriodSelector", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches period selector", () => {
act(() => result.current.enablePeriodSelector(true));
expect(result.current.settings.periodSelector).toBeTruthy();
act(() => result.current.enablePeriodSelector(false));
expect(result.current.settings.periodSelector).toBeFalsy();
});
});
describe("enableDynamicFilter", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches dynamic filter selector", () => {
act(() => result.current.enableDynamicFilter(true));
expect(result.current.settings.filters.dynamic).toBeTruthy();
act(() => result.current.enableDynamicFilter(false));
expect(result.current.settings.filters.dynamic).toBeFalsy();
});
});
describe("resetState", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches dynamic filter selector", () => {
act(() => result.current.enableDynamicFilter(true));
act(() => result.current.enablePeriodSelector(true));
act(() => result.current.addShortcut(Facet.Focuses));
act(() => result.current.resetState());
expect(result.current.settings.periodSelector).toBeFalsy();
expect(result.current.settings.filters.dynamic).toBeFalsy();
expect(result.current.settings.filters.static).toEqual([]);
});
});
All functions works in real life. How to fix this? Thanks!
use beforeEach and reset all mocks(functions has stale closure data) or make common logic to test differently and use that logic to test specific cases.
The answer was: useHook is called before "it". It must be called below.

Multiple state changes in event listener, how to NOT batch the DOM updates?

I'm building a component to test the performance of different algorithms. The algorithms return the ms they took to run and this is want I want to display. The "fastAlgorithm" takes about half a second, and the "slowAlgorithm" takes around 5 seconds.
My problem is that the UI is not re-rendered with the result until both algorithms have finished. I would like to display the result for the fast algorithm as soon as it finishes, and the slow algorithm when that one finishes.
I've read about how React batches updates before re-rendering, but is there someway to change this behavior? Or is there a better way to organize my component/s to achieve what I want?
I'm using react 16.13.1
Here is my component:
import { useState } from 'react'
import { fastAlgorithm, slowAlgorithm } from '../utils/algorithms'
const PerformanceTest = () => {
const [slowResult, setSlowResult] = useState(false)
const [fastResult, setFastResult] = useState(false)
const testPerformance = async () => {
fastAlgorithm().then(result => {
setFastResult(result)
})
slowAlgorithm().then(result => {
setSlowResult(result)
})
}
return (
<div>
<button onClick={testPerformance}>Run test!</button>
<div>{fastResult}</div>
<div>{slowResult}</div>
</div>
)
}
export default PerformanceTest
I read somewhere that ReactDOM.flushSync() would trigger the re-rendering on each state change, but it did not make any difference. This is what I tried:
const testPerformance = async () => {
ReactDOM.flushSync(() =>
fastAlgorithm().then(result => {
setFastResult(result)
})
)
ReactDOM.flushSync(() =>
slowAlgorithm().then(result => {
setSlowResult(result)
})
)
}
And also this:
const testPerformance = async () => {
fastAlgorithm().then(result => {
ReactDOM.flushSync(() =>
setFastResult(result)
)
})
slowAlgorithm().then(result => {
ReactDOM.flushSync(() =>
setSlowResult(result)
)
})
}
I also tried restructuring the algorithms so they didn't use Promises and tried this, with no luck:
const testPerformance = () => {
setFastResult(fastAlgorithm())
setSlowResult(slowAlgorithm())
}
Edit
As Sujoy Saha suggested in a comment below, I replaced my algorithms with simple ones using setTimeout(), and everything works as expected. "Fast" is displayed first and then two seconds later "Slow" is displayed.
However, if I do something like the code below it doesn't work. Both "Fast" and "Slow" shows up when the slower function finishes... Does anyone know exactly when/how the batch rendering in React happens, and how to avoid it?
export const slowAlgorithm = () => {
return new Promise((resolve, reject) => {
const array = []
for(let i = 0; i < 9000; i++) {
for(let y = 0; y < 9000; y++) {
array.push(y);
}
}
resolve('slow')
})
}
Your initial PerfomanceTest component is correct. The component will re-render for the each state change. I think issue is in your algorithm. Please let us know how did you returned promise there.
Follow below code snippet for your reference.
export const fastAlgorithm = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fast')
}, 1000)
})
}
export const slowAlgorithm = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('slow')
}, 3000)
})
}
Are you running your algorithms synchronously on the main thread? If so, that's probably what's blocking React from re-rendering. You may need to move them to worker threads.
The below is loosely based on this answer, minus all the compatibility stuff (assuming you don't need IE support):
// `args` must contain all dependencies for the function.
const asyncify = (fn) => {
return (...args) => {
const workerStr =
`const fn = ${fn.toString()}
self.onmessage = ({ data: args }) => {
self.postMessage(fn(...args))
}`
const blob = new Blob([workerStr], { type: 'application/javascript' })
const worker = new Worker(URL.createObjectURL(blob))
let abort = () => {}
const promise = new Promise((resolve, reject) => {
worker.onmessage = (result) => {
resolve(result.data)
worker.terminate()
}
worker.onerror = (err) => {
reject(err)
worker.terminate()
}
// In case we need it for cleanup later.
// Provide either a default value to resolve to
// or an Error object to throw
abort = (value) => {
if (value instanceof Error) reject(value)
else resolve(value)
worker.terminate()
}
})
worker.postMessage(args)
return Object.assign(promise, { abort })
}
}
const multiplySlowly = (x, y) => {
const start = Date.now()
const arr = [...new Array(x)].fill([...new Array(y)])
return {
x,
y,
result: arr.flat().length,
timeElapsed: Date.now() - start,
}
}
const multiplySlowlyAsync = asyncify(multiplySlowly)
// rendering not blocked - just pretend this is React
const render = (x) => document.write(`<pre>${JSON.stringify(x, null, 4)}</pre>`)
multiplySlowlyAsync(999, 9999).then(render)
multiplySlowlyAsync(15, 25).then(render)
Note that fn is effectively being evaled in the context of the worker thread here, so you need to make sure the code is trusted. Presumably it is, given that you're already happy to run it on the main thread.
For completeness, here's a TypeScript version:
type AbortFn<T> = (value: T | Error) => void
export type AbortablePromise<T> = Promise<T> & {
abort: AbortFn<T>
}
// `args` must contain all dependencies for the function.
export const asyncify = <T extends (...args: any[]) => any>(fn: T) => {
return (...args: Parameters<T>) => {
const workerStr =
`const fn = ${fn.toString()}
self.onmessage = ({ data: args }) => {
self.postMessage(fn(...args))
}`
const blob = new Blob([workerStr], { type: 'application/javascript' })
const worker = new Worker(URL.createObjectURL(blob))
let abort = (() => {}) as AbortFn<ReturnType<T>>
const promise = new Promise<ReturnType<T>>((resolve, reject) => {
worker.onmessage = (result) => {
resolve(result.data)
worker.terminate()
}
worker.onerror = (err) => {
reject(err)
worker.terminate()
}
// In case we need it for cleanup later.
// Provide either a default value to resolve to
// or an Error object to throw
abort = (value: ReturnType<T> | Error) => {
if (value instanceof Error) reject(value)
else resolve(value)
worker.terminate()
}
})
worker.postMessage(args)
return Object.assign(promise, { abort }) as AbortablePromise<
ReturnType<T>
>
}
}

Can't display elements of array React

I can see my array in state, but I don't know why elements of array doesn't display on the app interface.
const [members, setMembers] = useState([])
useEffect( () => {
getMembers();
}, [props.event])
const getMembers = () => {
let new_members = [];
console.log(props.event)
props.event && props.event.uczestnicy.map(member => {
member.get().then(doc => {
let new_member;
new_member = {
...doc.data(),
id: doc.id
}
new_members.push(new_member)
})
setMembers(new_members)
})
console.log(new_members)
console.log(members)
}
[...]
{members && members.map(member => {
console.log('mem',member)
return(
<div key={member.id}>
{member.nick}
</div>
)
})}
So I can see this array in Components using React Developer Tools, but even console.log doesn't see it in the moment of performing.
And console.log(new_members) and console.log(members) result :
Your member values are fetch asynchronously, so its ideal if you set state only after all the values are resolved. For this you can use a Promise.all
const getMembers = async () => {
let new_members = [];
console.log(props.event)
if(props.event) {
const val = await Promise.all(props.event.uczestnicy.map(member => {
return member.get().then(doc => {
let new_member;
new_member = {
...doc.data(),
id: doc.id
}
return new_member
})
});
setMembers(values);
console.log(values);
}
}

Resources