In my React application, I have the following code.
state = {
credentialDeletion: false,
accountDeletion: false
}
static getDerivedStateFromProps(nextProps) {
if (nextProps.accountDeleting) {
return {
accountDeletion: true
}
}
if (nextProps.credentialDeletion) {
return {
credentialDeletion: true
}
}
return null;
}
But this code doesn't work properly. If I remove one condition, then the other works fine. But together, only the first condition works. How should I write this code properly?
You could create an object that you add properties to in each if statement and return that, so that both if statements will be run.
static getDerivedStateFromProps(nextProps) {
const update = {};
if (nextProps.accountDeleting) {
update.accountDeletion = true;
}
if (nextProps.credentialDeletion) {
update.credentialDeletion = true;
}
return update;
}
Related
Opening this question here, because StackOverflow is listed as a recommended place for asking React-related questions in React docs.
I am looking for a reasoning behind throwing a The final argument passed to useCallback changed size between renders. The order and size of this array must remain constant. warning.
After looking into React code it looks like React does not properly compare prevDeps and nextDeps arrays when they have different lengths.
The comparison function looks like this (some checks were omitted for brevity):
function areHookInputsEqual(prevDeps,nextDeps) {
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
Which means:
areHookInputsEqual( ['a','b'], ['c','d'] ) === false - correct
areHookInputsEqual( ['a','b','c'], ['a','b'] ) === true - wrong
areHookInputsEqual( ['a','b'], ['a','b','c'] ) === true - wrong
areHookInputsEqual( [], ['a'] ) === true - wrong
areHookInputsEqual( ['a'], [] ) === true - wrong
Why not to write this function as following and remove warning from the codebase?
function areHookInputsEqual(prevDeps,nextDeps) {
if (prevDeps.length !== nextDeps.length) {
return false;
}
for (let i = 0; i < prevDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
The use case which led to this question
We have a custom hook like this:
function useLoadMessageKeys(messageKeys: string[]) {
return React.useCallback(
() => {
return load(messageKeys)
},
messageKeys
)
}
Because of current React implementation, load does not get called when messageKeys change from [] to ['a'].
Update (how we currently solved this)
function areArraysEqual<T>(prevDeps: T[], nextDeps: T[]): boolean {
if (prevDeps === nextDeps) {
return true
}
if (prevDeps.length !== nextDeps.length) {
return false
}
for (let i = 0; i < prevDeps.length; i++) {
if (!Object.is(nextDeps[i], prevDeps[i])) {
return false
}
}
return true
}
export function useLoadMessageKeys(messageKeys: string[]) {
const messageKeysRef = React.useRef(messageKeys)
if (!areArraysEqual(messageKeys, messageKeysRef.current)) {
messageKeysRef.current = messageKeys
}
const currentMessageKeys = messageKeysRef.current
return React.useCallback(
() => load(currentMessageKeys),
[currentMessageKeys]
)
}
If the code linked in the question properly compared 2 arrays we'd avoid having this complexity.
I think the reasoning is that the array of dependencies should always be exactly the list of variables used inside the effect. So in particular it can be statically determined and cannot change size. If it does change size, you're probably doing something more than just listing the dependencies, so it is warning you that you are not using the dependency list as intended.
You could use instead use a version of messageKeys that does not change if it is only shallow equal to the previous one (untested):
const useMemoizedArray = (array: string[]) => {
const [memoizedArray, setMemoizedArray] = React.useState(array);
React.useEffect(() => {
// Define `isShallowEqual` yourself somewhere
if (!isShallowEqual(array, memoizedArray)) {
setMemoizedArray(array);
}
}, [array, memoizedArray]);
return memoizedArray;
};
function useLoadMessageKeys(messageKeys: string[]) {
const memoizedMessageKeys = useMemoizedArray(messageKeys);
return React.useCallback(
() => {
return load(memoizedMessageKeys)
},
[memoizedMessageKeys]
)
}
The combobox in modern fires the "select" event every time the input changes. This is very different from classic. With it doing this there is no way to differentiate between a user making a selection and pragmatically setting the value of the field.
This was reported as a bug in the Sencha forums:
https://www.sencha.com/forum/showthread.php?468730-Modern-6-5-2-ComboBox-fires-select-event-on-every-value-change
The link above also contains a fiddler to demonstrate the issue.
Has anyone run into this as an issue, and how did you overcome it?
forceSelection: true will help to solve this problem but will not cancel the bug in cases when forced selection is not needed
Edit:
This behavior is due to method syncValue (search in this source - method is private and hasn't documentation)
I don’t understand why the component developer chose to create a record even if it isn’t exist.
Comment from source file:
Either user has typed something (isInput), or we've had a setValue to a value which has no match in the store, and we are not forceSelection: true. We create a new record.
I propose to fix this behavior using the following override:
fiddle
Ext.define('Ext.field.SelectOverride', {
override: 'Ext.field.Select',
autoCreateRecord: false,
syncValue: function() {
var me = this,
store = me.getStore(),
forceSelection = me.getForceSelection(),
valueNotFoundText = me.getValueNotFoundText(),
is, isCleared, isInput, value, matchedRecord;
if (me.reconcilingValue || !store || !store.isLoaded() || store.hasPendingLoad()) {
return;
}
me.reconcilingValue = true;
me.getSelection(); // make sure selection config is flushed
is = {};
is[me.syncMode] = true;
value = ((isInput = is.input || is.filter)) ? me.getInputValue() : me.getValue();
isCleared = value == null || value === '';
if (!isCleared) {
if (me.getMultiSelect()) {
return me.syncMultiValues(Ext.Array.from(value));
}
matchedRecord = (isInput ? store.byText : store.byValue).get(value);
if (matchedRecord) {
if (!matchedRecord.isEntity) {
matchedRecord = matchedRecord[0];
}
}
else if (!forceSelection) {
matchedRecord = me.findRecordByValue(value);
}
}
// Either user has typed something (isInput), or we've had a setValue
// to a value which has no match in the store, and we are not forceSelection: true.
// We create a new record.
if (!isCleared && !matchedRecord && !forceSelection && me.autoCreateRecord) {
matchedRecord = me.createEnteredRecord(value);
}
else {
if (isInput || is.store) {
if (!matchedRecord && forceSelection) {
me.setValue(null);
me.setSelection(null);
if (!is.filter) {
me.setFieldDisplay();
}
}
} else {
if (isCleared) {
if (me.mustAutoSelect()) {
matchedRecord = store.first();
if (me.getAutoSelect() === 'initial') {
me.setAutoSelect(false);
}
}
else {
me.setSelection(null);
}
}
else if (!matchedRecord && valueNotFoundText) {
me.setError(valueNotFoundText);
}
}
}
if (matchedRecord) {
me.setSelection(matchedRecord);
}
me.reconcilingValue = false;
}
});
I try to test method like this:
method() {
return service1.method1.then((result) => {
if(result.val1 && result.val2) {
return service2.method2.then(() =>{
if() {
return service3.method3().then(callback);
}
})
}
}
}
I just understand how to test call of service1.method1 but how I can check another methods and conditions in them?
Please, give me direction, because I already tryed all I know^)
If i do:
scope.$watch('obj', function(newObj, oldObj) {
//...
}, true);
How do I find the key-value pair in the object that changed?
.
Only to understand what I try to do:
I have an object of the form:
scope.actions = {
action1: false,
action2: false
}
When the boolean changes, I want to assign function calls to it. Something like, DO-action - UNDO-action.
So I watch it the following way:
scope.$watch('actions', function(newObj, oldObj) {
/*PSEUDO CODE START*/
IF (action1 changed && action1 true) {
do-func1();
}
IF (action1 changed && action1 false) {
undo-func1();
}
...
/*PSEUDO CODE END*/
}, true);
My problem here is, that if I check the values for their boolean, all the functions get called. So the point here is, how do I find the changed key-value pair in the object?
scope.$watch('actions', function(newActions, oldActions) {
for(var i in newActions) {
if(newActions.hasOwnProperty(i)) {
var newAction = newActions[i];
var oldAction = oldActions[i];
if(newAction !== oldAction) {
if(newAction === true) {
doActions[i](); // do-func-i();
} else if (newAction === false) {
undoActions[i](); // undo-func-i();
}
}
}
}
}, true);
doActions here is a conventional map of actions
doActions = {
action1 : function() {
console.log('action1');
},
action2 : function() {
console.log('action2');
}
}
doActions['action1'] will reference first function
You may have an array as well, but then you'll need to introduce an index to fetch proper function doActions[0]
I want that i can turn the live synchronisation on and off, but it's not getting on again.
function startSync() {
localdbSync = localDB.sync(remoteDB, {live: true});
}
function stopSync() {
localdbSync.cancel();
}
$scope.toggleSync = function () {
if (localDB.sync.canceled == true) {
startSync();
} else {
stopSync();
}
};
Thanks for any hints.
nevermind, same code with using a service now works.