How avoid Warning: [antd: Table] `FilteredKeys` should all be controlled or not controlled - reactjs

When I use custom filters and native and design filters in the same problem I get next error
Warning: [antd: Table] `FilteredKeys` should all be controlled or not controlled.
I can use just custom filters on just ant design filters when create table to avoid this warning, but maybe there is a way avoid it different way, or it's just library bug.
I find topic where developer say that this is not a bug and the problem was closed, but
why I get red warning in my project build and development. It's ok?

Since you haven't demonstrated your code I can only show how I resolved the same issue in my case.
Initially, filters for my columns were set like this:
for (let col of columns) {
if (!filter[col.key])
continue;
col.filteredValue = filter[col.key].value || null;
}
The code triggered the same warning as you get. In documentation for the table component is only said that
Defining filteredValue or sortOrder means that it is in the controlled
mode
Whereas if we go down the stack to the file that throws it, we'll find the next function:
var mergedFilterStates = React.useMemo(function () {
var collectedStates = collectFilterStates(mergedColumns, false);
var filteredKeysIsNotControlled = collectedStates.every(function (_ref5) {
var filteredKeys = _ref5.filteredKeys;
return filteredKeys === undefined;
}); // Return if not controlled
if (filteredKeysIsNotControlled) {
return filterStates;
}
var filteredKeysIsAllControlled = collectedStates.every(function (_ref6) {
var filteredKeys = _ref6.filteredKeys;
return filteredKeys !== undefined;
});
(0, _devWarning["default"])(filteredKeysIsNotControlled || filteredKeysIsAllControlled, 'Table', '`FilteredKeys` should all be controlled or not controlled.');
return collectedStates;
}, [mergedColumns, filterStates]);
Simply put, there is a check whether all column filters are either controlled (column.filteredValue !== undefined) or uncontrolled (column.filteredValue === undefined).
So, to get rid of the warning I indicated that all my column filters are controlled (even if they aren't filtered at this particular moment, they aren't undefined either):
for (let col of columns) {
col.filteredValue = (filter[col.key] || {}).value || null;
}
As of why this warning was introduced, we can see here that the change was intended to prevent some edge cases when one could accidentally use filteredValue alongside onFilter and break filters for other columns. That issue could be resolved by changing filteredValue to defaultFilteredValue, but it was decided to add the warning instead. However, in that case satisfying the warned condition wouldn't guarantee the right behaviour: setting up filteredValue for all the columns turns off the warning but don't make the problematic filter workable.
So, answering the second part of your question if this warning makes sense, it depends as in my case all the column filters worked just fine even with this warning displayed.

In short:
Make sure to have filteredValue with a string | undefined where you have filters set for each column

Related

prevent a runtime error when filtering an array that might not exist at certainty point?

I have this reducer state chunk, that is causing a runtime error
(TypeError: (intermediate value)(intermediate value)(intermediate value) is not iterable)
since, at a certain point, the object might not be available in the state, any idea how to prevent the run time from stumbling on the part of chunk that is not available (childGroup) at the current moment? and run it only when certain criteria are met I tried adding ?? [] before the filter but typescript errored out cannot call filter on never. the issue happens when I want to delete the parent group only when there is no child group! , if there is a child group works like charm.
// REMOVE GROUP
case types.DELETE__GROUP:
return {
...state,
parentGroup: {
...state.parentGroup,
[action.payload.parentId!]: state.parentGrou[action.payload.parentId!]?.filter((el) => el.id !== action.payload.id),
},
childGroup: {
...state.childGroup,
[action.payload.parentId!]: [...state.childGroup[action.payload.parentId!]?.filter((el) => el.parentId !== action.payload.id)],
},
};
Conditional indexed access is spelled ?.[] in both TypeScript and JavaScript. So in addition to the ?.filter you need to add ?. to your [action.payload.parentId!] index, making that entire line:
[action.payload.parentId!]:
[
...state.childGroup?.[action.payload.parentId!]?.filter((el) => el.parentId !== action.payload.id)
],
But consider in investing in some utilities around this to make it clearer what the actual business logic is (for example Immer.js)

Showing the new row in react-table on the current page

I have been playing with ReactTable v7 for a while and have encountered the following problem: when the table is sorted and uses paginator sometimes adding (or editing) a row causes it to be outside the current page.
You can see the problem here:
https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/material-UI-kitchen-sink
Sort the table by First Name
Press add
Enter a record with First Name "zzzzz"
The record is added but is currently hidden which confuses users.
Is there a "standard" way to fix the issue? Am I missing something?
In v6 I have done a workaround for it:
React.useEffect(() => {
if (editedElementId && reactTable) {
const { data } = reactTable.props;
if (data && data.length > 0) {
const internal = reactTable.getResolvedState();
let position = -1;
internal.sortedData.forEach((row, i) => {
if (row._original.id === editedElementId) position = i;
});
if (position >= 0) {
const pageNumber = Math.floor(position / pageSize);
setPage(pageNumber);
} else {
alert.info("Element not visible");
}
}
}
}, [editedElementId]);
...
<ReactTable
ref={(r) => {setReactTable(r);}}
...
But maybe there is a bulit-in way to achieve it?
There is not currently a way to only sort the elements which are currently being displayed, no.
React Table v7's useSortBy hook sorts the entirety of the input data array, so sorting by First Name descending (A->Z) naturally places 'ZZZZZZ' at the end of the list, which will be hidden due to pagination. The v7 way of doing it would probably be similar to what you are doing already, using the exposed properties from the useTable hook instead of reactTable.resolvedState() and etc. Another way to do it would be to write your own custom sortBy method and only sort the items [0...n] where n is the number of currently displayed items.
As a side note, since the autoResetSortBy property is true by default in the example you linked, the step of sorting on First Name is irrelevant -- since the function which adds a new user to the list mutates the data array, the sorting method is reset. That function appends the new user to the end of the list, so it will always be on a new page, even if the "Rows per page" option is set to "All". Both issues can be fixed by setting autoResetSortBy to false, and changing the pageSize in addUserHandler.

Warnings in AngularJs

Angularjs has great infrastructure for form validation and showing error messages. But, I am in a situation that I have to show a warning message to a user in a specific scenario. Here is the diagram of my simple form
The form has required and pattern validation applied on both fields. In addition to this validation I want a warning message to be displayed to the user if VatAmount is not 20 percent of the InvoiceAmount. The warning will differ from validation in following aspects
It will not prevent the form submission
It will only appear if both fields (InvoiceAmount and VATAmount) are
valid
The warning should have a button or link that would read "Change and
proceed". When user presses that button the warning message will
hide and focus will be set to VATAmount field.
I believe this is a prefect use case for creating a directive. Actually, I have given it a try and put my effort in the form of a plunker. But my directive does not handle following cases
It appears even if the fields involved in warning are invalid
The show and hide functionality is not implemented (have no idea how
to target it)
Here is the link to the plunker
Your plunkr demo was on the right track; really you just needed to check for the special cases of when one of the values was empty.
I'd suggest calculating the fraction and storing it in the scope, and then watching that to see whether you should display your tax rate warning. Here's how to calculate the fraction. If either invoice.Amount or invoice.VAT is empty, the fraction will be set to null.
if (amt == null || vat == null) {
$scope.warning.fraction = null;
return;
}
$scope.warning.fraction = vat / amt;
This works because those properties will be set to undefined if the user doesn't enter a valid number due to your use of ng-pattern.
However, while it's nice to encapsulate this in a directive, you don't need to compile the template yourself. Just use the built-in ng-transclude directive. Then you can include a button that references the current scope like this:
<vat-warning>
Vat Amount is not 20%.
<button ng-click="invoice.VAT = invoice.Amount / 5">change</button>
</vat-warning>
Your directive would contain this declaration:
transclude: true,
template: '<span class="alert-warning" ng-show="warning.show" ng-transclude></span>'
Plus a controller to update the directive's local scope to manipulate the warning object. Here's an updated demo.
You need to calculate visibility of vat-warning tag in controller on basis of $error.required and $error.pattern of invoiceAmount and vatAmount and then use it as below:
$scope.isInvoiceAmountInvalid = function () {
var error = $scope.invoiceForm.invoiceAmount.$error;
var required = error.hasOwnProperty("required") && error.required;
var pattern = error.hasOwnProperty("pattern") && error.pattern;
console.log("Inside isInvoiceAmountInvalid", error, required, pattern);
return (required || pattern);
};
$scope.isVatAmountInvalid = function () {
var error = $scope.invoiceForm.vatAmount.$error;
var required = error.hasOwnProperty("required") && error.required;
var pattern = error.hasOwnProperty("pattern") && error.pattern;
console.log("Inside isVatAmountInvalid", error, required, pattern);
return (required || pattern);
};
Here is an updated plunker for the same

How to watch for change in one particular nested array item attribute in Angular JS?

I have an array that looks like this:
$rootScope.array = [{
"id":"a",
"title":"a",
"options":[
{
"optId":"abc",
"isInvalid":false
},
{
"optId":"efg",
"isInvalid":false
}
]
},
{
"id":"b",
"title":"b",
"options":[
{
"optId":"hij",
"isInvalid":false
},
{
"optId":"lmn",
"isInvalid":false
}
]
}];
On any change to the 'isInvalid' attribute inside 'options', I want to trigger a function that would evaluate the entire array and set some properties to 'true' and some to 'false'.
I understand something of this sort can be done using deep $watch object equivalence, but I am not sure as to how exactly I can watch only for change in the 'isInvalid' and not change in lets say 'optId'.
In the bigger scheme of things, my intent is to validate the entire array by a set of business rules. So, if the "isInvalid" attribute of the first object changes to 'true', I want to make sure that the 'isInvalid' of the second object is set to 'false' and so on.
So if there is a better way to do this other than $watch, I am all ears, because I'm relatively new to Angular and I'm a little lost!
Rather than using a deep watch (which can silently cause performance issues), create a custom function for your watch expression. The example below iterates through the array elements and options creating one check string containing each item's id, option id and its respective isInvalid value.
The check string will look something like this: "a|abcfalseefgfalseb|hijfalselmnfalse"
If an isInvalid value changes, the check string will change, and the watch will trigger.
$scope.$watch(function() {
var check = "";
for (var i = 0; i < $scope.array.length; i++) {
var item = $scope.array[i];
check += item.id + "|";
for (var j = 0; j < item.options.length; j++) {
var option = item.options[j];
check += option.id + item.isInvalid;
}
}
return check;
}, doSomething());
If your structure is not too big, a deep watch would do the trick.
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
If you are concerned about performance, and depending your scenario, you could evaluate doing something like:
Having a parent/global isInvalid flag, whenever a flag is marked as invalid mark as well the global invalid, then you only have to watch for the global invalid flag and once the flag is invalid traverse to find the not valid element.
Other options that come to my mind could be to add a watch per entry in the array, but not sure if this could have worse performance than the deepwatch solution.
If you don't make a deepwatch abuse, it can be a good solution for your scenario.
If you don't want to use a deep watch. You can define your own watch logic as well.
According to angular document. In $watch(expression) expression function will be executed multiple times during $digest.
So you can use you own code to detect the change in your huge array.
live demo: http://plnkr.co/edit/X4J5Faoa2ni79RmFa3co?p=preview (open your console to see the log).

ExtJS findExact() and custom validator bug

I'm using a custom validator on my combobox's:
function(v) {
console.log(v === 'some value I know for SURE is in the store'); // (1)
var index = this.getStore().findExact(this.displayField, v);
return (index!==-1) ? true : 'Invalid selection';
}
Basically admits the same set as forceSelection but allows the user to type arbitrary text to attempt to auto-complete.
However; I'm having really odd results with findExact(). For example, if the combobox's value is currently valid, and a user does a space + backspace, the validator will fail, even though the output of (1) is true.
Any ideas what is causing the problem? The end-experience is currently very buggy-feeling..
When you type additional space, store is filtered. After you press backspace, and validator is fired, store is still empty.
If you have local store, then you could validate combo with some delay after each change. Example:
listeners: {
change: function() {
this.validate();
},
delay: 100
}
That should be enough.
On the other hand if you have remote store, try something like this:
validator: function(v) {
var store = this.getStore(),
index = store.findExact(this.displayField, v);
if (index === -1 && store.isLoading()) {
store.on('load', function() {
this.validate();
}, this, { single: true, delay: 100 });
}
return (index !== -1) ? true : 'Invalid selection';
}
I had a similar issue and found this entry. My problem was, that I reused the same store instance across multiple ComboBoxes. After giving each ComboBox an own store with cloned data, everything was fine.
See also:
https://www.sencha.com/forum/showthread.php?305182-ComboBox-forceSelection-clears-input&p=1127218&viewfull=1#post1127218
I just spent a few days on this issue and found a really great solution (by accident, really). You can - as the accepted answer suggests - utilize the provided validator function; however, from my understanding, there is a much simpler solution than the accepted answer provides: evaluating whether or not the user-provided input equates to a value in the store (which is the underlying question in the original post).
The advantage of thinking about the input in this way is that it enables us to handle the use case of an invalid value entered by the user (validated; no value) and - after the field loses focus - Ext JS sets the field back to its previous value (which remembers its store value).
This is an entirely different route than your thinking, but it should work, especially as .validate() runs regardless of whether you provide an implementation of the validator procedure:
validator : function(someParam) {
if(this.value === null) {
return "error message"; //falsy
} else {
return true;
}
}
If you enable forceSelection, the above works, very well, and gets rid of the buggy feeling. This allows you to rely on the .validate to do its magic elsewhere (notice I don't even call it; read the doc. to figure out when its called in relationship to validator) and not have to worry about what the user correctly explains in the accepted answer.
We were having trouble with forceSelection clearing user text before they were finished typing. We seemed to get what we needed by setting forceSelection false and just checking that they selected something.
validator: function(v) {
if (this.getSelection() === null) {
return 'invalid text';
}else{
return true;
}
}

Resources