Why a set function in dependency array of useEffect()? - react-hooks

Understand dependency lists out all as soon as any of them is changed will run whatever in the useEffect. Mui-data-grid pagination demo, line 75, dependency has setRowCountState which is a function:
const [rowCountState, setRowCountState] = React.useState(rowCount || 0);
React.useEffect(() => {
setRowCountState((prevRowCountState) =>
rowCount !== undefined ? rowCount : prevRowCountState,
);
}, [rowCount, setRowCountState]);
If a reference execution is a trigger, it will then create an infinite loop which needs outCallBack. But it doesn't have one. So what a function in dependency array serves?
Update The suggested answer changes the function implementation which is not the case here. I'd like to point out that Not everyone has the same intellectual level, while answer A understood by person A, it may require answer B for person B. Vice versa. Appreciate it if this is not closed.

Related

Cypress: Get JQuery value without needing `then` or `each`

I'm hoping someone can help, but I've posted this as a Cypress discussion as well, although it might just be my understanding that's wrong
I need to get the Cypress.Chainable<JQuery<HTMLElement>> of the cell of a table using the column header and another cell's value in the same row.
Here's a working example JQuery TS Fiddle: https://jsfiddle.net/6w1r7ha9/
My current implementation looks as follows:
static findCellByRowTextColumnHeaderText(
rowText: string,
columnName: string,
) {
const row = cy.get(`tr:contains(${rowText})`);
const column = cy.get(`th:contains(${columnName})`)
const columnIndex = ???
return row.find(`td:eq(${columnIndex})`)
}
This function is required because I want to write DRY code to find cells easily for content verification, clicking elements inside of it etc.
The only example I've seen is this https://stackoverflow.com/a/70686525/1321908, but the following doesn't work:
const columns = cy.get('th')
let columnIndex = -1
columns.each((el, index) => {
if (el.text().includes(columnName) {
columnIndex = index
}
cy.log('columnIndex', columnIndex) // Outputs 2 as expected
})
cy.log('finalColumnIndex', columnIndex) // Outputs -1
My current thinking is something like:
const columnIndex: number = column.then((el) => el.index())
This however returns a Chainable<number> How to turn it into number, I have no idea. I'm using this answer to guide my thinking in this regard.
Using .then() in a Cypress test is almost mandatory to avoid flaky tests.
To avoid problems with test code getting ahead of web page updating, Cypress uses Chainable to retry the DOM query until success, or time out.
But the Chainable interface isn't a promise, so you can't await it. You can only then() it.
It would be nice if you could substitute another keyword like unchain
const column = unchain cy.get(`th:contains(${columnName})`)
but unfortunately Javascript can't be extended with new keywords. You can only add methods like .then() onto objects like Chainable.
Having said that, there are code patterns that allow extracting a Chainable value and using it like a plain Javascript variable.
But they are limited to specific scenarios, for example assigning to a global in a before() and using it in an it().
If you give up the core feature of Cypress, the automatic retry feature, then it's just jQuery exactly as you have in the fiddle (but using Cypress.$() instead of $()).
But even Mikhail's thenify relys on the structure of the test when you add a small amount of asynchronicity
Example app
<foo>abc</foo>
<script>
setTimeout(() => {
const foo = document.querySelector('foo')
foo.innerText = 'def'
}, 1000)
</script>
Test
let a = cy.get("foo").thenify()
// expect(a.text()).to.eq('def') // fails
// cy.wrap(a.text()).should('eq', 'def') // fails
cy.wrap(a).should('have.text', 'def') // passes
let b = cy.get("foo") // no thenify
b.should('have.text', 'def') // passes
Based on your working example, you will need to get the headers first, map out the text, then find the index of the column (I've chosen 'Col B'). Afterwards you will find the row containing the other cell value, then get all the cells in row and use .eq() with the column index found earlier.
// get headers, map text, filter to Col B index
cy.get("th")
.then(($headers) => Cypress._.map($headers, "innerText"))
.then(cy.log)
.invoke("indexOf", "Col B")
.then((headerIndex) => {
// find row containing Val A
cy.contains("tbody tr", "Val A")
.find("td")
// get cell containing Val B
.eq(headerIndex)
.should("have.text", "Val B");
});
Here is the example.

How can I use func in a C# EF dbquery statement for sorting?

I have to do multi-part sorts and want to do it dynamically.
I found this question but do not know how to use func in a dbquery statement.
No generic method 'ThenBy' on type 'System.Linq.Queryable'
If I could get the code in the thread to work it would be nirvana.
All the examples I have seen use then within a where statement, but I need to use the function to do sorting.
I have written extensions using IQueryable, including ones for orderby and orderbydescending. The problem is thenby and thenbydescending use iorderedqueryable.
The error I get when using ThenByProperty is
Object of type 'System.Data.Entity.Infrastructure.DbQuery1[ORMModel.v_Brand]' cannot be converted to type 'System.Linq.IOrderedEnumerable1[ORMModel.v_Brand]'.
Do not get such an error when I use a comparable OrderByProperty extension.
what a mess, obviously I do not post often here. Anyway I am stumped and clueless so any tips are very appreciated.
Tried to post code but kept getting format errors so gave up. But help me anyways :)
If you use method syntax, you'll see func quite often, for instance in Where, GroupBy, Join, etc
Every method with some input parameters and one return value can be translated to a Func<...> as follows
MyReturnType DoSomething(ParameterType1 p1, ParameterType2, p2) {...}
Func<ParameterType1, ParameterType2, MyReturnType> myFunc = (x, y) => DoSomething(x, y);
The part Func<ParameterType1, ParameterType2, MyReturnType> means: a function with two input parameters and one return value. The input parameters are of type ParameterType1 and ParameterType2, in this order. The return value is of MyReturnType.
You instantiate an object of Func<ParameterType1, ParameterType2, MyReturnType> using a lambda expression. Before the => you type a declaration for the input parameters, after the => you call the function with these input parameters. If you have more than one input parameter you make them comma separated surrounded by brackets.
For a Where you need a Func<TSource, bool>. So a function that has as input one source element, and as result a bool:
Where(x => x.Name == "John Doe")
For a GroupJoin you need a resultSelector of type Func<TOuter,System.Collections.Generic.IEnumerable<TInner>,TResult> resultSelector
So this is a function with as input one element of the outer sequence, and a sequence of elements of the inner sequence. For example, to query Teachers with their Students:
var result = Teachers.GroupJoin(Students,
teacher => teacher.Id, // from every Teacher take the Id,
student => student.TeacherId, // from every Student take the TeacherId,
(teacher, students) => new
{
Id = teacher.Id,
Name = teacher.Name,
Students = students.Select(student => new
{
Id = student.Id,
Name = student.Name,
})
.ToList(),
});
Here you see several Funcs. TOuter is Teacher, TInner is Student, TKey is int
OuterKeySelector: Func<TOuter, TKey>: teacher => teacher.Id
InnerKeySelector: Func<TInner, TKey>: student => student.TeacherId
ResultSelector: Func<Touter, IEnumerable<TInner>, TResult>
The resultSelector is a function that takes one TOuter (a Teacher), and a sequence of TInner (all students of this Teacher) and creates one object using the input parameters
(teacher, students) => new {... use teacher and students }
When creating the lambda expression it is often helpful if you use plurals to refer to collections (teachers, students) and singulars if you refer one element of a collection (student).
Use the => to start defining the func. You can use input parameters that were defined before the => to define the result after the =>

Filter an observable by an observable

Let's consider the following simplified situation:
We have an Observable apples of type Observable < Apple >
Every Apple object has a method isRotten() which returns an observable of type Observable < Boolean > which is guaranteed to emit at least one boolean value.
I want to filter the apples observable such that the rotten apples don't pass the filter. More precisely, an apple A passes the filter if and only if the first item emitted by A.isRotten() is false. What is the best way to implement this filter?
After some thinking I could come up with this:
apples
.concatMap(apple =>
apple.isRotten()
.first()
.filter(bool => bool)
.map(bool => apple))
Which is written in javascript. ( ... => ... is a function). This works, but I think it is rather lengthy and difficult to understand. Is there a better way to do this kind of thing?
What you've got is fine and, tbh, I can't think of a more concise way of doing it. I'd probably use flatMap rather than concatMap if out-of-order apples aren't an issue.
If readibility is an issue for you, just move the implementation into it's one function (eg. filterObservable that accepts a function that takes a value and returns an IObservable<bool>)
One way to achieve that is like this, sorry I didn't get to adapt this to fruit filtering:
const orders$: Observable<Order[]> = searchOrders(...);
const filteredOrders$ = orders$.pipe(switchMap((orders) => {
// take all orders and determine visibility based on an observable
const visibilityRules = orders.map(o => {
return {
order: o,
visible$: o.isPaidFor$ // this is an observable on an Order object
};
});
const filtered = visibilityRules.map(o => o.visible$.pipe(map(visible => visible ? o.order : undefined )));
return (filtered.length == 0) ? of([]) : combineLatest(filtered).pipe(map(combined => combined.filter(x => x != undefined)));
}));
This filters 'paidFor' orders and emits a new array every time an order becomes paid or unpaid.
Note: If the isPaidFor$ observable can't change between searches then this whole exercise is pointless because there would be no reason to provide a 'live view' like this. This only makes sense if the observable can actually change between search results.
This could be extended to much more complicated rules if needed (such as adding filtering checkboxes) - that's why I created the intermediate visibilityRules array - which strictly speaking is just for readability.
You can do something like this:
var seq = Rx.Observable.from([1, 2, 3, 4, 5, 6])
.filter(x => {
let isRotten = true;
Rx.Observable.just(x % 2 === 0)
.first()
.subscribe(val => isRotten = val);
if (isRotten) {
return x;
}
});
seq.subscribe(x => console.log(x));

group.all() call required for data to populate correctly

So I've encountered a weird issue when dealing with making Groups based on a variable when the crossfilter is using an array, instead of a literal number.
I currently have an output array of a date, then 4 values, that I then map into a composite graph. The problem is that the 4 values can fluctuate depending on the input given to the page. What I mean is that based on what it receives, I can have 3 values, or 10, and there's no way to know in advance. They're placed into an array which is then given to a crossfilter. When in testing, I was accessing using
dimension.group.reduceSum(function(d) { return d[0]; });
Where 0 was changed to whatever I needed. But I've finished testing, for the most part, and began to adapt it into a dynamic system where it can change, but there's always at least the first two. To do this I created an integer that keeps track of what index I'm at, and then increases it after the group has been created. The following code is being used:
var range = crossfilter(results);
var dLen = 0;
var curIndex = 0;
var dateDimension = range.dimension(function(d) { dLen = d.length; return d[curIndex]; });
curIndex++;
var aGroup = dateDimension.group().reduceSum(function(d) { return d[curIndex]; });
curIndex++;
var bGroup = dateDimension.group().reduceSum(function(d) { return d[curIndex]; });
curIndex++;
var otherGroups = [];
for(var h = 0; h < dLen-3; h++) {
otherGroups[h] = dateDimension.group().reduceSum(function(d) { return d[curIndex]; });
curIndex++;
}
var charts = [];
for(var x = 0; x < dLen - 3; x++) {
charts[x] = dc.barChart(dataGraph)
.group(otherGroups[x], "Extra Group " + (x+1))
.hidableStacks(true)
}
charts[charts.length] = dc.lineChart(dataGraph)
.group(aGroup, "Group A")
.hidableStacks(true)
charts[charts.length] = dc.lineChart(dataGraph)
.group(aGroup, "Group B")
.hidableStacks(true)
The issue is this:
The graph gets built empty. I checked the curIndex variable multiple times and it was always correct. I finally decided to instead check the actual group's resulting data using the .all() method.
The weird thing is that AFTER I used .all(), now the data works. Without a .all() call, the graph cannot determine the data and outputs absolutely nothing, however if I call .all() immediately after the group has been created, it populates correctly.
Each Group needs to call .all(), or only the ones that do will work. For example, when I first was debugging, I used .all() only on aGroup, and only aGroup populated into the graph. When I added it to bGroup, then both aGroup and bGroup populated. So in the current build, every group has .all() called directly after it is created.
Technically there's no issue, but I'm really confused on why this is required. I have absolutely no idea what the cause of this is, and I was wondering if there was any insight into it. When I was using literals, there was no issue, it only happens when I'm using a variable to create the groups. I tried to get output later, and when I do I received NaN for all the values. I'm not really sure why .all() is changing values into what they should be especially when it only occurs if I do it immediately after the group has been created.
Below is a screenshot of the graph. The top is when everything has a .all() call after being created, while the bottom is when the Extra Groups (the ones defined in the for loop) do not have the .all() call anymore. The data is just not there at all, I'm not really sure why. Any thoughts would be great.
http://i.stack.imgur.com/0j1ey.jpg
It looks like you may have run into the classic "generating lambdas from loops" JavaScript problem.
You are creating a whole bunch of functions that reference curIndex but unless you call those functions immediately, they will refer to the same instance of curIndex in the global environment. So if you call them after initialization, they will probably all try to use a value which is past the end.
Instead, you might create a function which generates your lambdas, like so:
function accessor(curIndex) {
return function(d) { return d[curIndex]; };
}
And then each time call .reduceSum(accessor(curIndex))
This will cause the value of curIndex to get copied each time you call the accessor function (or you can think of each generated function as having its own environment with its own curIndex).

What do they mean when they say LINQ is composable?

What does it mean and why (if at all) is it important?
It means you can add additional "operators" to a query. It's important because you can do it extremely efficiently.
For example, let's say you have a method that returns a list (enumerable) of employees:
var employees = GetEmployees();
and another method that uses that one to return all managers:
IEnumerable<Employee> GetManagers()
{
return GetEmployees().Where(e => e.IsManager);
}
You can call that function to get managers that are approaching retirement and send them an email like this:
foreach (var manager in GetManagers().Where(m => m.Age >= 65) )
{
SendPreRetirementMessage(manager);
}
Pop quiz: How many times will that iterate over your employees list? The answer is exactly once; the entire operation is still just O(n)!
Also, I don't need to have separate methods for this. I could compose a query with these steps all in one place:
var retiringManagers = GetEmployees();
retiringManagers = retiringManagers.Where(e => e.IsManager);
retiringManagers = retiringManagers.Where(m => m.Age >= 65);
foreach (var manager in retiringMangers)
{
SendPreRetirementMessage();
}
One cool thing about this is that I can change is at run time, such that I can include or not include one part of the composition inside an if block, such that the decision to use a specific filter is made at run time, and everything still comes out nice and pretty.
I think it means that you can daisy chain your queries, like this
var peterJacksonsTotalBoxOffice
= movies.Where(movie => movie.Director == "Peter Jackson")
.Sum(movie => movie.BoxOffice);

Resources