useEffect received a final argument during this render - javascript

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]);

Related

React 'Cannot set properties to undefined': expensive computation?

I have an issue while rendering the following.
I have a useBoard hook, which should create a new board: a 2d Array, which every cell is an object, in which some of these object have a value between 1-3 included, and the other 'empty cells' have the 0 value.
Cell object:
{
value: 0,
className: ''
}
My useBoard simply call the function to create the board.
export const useBoard = ({ rows, columns, pieces }: IBoardItems) => {
const [board, setBoard] = useState<ICell[][]>();
useEffect(() => {
const getBoard = () => {
const newBoard = createBoard({ rows, columns, pieces });
setBoard(newBoard);
}
getBoard();
}, [])
return [board] as const;
};
And here is the utility function to build it.
export const createBoard = ({ rows, columns, pieces }: IBoardItems) => {
let randomIndexes: number[][] = [];
while (randomIndexes.length < pieces) {
let randomIndex = [getRandomNumber(5, rows), getRandomNumber(0, columns)];
if (randomIndexes.includes(randomIndex)) {
continue;
} else {
randomIndexes.push(randomIndex);
};
};
let board: ICell[][] = [];
for (let i = 0; i < rows; i++) {
board.push(Array(columns).fill(defaultCell));
};
randomIndexes.forEach(([y, x]) => {
board[y][x] = {
value: getRandomNumber(1, 3),
className: ''
}
});
return board;
};
The fact is, that when I click start in my related component, which should render the <Game /> containing the <Board />, sometimes it works, sometimes not, and the console logs the error 'Cannot set properties to undefined'. I think I understand but I'm not sure: is it because could happen, that in some steps, some data is not ready to work with other data?
So, in this such of cases, which would be the best approach? Callbacks? Promises? Async/await?
NB. Before, I splitted the createBoard function into smaller functions, each one with its owns work, but to understand the issue I tried to make only one big, but actually I would like to re-split it.
EDIT: I maybe found the issue. In the createBoard I used getRandomNumber which actualy goes over the array length. Now the problem no longer occurs, but anyway, my question are not answered.

store function names in an array and call them one by one using array.forEach()

I am building a react-native has which has almost 15 states to store some data. Now I want is store all these values (and fetch all of them) in/from internal storage, I'm using react-native-mmkv.
//functions from context api
const {setUsername, setToken, setProgress} = useAppContext();
const getsettingsconfig = () => {
const keys = storage.getAllKeys();
console.log(keys); // ['setProgress', 'setUsername', 'setToken']
keys.forEach(key => {
const data = storage.getString(key);
// I want to set all the values dynamically here
key(parsedData); // setProgress(data), setUsername(data) etc. like this
});
};
useAppContext.js
import { createContext, useContext } from "react";
export const GlobalContext = createContext({});
export default function useAppContext() {
return useContext(GlobalContext);
}
However, I keep getting
[TypeError: 'setProgress' is not a function]
What am I doing wrong here?
This is one possible way to execute a function whose name is present in an array. The trick here is mapping the name of the function with the function itself (by using the object myObj). Then, one simply accesses the object with the key, and executes the function (which is the corresponding value in myObj object).
// declaring, defining 4 functions here
// this may even be from useState - same will work
const setField1 = d => console.log('setField1, data: ', d);
const setField2 = d => console.log('setField2, data: ', d);
const setField3 = d => console.log('setField3, data: ', d);
const setField4 = d => console.log('setField4, data: ', d);
// declaring an array of functions
// this can be populated from local-storage - and it will work
const fnArr = [setField1, setField2, setField3, setField4];
// for-each loop to execute each function in the array
fnArr.forEach((fn, idx) => fn(idx));
// using a string array - which will not execute the functions
console.log("\n\n can't execute strings as functions\n\n");
const strArr = ['setField1', 'setField2', 'setField3', 'setField4'];
strArr.forEach(st => console.log(`'${st}' is a string & not a function... one can't execute it`));
// executing the same functions by using the string keys
console.log('\n\n executing functions by using string-names as keys\n\n');
const myObj = Object.fromEntries(strArr.map((k, idx) => ([ k, fnArr[idx]])));
strArr.forEach((k, idx) => myObj?.[k](idx));
.as-console-wrapper { max-height: 100% !important; top: 0 }

Can we use expose method to return other reactive variables and computed properties like methods in vue 3?

I am changing my application from vue 2 to vue 3. By using composition api,i have changed my previous render function in that setup hook. After checking some documentation,I got to know that i can expose methods by using context.expose({}).
Now my questions are:
How to replace created method in vue 3 composition api? (As we know, setup hook occurs beforeCreate hook but not able to understand how to do those operations inside setup hook?)
Can we return reactive variables and computed properties by using context.expose?
Here is my setup script:
<script>
import {h,ref,computed,provide} from 'vue';
export default {
name: 'something',
props: some props,
setup(props,context) {
const currIdx = ref(0);
const tabCnt = ref(0);
const idxMap = ref(new Map());
const idxs = ref([]);
// other variables
// computed properties
const $_size = computed(() =>{
// 1. check this.size
if(props.size) {//'medium','small'
if(props.size === 'medium') return 'medium'
if(props.size === 'small' ) return 'small'
}
// 2. check flags
if(props.medium) return 'medium'
if(props.small ) return 'small'
// 3. default value : 'medium'
return 'medium';
});
// [COMPUTED] Props normalize : SHAPE
const $_shape = computed(() =>{
// 1. check this.shape
if(props.shape) { // 'border','underline','none'
if(props.shape === 'border' ) return 'border'
if(props.shape === 'underline') return 'underline'
if(props.shape === 'none' ) return 'none'
}
// 2. check flags
if(props.border ) return 'border'
if(props.underline) return 'underline'
// x. default value : 'border'
return 'border';
});
// [COMPUTED] - [END REGION]
const getLabelNode = (props) => {
var label = props.label, idx = props.idx, disabled = !!(props.disabled || props.disable)
return h('vu-tab-label',{props:{idx, disabled}},[label]);
};
// 2. make window area -> label + navi(?)
var labelWindow = [];
labelWindow.push(h("div",{"class":"vu-tab__label-wrapper","ref":"scroll"}, labels));
if(props.navigation || props.addBtn) {
labelWindow.push(h(tabNavi))
}
// 3. do something
idxs.value = Array.from(idxMap.value.keys());
// 4. make class
let tabClass = [];
tabClass.push("vu_tab-box");
tabClass.push(`vu-tab-box--${this.$_shape}`);
// methods
const onAddClick =(e) => {
context.emit('add-tab',e);
};
context.expose({
onAddClick,
});
// x. return all nodes
return h("div",{"class":tabClass},[
h("div",{"class":"vu-tab__label-window","ref":"window"},labelWindow),
h("div",{"class":"vu-tab__content-wrapper"},contents)
]);
},
}
</script>
For question 1, It is my created hook and i want to do those operations inside setup.
created() {
// 1. Check default index
if( (this.defaultIdx === 0) || this.defaultIdx ) {
this.currIdx = this.defaultIdx;
return;
}
// 2. check slots
var slots = this.$slots['default']
if(!!slots) {
slots.find(vNode => {
if (!vNode.componentOptions) { return false }
var idx = vNode.componentOptions.propsData.idx;
if (idx === undefined) { return false }
this.currIdx = idx;
return true;
});
}
},
created hook in the composition api
This one is simple, there is no created or beforeCreate hook in the composition api. It is entirely replaced by setup. You can just run your code in the setup function directly or put it in a function you call from within setup.
Are properties exposed using expose reactive
Yes. While accessing values of child components using template refs is not really the "Vue"-way, it is possible and the values passed keep their reactivity. I couldn't find any documentation on this, so I quickly implemented a small code sandbox to try it out. See for yourself.
https://codesandbox.io/s/falling-breeze-twetx3?file=/src/App.vue
(If you encounter an error similar to "Cannot use import outside a module", just reload the browser within code sandbox, there seems to be an issue with the code sandbox template)

How to deal with a `Variable 'xxx' is used before being assigned.`

I have a code block like below where I need to find something inside a loop, and also return a second variable. So I can't use a simple Array.find or Array.some good ole' for...of is my friend. map/filter don't allow a break and find can only return actual elements from the array, not a related calculation.
But the below within typescript is giving me an unavoidable error.
I'm wondering if either there's a more idiomatic way to do this, or a better structure / place to declare the variable?
Variable 'found' is used before being assigned.
let found: ParseResult
// breaks when first item found but we capture a different value
for (const rule of ParserRules) {
// const rex = new RegExp(route.match)
const parsed = rule.rex.exec(input)
if (parsed) {
found = { parsed, rule }
break
}
}
// #ts-ignore
return found // FIXME used before defined?
Here are the various JS iterator methods I tried...
const ar = [1, 2, 3, 4]
const log = console.log
const finder = (list) => {
console.log('map', list.map(it => it === 3))
console.log('find', list.find(it => it === 3))
console.log('some', list.some(it => it === 3))
console.log('filter', list.filter(it => it === 3))
console.log('find', list.find(it => {
if (it === 3) return it * 2 // value coerced to T|F
}))
console.log('filter', list.filter(it => {
if (it === 3) return it * 2 // value coerced to T|F
}))
const arr = list.forEach((k) => {
if (k === 3) return ('here')
})
log('arr', arr)
let found
for (const elem of list) {
log('elem of', elem)
if (elem === 2) {
found = elem
break
}
}
log('found', found)
}
finder(ar)
The summary of your problem is that a function returning a value in a variable when, at times, the logic doesn't get a chance to assign any value to it.
You can either initialize the variable with a default value or, at the point of returning, check if it really has a value.
let found: ParseResult= {}
OR
return found || false //Or an empty object etc
This can be done in many ways but what might suit your case would be
ar
.map((rule) => {
const parsed = rule.rex.exec(input)
if (parsed) {
return { parsed, rule }
}
})
.find((x) => !!x)
Yes you are looping it once more but this is more readable. Also it would not be that costly.
If your processing is heavy, you can try this approach as well but this will be a custom implementation and will not come out of the box:
function getParsedValue(ar, input) {
let parsed;
const rule = ar
.find((rule) => {
parsed = rule.rex.exec(input);
return !!parsed;
})
return !!rule ? { rule, parsed } : null
}

React-Native — Accessing functions outside componentDidMount

I am facing a scope issue when calling a function inside componentDidMount. The function getArrival is defined outside componentDidMount. I tried a few things with no success.
What is the correct way to define getArrival so that I can access it inside the componentDidMount?
Thank you in advance.
Code:
getArrival = (lines) => {
return fetch('https://api.tfl.gov.uk/Line/' + lines + '/Arrivals/' + nearestStopId)
.then((response) => response.json())
.then((arrivalData) => {
}, function() {
// do something with new state
});
console.log(arrivalData);
})
.catch((error) => {
console.error(error);
}
);
}
componentDidMount() {
// Variable to store user data
let userLines = null
// Variable to store nearest stop id
let nearestStopId = null
// Check all variables against the API
for (i = 0; i < apidata.length; i++) {
// Checks user stops id against beacon id
if (userdata[i].bustopId == beaconId) {
// Store match into variable
nearestStopId = userdata[i].bustopId
userLines = userdata[i].buses
// matchStop(nearestStopId)
break // Breaks the loop
}
console.log(userLines)
}
// Data for stops
return fetch('https://api.tfl.gov.uk/StopPoint/' + nearestStopId )
.then((response) => response.json())
.then((stopData) => {
let linesId = []
let selectedLines = []
console.log("linesId at this stop", stopData.lines)
console.log("userlines", userLines)
// Loop through the data in stopData
for (i = 0; i < stopData.lines.length; i++) {
// ... and add all buses id from the API to the array
let currentLine = stopData.lines[i].id
linesId.push(currentLine)
// Checks if user lines are included in current lines ...
if ( userLines.includes(currentLine) ) {
// ... if so, push it into selected lines
selectedLines.push(currentLine)
getArrival(lines) // This gets an error not defined
let lines = selectedLines.toString()
}
}
console.log("selectedLines", selectedLines, "from ", linesId)
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(selectedLines),
}, function() {
// do something with new state
});
})
.catch((error) => {
console.error(error);
}
);
}
I'm going to assume that the code you've shown creating getArrival is inside your class body, e.g.: (you've confirmed that now)
class YourComponent extends React.Component {
getArrival = () => {
// ...
};
componentDidMount() {
// ...
}
// ...
}
If so, that's using the class properties proposal (not yet standard, but at Stage 3) and it creates a property on your instance (like this.getArrival = ...; in your constructor).
So you would access it on this:
this.getArrival(lines)
// ^^^^^
That code is in a callback (or two), but they all appear to be arrow functions, and so they close over this and will use the one componentDidMount was called with (which is the right one).
See this question's answers for why I checked that the callbacks were arrow functions, and what to do in cases where you can't use arrow functions for whatever reason.

Categories

Resources