Mutating ref value from watcher in Vue 3 with Composition API - javascript

I'm trying to mutate a ref within a watch function but it doesn't seem to work. I'm using watch to pay attention if my array length changes, whenever that changes I need to compare the old length with the new one and then mutate a ref (selectedTitle) if the newValue is less than the oldValue.
setup() {
const slots = useSlots();
const tabTitles = computed(() =>
slots.default()[0].children.map((tab) => tab.props.title)
);
const tabTitlesLength = computed(() => tabTitles.value.length);
let selectedTitle = ref(tabTitles.value[0]);
provide("selectedTitle", selectedTitle);
provide("tabTitles", tabTitles);
watch(tabTitlesLength, (currentValue, oldValue) => {
if (currentValue < oldValue) {
selectedTitle = tabTitles.value[0];
} else {
console.log("fuera del if");
}
});
return {
tabTitles,
selectedTitle,
tabTitlesLength,
};
},
With that code the selectedTitle ref never change it always has the last value that received whenever the computed property changes (tabTitles). I just don't understand why since everything else seems to work just fine.

Since selectedTitle is ref try with
selectedTitle.value = tabTitles.value[0];

Related

How to set state with a setInterval() when a button is clicked?

I'm trying to update the count of activeIndex within the setInterval when the checkbox is ticked. handleNext() and handlePrevious() are working fine when buttons are clicked but when the checkbox is checked the value of activeIndex is not getting updated in handleNext() but it's getting updated on the screen, so there is no condition check for activeIndex and it goes beyond 3.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [slideTimer, setSlideTimer] = useState(null);
const [activeIndex, setActiveIndex] = useState(0);
const slideDuration = 1000;
const handleNext = () => {
if ((activeIndex) >= 3) {
setActiveIndex(0);
} else {
setActiveIndex(prev => prev + 1);
}
};
const handlePrev = () => {
if (activeIndex <= 0) {
setActiveIndex(3);
} else {
setActiveIndex((prev) => prev - 1);
}
};
const toggleSlider = () => {
if (slideTimer) {
clearInterval(slideTimer);
setSlideTimer(null);
return;
}
setSlideTimer(setInterval(handleNext, slideDuration));
};
return (
<div className="App">
<h1>{activeIndex}</h1>
<button onClick={() => handleNext()}>Next</button>
<button onClick={() => handlePrev()}>Previous</button>
<input onClick={() => toggleSlider()} type="checkbox" />
</div>
);
}
I have tried putting the code for toggleSlider inside useEffect() but that too is not working.
Your problem is that when handleNext gets defined (which occurs on every rerender), it only knows about the variables/state in its surrounding scope at the time that it's defined. As a result, when you queue an interval with:
setInterval(handleNext, slideDuration)
You will be executing the handleNext function that only knows about the component's state at that time. When your component eventually rerenders and sets a new value for activeIndex, your interval will still be exeucting the "old" handleNext function defined in the previous render that doesn't know about the newly updated state. One option to resolve this issue is to make the hanldeNext function not rely on state obtained from its outer scope, but instead, use the state setter function to get the current value:
const handleNext = () => {
setActiveIndex(currIndex => (currIndex + 1) % 4);
};
Above I've used % to cycle the index back to 0, but you very well can also use your if-statement, where you return 0 if currIndex >= 3, or return currIndex + 1 in your else.
I would also recommend that you remove the slideTimer. This state value isn't being used to describe your UI (you can see that as you're not using slideTimer within your returned JSX). In this case, you're better off using a ref to store your interval id:
const slideTimerRef = useRef(0); // instead of the `slideTimer` state
...
clearInterval(slideTimerRef.current);
slideTimerRef.current = null;
...
slideTimerRef.current = setInterval(handleNext, slideDuration);

useEffect received a final argument during this render

I am getting this warning:
Warning: useEffect received a final argument during this render, but not during the previous render. Even though the final argument is optional, its type cannot change between renders.
warning screenshot here
//importing ...
const Relevance = () => {
const { data, setData } = useContext(defaultData);
if (data.length > 0) {
const Relevance = data.map((d) => {
return d.relevance;
});
const maxValue = Math.max(...Relevance);
let HashRelevance = new Array(maxValue + 1).fill(0);
for (let i = 0; i < Relevance.length; i++) {
if (Relevance[i] !== null) {
HashRelevance[Relevance[i]]++;
}
}
var keyRelevance = [];
var valueRelevance = [];
for (let i = 0; i < HashRelevance.length; i++) {
if (HashRelevance[i] !== 0) {
keyRelevance.push(i);
valueRelevance.push(HashRelevance[i]);
}
}
}
const [keyRelevanceState, setKeyIntenstityState] = useState([]);
const [valueRelevanceState, setValueIntenstityState] = useState([]);
useEffect(() => {
setKeyIntenstityState(keyRelevance);
}, keyRelevance);
useEffect(() => {
setValueIntenstityState(valueRelevance);
}, valueRelevance);
if (valueRelevanceState !== undefined && valueRelevanceState.length > 0) {
return (
returning component...
)
};
export default Relevance;
What is the error about?
That error is telling you that you're not passing a consistent value to useEffect as its final (second) argument (the optional dependency array) between renders. The dependency array argument you pass useEffect must either always be an array of dependencies (with the same length), or always be undefined (because you left it off, usually), on every render of your component.
You have this code:
if (/*...a condition...*/) {
// ...
var keyRelevance = [];
// ...
}
// ...
useEffect(() => {
// ...
}, keyRelevance);
That's functionally identical to:
var keyRelevance; // ***
if (/*...a condition...*/) {
// ...
keyRelevance = []; // ***
// ...
}
// ...
useEffect(() => {
// ...
}, keyRelevance);
That means you're declaring a keyRelevance variable that will initially have the value undefined, and then only assigning an array to it if a condition is met. Then you're using keyRelevance as the dependencies array.
The second argument to useEffect should be an array of dependencies, not just a single dependency, so wrap keyRelevance in an array:
useEffect(() => {
setKeyIntenstityState(keyRelevance);
}, [keyRelevance]);
// ^ ^
More in the documentation.
But separately, since you're creating a new array every time, that would cause the effect to change every time. Instead, memoize the creation of keyRelevance (and similar) using useMemo or useRef (since useMemo's documentation says it's just for performance optimization, and in your case you need to memoize the value for correctness, not just optimization).
For instance:
const relevanceInfo = useRef(null);
// (Probably put this condition in a function)
if (!relevanceInfo ||
data.length !== relevanceInfo.sourceData.length ||
data.some((d, index) => d.relevance !== relevanceInfo.sourceData[index].relevance)) {
relevanceInfo.keyRelevance = /* ...build the array... */;
relevanceInfo.sourceData = data;
}
// ...
useEffect(() => {
// ...
}, [relevanceInfo.keyRelevance]);
...or similar.
Side note: I wouldn't declare the variable inside the if block and then use it outside the if block. Instead, declare it in the top scope where it'll be used (in this case function scope). (Also, I suggest using let and const, not var; var is effectively deprecated and shouldn't be used in new code.)
Final value, (second parameter) of the useEffect should be in array of different values or empty array. You are providing a valueRelevance as final value of useEffect which itself an array but not in array , like below.
useEffect(() => {
setKeyIntenstityState(keyRelevance);
}, [valueRelevance]);
Or,
useEffect(() => {
setKeyIntenstityState(keyRelevance);
}, [param1, param2]);

Two similar react functions produce Inconsistent Results in rerendering

This function works properly and the component rerenders
handleRemove = (e) => {
//console.log(e);
const arrayCopy = this.state.scanlist.filter((row) => row.ref + row.lot !== e.ref + e.lot);
this.setState({ scanlist: arrayCopy });};
This function changes the state but the component does not rerender
handleAdd = (e) => {
//console.log(e);
const index = this.state.scanlist.findIndex((row) => row.ref === e.ref && row.lot === e.lot);
let scancopy = this.state.scanlist;
scancopy[index].qty = scancopy[index].qty + 1;
console.log(scancopy);
this.setState({ scanlist: scancopy });};
Does anyone see the issue? Mutation?
Using Array.prototype.filter to remove an element from an array is pretty standard, but in the second handler yes, you've a state object mutation.
handleAdd = (e) => {
const index = this.state.scanlist.findIndex((row) => row.ref === e.ref && row.lot === e.lot);
let scancopy = this.state.scanlist;
scancopy[index].qty = scancopy[index].qty + 1; // <-- state object mutation
this.setState({ scanlist: scancopy });
};
You should shallow copy the scanlist array and the element that is being updated. Array.prototype.map is a common method to shallow copy the state array, and spreading the object properties of the element that needs to be updated shallow copies it. Anytime you are updating any nested state objects you should shallow copy the parent object.
handleAdd = (e) => {
this.setState(prevState => ({
scanlist: prevState.scanlist.map(
(row) => row.ref === e.ref && row.lot === e.lot ? {
...row,
qty: row.qty + 1,
} : row)
}));
};
In the first approach filter method returns new list, whereas in the second approach it is just referencing to state array.
Try shallow copy in second approach,
let scancopy = [...this.state.scanlist]
Or
Simple deep copy approach,
let scancopy = JSON.parse(JSON.stringify(this.state.scanlist));

How to update value returned by function React (useState implementation)

Let's say I have such a thing, function returning value and setter Function, how Can I implement the setter function correctly to update returned value , every time it is called? (like useState's returned value and the updater function)
const myFunction = (initialValue) => {
let value = initialValue;
const setterFunction = (newValue) =>{
value= newValue;
}
forceRerender() //function that forces re-renders
return [value,setterFunction];
}
const [myValue,updaterFunc] = myFunction('someValue');
updaterFunc('newValue'); // myValue's new Value should be 'newValue'
If you're trying to re-implement how React does it, you would have to have setter functions result in the whole block running again - something like the following:
const state = [];
const App = () => {
let stateIndex = 0; // This variable keeps track of the sequential calls to `myFunction`
// (Needed if myFunction gets called multiple times)
const myFunction = (initialValue) => {
// Assign initial value if it doesn't exist yet
if (!state.hasOwnProperty(stateIndex)) state[stateIndex] = initialValue;
const value = state[stateIndex];
// Need to create a closure over the current state index...
const closureStateIndex = stateIndex;
const setterFunction = (newValue) => {
state[closureStateIndex] = newValue;
// Re-run the entire function asynchronously:
setTimeout(App);
};
// Increment state index to allow for additional calls to myFunction
stateIndex++;
return [value, setterFunction];
}
const [myValue, updaterFunc] = myFunction('initialValue');
// Call updater only on initial render:
if (myValue === 'initialValue') {
updaterFunc('newValue'); // myValue's new Value should be 'newValue'
}
console.log('Rendering. Current value is:', myValue);
};
App();
That's a bit similar to how React does it.
For an example with multiple state variables, and renaming myFunction to useState:
const state = [];
const App = () => {
let stateIndex = 0;
const useState = (initialValue) => {
if (!state.hasOwnProperty(stateIndex)) state[stateIndex] = initialValue;
const value = state[stateIndex];
const closureStateIndex = stateIndex;
const setterFunction = (newValue) => {
state[closureStateIndex] = newValue;
setTimeout(App);
};
stateIndex++;
return [value, setterFunction];
}
const [name, setName] = useState('bob');
const [age, setAge] = useState(5);
if (age === 5) {
setAge(10);
}
console.log('Rendering. Current name and age is:', name, age);
};
App();

create react state name with variable reference?

i want to create state like this:
componentWillReceiveProps(nextProps) {
nextProps.columns.forEach((c) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
this.setState({ `${name}`: (this.props.activeHeaders.indexOf(c) > -1) });
console.log(`${name}`);
});
}
I am mapping on my array columns, so each item on the array, i want to set state on them as key, is there a possibe way?
Is there a possible way?
Yes, but the way you are trying is not correct, instead of calling setState inside loop, first prepare an object with all the key-value, then pass that object to setState.
Like this:
componentWillReceiveProps(nextProps) {
let obj = {};
nextProps.columns.forEach((c, i) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
obj[name] = this.props.activeHeaders.indexOf(c) > -1;
});
this.setState(obj);
}
Didn't get the meaning of this line:
const name = nextProps.columns[nextProps.columns.indexOf(c)];
componentWillReceiveProps(nextProps) {
nextProps.columns.forEach((c) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
this.setState({ [name]: (this.props.activeHeaders.indexOf(c) > -1) });
console.log(`${name}`);
});
}
This should do the job

Categories

Resources