Not getting updated state value in socket callback react native - javascript

I have listend an event in customhook and when that event works, I have to do some logic there with state.but now I only get empty array every time that event callback works.
const useChatHistoryList = () => {
const sk = useSocket();
const [chatList, setChatList] = useState([]);
const [end, setEnd] = useState(true);
useEffect(() => {
sk.emit('chatlist');
}, [start]);
useEffect(() => {
const onChatListReceived = data => {
const _data = JSON.parse(data);
setHistoryLoading(false);
setChatList(_data);
};
const onChatListToUpdateReceived = data => {
const _data = JSON.parse(data);
console.log(chatList);//getting only empty array everytime
};
sk.on('chatlist', onChatListReceived);
sk.on('chatlistToUpdate', onChatListToUpdateReceived);
return () => {
sk.off('chatlistToUpdate');
sk.off('chatlist');
};
}, []);
return { chatList,end};
};

Try to log your data first to make sure the data is there, then set your state with the data.
const [state, setState]= useState([]);
const _onReceived = (data) => {
// Here is your data from socket
console.log(data);
// Then set state value with data
setState(data);
}
useEffect(()=>{
// Init socket listener
socket.on("event", _onReceived);
}, []);
// This effect will runs everytime state value is set (including when setting default value)
useEffect(()=>{
// Actual 'state' value
console.log('State value: ', state);
}, [state]);
==========================
Edit, related to your updated codes in the question
Your onChatListToUpdateReceived function brings empty default value to the listener even later when it’s updated, your listener will still recognize chatList value as an empty string. Try to move out onChatListToUpdateReceived outside useEffect.
const onChatListToUpdateReceived = data => {
const _data = JSON.parse(data);
console.log(chatList);//getting only empty array everytime
};
useEffect(() => {
const onChatListReceived = data => {
const _data = JSON.parse(data);
setHistoryLoading(false);
setChatList(_data);
};
sk.on('chatlistToUpdate', onChatListToUpdateReceived);
return () => {
sk.off('chatlistToUpdate');
sk.off('chatlist');
};
}, []);
useEffect(() => {
sk.off('chatlistToUpdate');
sk.on('chatlist', onChatListReceived);
}, [chatList]);

I have not used socket.io before but this is what I meant by asynchronous update. From your code, it looked to me like your callback is getting called before the state is updated. So to solve this, I added a useEffect() with chatList as a dependency so that callback gets called every time chatList gets updated. I hope this makes sense.
const useChatHistoryList = () => {
const sk = useSocket();
const [chatList, setChatList] = useState([]);
const [end, setEnd] = useState(true);
const onChatListReceived = data => {
const _data = JSON.parse(data);
setHistoryLoading(false);
setChatList(_data);
};
const onChatListToUpdateReceived = data => {
const _data = JSON.parse(data);
console.log(chatList); //getting only empty array everytime
};
useEffect(() => {
sk.on('chatlist', onChatListReceived);
sk.on('chatlistToUpdate', onChatListToUpdateReceived);
return () => {
sk.off('chatlistToUpdate');
sk.off('chatlist');
};
}, []);
// Emit chatlistToUpdate whenever chatList is updated
useEffect(() => {
sk.emit('chatlistToUpdate');
}, [chatList]);
return {
chatList,
end
};
};

Related

Custom react hook initial value return null, how prevent that

I have custom react hook but it always return null for the first render and only second time is needed value
const useGetUrlParams = (params) => {
const [data, setData] = useState(null);
useEffect(() => {
if (params === null) {
return '';
}
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const res = urlParams.get(params);
setData(res);
}, [params]);
return [data];
};
how is that possible to prevent and return the correct value only once from the first attempt?
I have no idea what to do with that.
First, we must ask where the data that we enter into the state UI is coming from. How long will it take to get them? From these, we will know when data will come in.
Your example can be implemented as follows:
const useGetUrlParams = (params) => {
const getData = (params) => {
if (params === null) {
return '';
}
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const res = urlParams.get(params);
return res;
}
const [data, setData] = useState(getData(params));
useEffect(() => {
const data = getData(params);
setData(data);
}, [params]);
return [data];
};
The value of the state is entirely dependent on how and when the other parameters can adapt to it. In example: If in the first render, params is valid, you can get expected state's value else you must be get unexpected state's value, no solution in this case.
Extract the code to a function and use it for the useState as well.
You can use the lazy initial state approach to avoid re-running an expensive computation each time your hook is run, and also use a flag (useRef) to not run the same code in the useEffect on the first execution.
function getParams(params) {
if (params === null) {
return '';
}
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const res = urlParams.get(params);
return res;
}
const useGetUrlParams = (params) => {
// use the isFirstRun trick
// to avoid calling re-running the
// getParams and setting the state in the
// useEffect on the first run (since we already
// calculate it when initialising the useState)
const isFirstRun = useRef(true);
// use the lazy initial state setting
const [data, setData] = useState(() => getParams(params));
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
} else {
setData(getParams(params));
}
}, [params]);
return [data];
};

How to get each item from a filtered array, without using a map for each item

I'm taking an array and filtering the value in a context:
const { responsible } = useResponsible()
const [ids, setIds] = useState([])
const filteredResponsible = responsible?.filter((resp) =>
ids.includes(resp.id)
)
The problem is that I need to make a map to get the corresponding value of each id, one by one. This ends up making the code too long:
const { filteredResponsible } = useResponsible
const responsibleName = filteredResponsible.map((resp) => resp.name)
const responsibleEmail = filteredResponsible.map((resp) => resp.email)
const responsibleAddress = filteredResponsible.map((resp) => resp.address)
...
And so on with each item in the array.
I'm using the React Hook Form's setValue to set the value in the inputs:
useEffect(() => {
setValue('name', `${responsibleName}`)
setValue('email', `${responsibleEmail}`)
setValue('address', `${responsibleAddress}`)
setValue('cep', `${responsibleCep}`)
setValue('district', `${responsibleDistrict}`)
setValue('city', `${responsibleCity}`)
setValue('state', `${responsibleState}`)
setValue('phone', `${responsiblePhone}`)
setValue('sex', `${responsibleSex}`)
}, [])
How can I make these maps smaller? Without having to make a map to get each item in the array?
There doesn't seem to be any reason to do those map calls on every render and to do them anywhere other than where you need them, since you only show using the result in a mount-only effect. Just do them there:
const { filteredResponsible } = useResponsible; // Is there really no `()` needed here?
useEffect(() => {
setValue("name", `${filteredResponsible.map(({name}) => name)}`);
setValue("email", `${filteredResponsible.map(({email}) => email)}`);
setValue("address", `${filteredResponsible.map(({address}) => address)}`);
// ...
}, []);
If you really need those distinct arrays on every render, unless you can change your data structures to be more amenable to your output I don't see you have a lot of options. You can at least avoid multiple loops through filteredResponsible:
const { filteredResponsible } = useResponsible; // ()?
const responsibleName = [];
const responsibleEmail = [];
const responsibleAddress = [];
for (const { name, email, address } of filteredResponsible) {
responsibleName.push(name);
responsibleEmail.push(email);
responsibleAddress.push(address);
}
And if that's really the case, you may want to avoid doing it on every render:
const { filteredResponsible } = useResponsible; // ()?
const { responsibleName, responsibleEmail, responsibleAddress } = useMemo(() => {
const responsibleName = [];
const responsibleEmail = [];
const responsibleAddress = [];
for (const { name, email, address } of filteredResponsible) {
responsibleName.push(name);
responsibleEmail.push(email);
responsibleAddress.push(address);
}
return { responsibleName, responsibleEmail, responsibleAddress };
}, [filteredResponsible]);

firebase data not being updated with use effect

hi i have to refresh my page to see the effect of the person adding an event to the calendar:
my code is
const handleDateClick = async (DateClickArg) => {
if (DateClickArg) {
const title = prompt("Enter title", DateClickArg.dateStr); // allows user to put a title in
// making object
const event = {
title: title ? title : DateClickArg.dateStr,
start: DateClickArg.date,
allDay: true,
};
allEvents.push(event);
const db = fire.firestore();
let currentUserUID = fire.auth().currentUser.uid;
const doc = await fire
.firestore()
.collection("userCalendar")
.doc(currentUserUID)
.get();
db.collection("userCal/" + currentUserUID + "/activities").add({ event });
}
};
and my getuserinfo is:
const getUserInfo = async () => {
let currentUserUID = fire.auth().currentUser.uid;
const qSnap = await fire
.firestore()
.collection("userCal")
.doc(currentUserUID)
.collection("activities")
.get();
const data = [];
data = qSnap.docs.map((d) => ({
id: d.id,
title: d.data().event.title,
start: d.data().event.start.toDate(),
allDay: d.data().event.allDay,
...d.data(),
}));
//setData(data)
console.log(data);
setData([...data]);
};
useEffect(() => {
let mounted = false;
if (!mounted) {
getUserInfo();
}
return () => {
mounted = true;
};
}, []);
where am i going wrong with my use effect? is there a way for the data to update in the browser once its added to firebase? i am using react full calendar
Using get() only returns a point-in-time snapshot of your data. If you want to listen for realtime updates, use .onSnapshot() instead.
You'll also need to make sure you unsubscribe from updates when your component is cleaned up
useEffect(() => {
const currentUserUID = fire.auth().currentUser.uid;
return fire
.firestore()
.collection("userCal")
.doc(currentUserUID)
.collection("activities")
.onSnapshot(({ docs }) => {
setData(
docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
title: data.event.title,
start: data.event.start.toDate(),
allDay: data.event.allDay,
...data,
};
})
);
});
}, []);
.onShapshot() returns an unsubscribe function so returning that from your effect hook will run it when your component is unmounted.
Assuming your firebase call is ok, there is an error inside your useEffect call. You are setting the mounted variable wrong, it is supposed to be false when your component is destroyed and true after your component is rendered. Also, to avoid unexpected behaviors I highly recommend using the useRef hook to check that.
function Component() {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
if (isMounted) {
getUserInfo();
}
return () => { isMounted.current = false }
}, []);
...
}
export default Component;

(React + Firestore) Component executing before Context authentication?

I have a problem with one of my components. The problem I think I have is that my component executes before my user context stores the currentUser. My code only works when doing a hot reload.
The watchlist component gets all the values from the watchlist array where the document matches the currentUser.uid.
UserContext.js:
const [currentUser, setCurrentUser] = useState(null)
const [watchlist, setWatchlist] = useState(null)
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
})
return unsubscribe
}, [])
const getWatchlist = async () => {
const userRef = await getDoc(doc(db, 'users', currentUser.uid))
setWatchlist(userRef.data().watchlist)
console.log(userRef.data().watchlist)
}
These values are the ids of objects I then GET from an API, these are then pushed to the watchlistData array.
CryptoContext.js
export const getWatchlistData = async (list) => {
const watchlistData = []
for (const item of list) {
const result = await axios.get(
`${coingecko}/coins/${item}`
)
watchlistData.push(result.data)
}
return watchlistData
}
And this is how my Watchlist component code currently looks.
WatchlistItems.jsx
const { watchlist, getWatchlist, currentUser } = useContext(UserContext)
const { dispatch } = useContext(CryptoContext)
useEffect(() => {
if (currentUser) {
dispatch({type: 'SET_LOADING'})
const getWatchlistDataFromAPI = async () => {
await getWatchlist()
const watchlistData = await getWatchlistData(watchlist)
dispatch({type: 'GET_WATCHLIST', payload: watchlistData})
console.log(watchlistData)
}
getWatchlistDataFromAPI()
}
}, [currentUser])
If I refresh the page I get "Uncaught (in promise) TypeError: the list is not iterable", but if I do a hot reload, watchlist, and watchlistData both console.log with the correct data.
This is my first post and so please let me know if I've left anything out.
Thank you in advance for any help :)

Why the default value variable change as the changed variable value, Vuejs

as you see the code, on the handleUpdateFilter function the second "if" some how defaultCourseData is filtered as filteredData of the first "if". Thank you for helping me!
setup() {
const course = ref();
const defaultCourseData = null
const gettingCourse = async () => {
const { data } = await getCourse();
defaultCourseData = data
course.value = data;
};
const handleUpdateFilter = (data) => {
// data is filtering value
if (data.value.view) {
const filteredData = defaultCourseData.sort((a, b) => b.luotXem - a.luotXem);
course.value = filteredData;
}
if (!data.value.view) {
course.value = defaultCourseData // This case some how defaultCourseData filtered too
}
};
onMounted(() => {
gettingCourse();
});
return {
course,
handleUpdateFilter,
defaultCourseData
};
},
Your defaultCourseData variable isn't reactive.
Therefore it should be evaluated as null at every call.
Try this
defineComponent({
setup() {
const course = ref([]);
const defaultCourseData = ref([]);
const gettingCourse = async () => {
const { data } = await getCourse();
defaultCourseData.value = data
course.value = data;
};
const handleUpdateFilter = (data) => {
// data is filtering value
if (data.value.view) {
course.value = defaultCourseData.value.sort((a, b) => b.luotXem - a.luotXem);
}
if (!data.value.view) {
course.value = defaultCourseData.value // This case some how defaultCourseData filtered too
}
};
onMounted(async () => {
await gettingCourse();
});
return {
course,
handleUpdateFilter,
defaultCourseData
};
})
Edit: The actual issue here was, that the defaultCourseData always returned a sorted array as Array.prototype.sort() mutates the Array.
So making a copy solves the issue.
if (data.value.view) { course.value = [...defaultCourseData.value].sort((a, b) => b.luotXem - a.luotXem); }

Categories

Resources