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.
Related
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]);
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, sorry for this weird title, i didn't know how to put this... Here my explanation:
I have a function where I define variable (I'm looping over an array and if condition is matched, I define let). It works everytime item.dataset.category changes, so after every scroll.
After the loop, I call another function, where I use this variable as an argument. In the second function I use it to check if another condition is matched:
//first function
const getDataset = (e) => {
let dataset;
const cat = Array.from(categories.children);
cat.forEach((item) => {
if (item.className.includes('active')) {
dataset = item.dataset.category;
}
});
changeNavActive(dataset);
};
//second function
const changeNavActive = (dataset) => {
const navItems = Array.from(navList.children);
navItems.forEach((item) => {
item.classList.remove('active');
if (item.dataset.category === dataset) {
item.classList.add('active');
}
});
};
It's not working and I think I understand why - callign of second function is at the same time as declaring variable, so I geting this let in the next call. The result is that second function works with delay of one scroll.
This is a function which calls getDataset():
const scrollRows = (e) => {
if (window.scrollY > slider.clientHeight) {
e.deltaY > 0 ? move++ : move--;
getDataset();
if (move > categories.children.length - 1)
move = categories.children.length - 1;
}
}
How to fix this?
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I modified my question to be more specific. now i don't care about the desired behavior and i just need to correct syntax error
I was studying this tutorial I face with an error in this code.
severity: 'Error'
message: 'Property 'offset' does not exist on type 'PagerserviceProvider'.'
actually i have the same error for this three variables.
that.pageSize,that.offset,that.size
public async getPager(tableName:string,pageSize: number = 10) {
let pageSize = pageSize;
let offset = 0;
let limit = pageSize;
let size = await this.getTotal(tableName);
let that = this;
return {
initialPage:function(){
return new Promise((resolve,reject)=>{
var d = [];
that.executeSql(tableName,limit,offset).then((data)=>{
console.log(JSON.stringify(data));
for(var i = 0 ; i < data.rows.length ; i++)
{
d.push(data.rows.item(i));
}
resolve(d);
},(e)=>{
reject(e);
});
});
},
nextPage:function(){
if(that.offset <= that.size - that.pageSize )
{
that.offset += that.pageSize;
}
return new Promise((resolve,reject)=>{
var d = [];
that.executeSql(tableName,limit,offset).then((data)=>{
for(var i = 0 ; i < data.rows.length ; i++)
{
d.push(data.rows.item(i));
}
resolve(d);
},(e)=>{
reject(e);
});
});
}
};}
When you use the keyword function to declare a function, the function's this does not refer to the upper this. so using this in the function body, refers to the function itself.
The problem you are facing is related to the fact that your function is declared inside a class which already have a this defined, so you need a way to reference the upper this while being inside the nested function.
class Test {
hello () { console.log('hello') }
method () {
this.hello() // It will work because `this` refers to the class
function sayHello () {
return this.hello()
// it won't work because `this` refers to the function sayHello
}
return sayHello()
}
}
To bypass this limitation, you can save your upper this in a variable while your code is in the upper scope. This variable is usually called that or self.
class Test {
hello () { console.log('hello') }
method () {
var that = this // that is now refering to the class
this.hello() // It will work because `this` refers to the class
function sayHello () {
return that.hello()
// that is still refering to the class so it will succeed
}
return sayHello()
}
}
EDIT:
another trick to avoid using that is to use ES6 arrow function. Inside an arrow function, this alway refers to the upper scope.
class Test {
hello () { console.log('hello') }
method () {
this.hello() // It will work because `this` refers to the class
// `this` refers to the upper scope by default so it works
const sayHello = () => this.hello()
return sayHello()
}
}
EDIT 2:
Your code should be:
public async getPager(tableName: string, pageSize: number = 10) {
let pageSize = pageSize;
let offset = 0;
let limit = pageSize;
let size = await this.getTotal(tableName);
let that = this;
return {
initialPage: function () {
return new Promise((resolve,reject)=>{
var d = [];
that.executeSql(tableName, limit, offset).then(data => {
console.log(JSON.stringify(data));
for(var i = 0 ; i < data.rows.length ; i++) {
d.push(data.rows.item(i));
}
resolve(d);
}, e => {
reject(e);
});
});
},
nextPage: function () {
if(offset <= size - pageSize ) {
offset += pageSize;
// no need to use `that` because you used `let`
}
return new Promise((resolve, reject) => {
var d = [];
that.executeSql(tableName, limit, offset).then(data => {
for(var i = 0 ; i < data.rows.length ; i++) {
d.push(data.rows.item(i));
}
resolve(d);
}, e => {
reject(e);
});
});
}
};
}
'that' is just the name of a variable used to store the original value of 'this' at the start of your code, as the value of 'this' changes. The variable name could just as easily be 'dog' or 'apple'.
You might choose to use 'this' later on in your code instead if you intend to access the current value of 'this' at that point in time. Otherwise you will probably reference your original variable that stored it's value, e.g. 'that', 'dog' or 'apple'.
getPager is a method: If you call it with an already lost context, that will get the current this value, which is not pointing the correct context.
const someInstance = new SomeClass()
someInstance.getPager() // You call getPager directly from the someInstance context
someHTMLButton.onClick = someInstance.getPager // Here the getPager method lost its context
A solution is to bind getPager to someInstance. This way it will always have its context this pointing to someInstance.
someHTMLButton.onClick = someInstance.getPager.bind(someInstance)
I currently initialize my state like this:
getInitialState: function () {
return {
conversations: {},
messages: {},
availableConversations: {}
}
},
If I do something like:
convosRef.on('value', snapshot => {
this.setState({ conversations: snapshot.val() });
});
It works as desired... however:
users: {
uid: {
conversations: {
conversationid: true,
conversationid2: true
}
}
}
I successfully get the desired object:
userSnapshot.child('conversations').forEach(function (conversationKey) {
var conversationRef = database.ref('conversations').child(conversationKey.key);
conversationRef.on('value', function (conversationsSnapshot) {
var conversation = conversationsSnapshot.val();
conversationLoadedCount = conversationLoadedCount + 1;
myObject[conversationKey.key] = conversation;
// console.log(conversationKey.key);
//this.state.conversations = conversation;
if (conversationLoadedCount === conversationCount) {
console.log("We've loaded all conversations");
}
});
this.setState({ conversations: myObject });
});
});
However. I receive two errors and I can't affect the state:
First error:
`FIREBASE WARNING: Exception was thrown by user callback. TypeError: Cannot read property 'setState' of null``
And slightly similar:
Uncaught TypeError: Cannot read property 'setState' of null
I based my code on this excellent answer with no success whatsoever:
Firebase two-way relationships / Retrieving data
This is running inside the componentDidMount function.
Any suggestions on how to affect the state?
In ES6 if you use the fat arrow function this would be much cleaner and "this" would be bound, For example:
userSnapshot.child('conversations').forEach((conversationKey) => {
var conversationRef = database.ref('conversations').child(conversationKey.key);
conversationRef.on('value', (conversationsSnapshot) => {
I found the solution to my problem just by trying to bind it.
The solution is to use bind(this) whenever I do a call on the firebase.database().ref().
Here is the final snippet, hope it helps a poor soul like me and if someone has a better answer or some explanation on how to improve this please let me know:
var myObject = {};
usersRef.on('value', function (userSnapshot) {
// First we get the qty of conversations.
var conversationCount = userSnapshot.child('conversations').numChildren();
// we initialize an empty counter
var conversationLoadedCount = 0;
// we traverse now the conversations ref with the past conversations.
userSnapshot.child('conversations').forEach(function (conversationKey) {
var conversationRef = database.ref('conversations').child(conversationKey.key);
conversationRef.on('value', function (conversationsSnapshot) {
var conversation = conversationsSnapshot.val();
conversationLoadedCount = conversationLoadedCount + 1;
myObject[conversationKey.key] = conversation;
// console.log(conversationKey.key);
this.state.conversations[conversationKey.key] = conversation;
this.setState({ conversations: this.state.conversations });
if (conversationLoadedCount === conversationCount) {
console.log("We've loaded all conversations");
}
}.bind(this)); // one for the usersRef
}.bind(this)); // another one for the forEach inside
}.bind(this)); // the final one for the conversationRef.on...