Update Flatlist when state array change on Functional component - react-native

I have this code below, when the socket event is activated it is supposed to update the list (which is not happening), I've searched everywhere but I couldn't find how to make this list update, can anyone help me?
const [globalMessages, setGlobalMessages] = useState([]);
const chatList = useRef(null);
useEffect(()=>{
socket.on("GlobalMessage", (globalMessageData) => {
console.log(globalMessages.length)
globalMessages.push(globalMessageData)
setGlobalMessages(globalMessages)
});
},[])
<FlatList
ref={chatList}
onContentSizeChange={() => chatList.current.scrollToEnd() }
onLayout={() => chatList.current.scrollToEnd() }
data={globalMessages}
extraData={globalMessages}
keyExtractor={message => message._id}
renderItem={(message) => {
return (
<ChatBubble
text={message.item.text}
from={message.item.user.name}
datetime= {message.item.createdAt}
myId={playerData.id}
msgfromId={message.item.user._id}
containerStyle={styles.msgContainer}
smallTextColor= "#6B6B6B"
msgTextColor= "white"
/>
)
}}
/>

I changed this inside socket.on
setGlobalMessages([])
setGlobalMessages(globalMessages)
and it worked as it should

Related

Updating my data when using FlatList causes "Maximum call stack size exceeded"

I am getting a very annoying Maximum call stack size exceeded. when loading data using <FlatList> and onEndReached. The onRefresh does not cause this. I presume this has to do with the <FeedList> re-rendering when I update the data.
Can someone help me fix this or at least point me in the right direction?
export function useFeedState() {
const state = useState(feedState);
const api = useApi();
function loadPosts(clear = false) {
api
.request<{ posts: IPost[] }>('get', 'posts', { feed: state.feed.value })
.then((data) => {
state.posts.set(
clear ? data.posts : [...state.posts.value, ...data.posts]
);
})
.catch((error) => {
console.log(error);
});
}
// ...
}
Component:
export default function FeedList() {
const feedState = useFeedState();
return (
<FlatList
data={feedState.posts}
renderItem={({ item }) => <PostSlide post={item} />}
keyExtractor={(item) => item.id.toString()}
refreshing={feedState.isLoading}
onRefresh={() => feedState.loadPosts(true)}
onEndReached={() => {
feedState.loadPosts();
}}
/>
);
}
I solved this by using a callback in the set-method. Hookstate is built upon Reacts state and therefor has some similarities. You can read more about when to use a callback here.
// works
state.posts.set((prev) => {
return clear ? data.posts : [...prev, ...data.posts];
});
// also works
clear ? state.posts.set(data.posts) : state.posts.merge(data.posts);
// doesn't work
state.posts.set(
clear ? data.posts : [...state.posts.value, ...data.posts]
);
I guess maybe the reason for the loop was caused by using the value of the same state I was updating.

How can I get dynamically updating Flatlist in react-native?

I am working on a Ble device, I used react-native-ble-manager, useState, and useEffect to get notifications from event listeners and try to show them on App. I have posted a part of the code related to incoming data.
const [dataTest, setDataTest] = useState([]);
const handleUpdateValueForCharacteristic = (data) => {
setDataTest(data.value); // data.value is a object incoming every one second from device.
};
useEffect(() => {
bleManagerEmitter.addListener("BleManagerDidUpdateValueForCharacteristic", handleUpdateValueForCharacteristic);
}, []);
const renderData = (item) => {
return (
<View>
<Text>{item}</Text>
</View>
);
};
return (
<>
<SafeAreaView>
<FlatList
data={dataTest}
renderItem={({ item }) => renderData(item)} />
</SafeAreaView>
</>
);
This code, It showing only the latest events are coming from the device. But I need a list that contains older and new upcoming events so that I can scroll the events data.
You almost got it. Your handleUpdateValueForCharacteristic function is the issue here.
Currently, you are overwriting the previous state (thus forgetting al previous values) by setting the state to new data.value. You want to append incoming data to the existing results, instead of overwriting them.
So you could do something like this:
const [dataTest, setDataTest] = useState([]);
const handleUpdateValueForCharacteristic = (data) => {
const prevData = [...dataTest];
prevData.unshift(data.value); // unshift adds elements to the beginning of an array
// instead of using the incoming data.value, we use the prevData array since we just added the new value to it.
setDataTest(prevData);
};
I asked the same question on Reddit. The solution provided by __o_0 worked for me:
const handleUpdateValueForCharacteristic = useCallback(({value, peripheral, characteristic, service}) => {
const data = bytesToString(value);
setDataTest(prevData => {
return [...prevData, data]});},[])
const renderItem = ({item, index}) => {
return (
<View><Text>{index}</Text></View>);};
const keyExtractor=(item,index,) => \${index}`;`
return (
<>
<SafeAreaView>
<FlatList
data={dataTest}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
</SafeAreaView>
</>
);
};

React Native useEffect confusion

I'm building a project overview app and I'm using React-Native-calendar. I also created two buttons to filter the calendar. I'm fetching the data(API), I'm mapping the data to an object for "markedDates". Everything works appropriately as it should. Now the onPress of each button assigns the object to a state to filter. That works as well. What doesn't work is that those markedDates, that for sure come in correctly, are not shown when the app loads. They are shown however when I click on a button, but not on load. The rough code order:
const [meineTermine, setMeineTermine] = useState([]);
const [dates, setdates] = useState([]);
const [markedFinal, setMarkedFinal] = useState({});
useEffect(() => {
const unsubscribe = db.collection("Dates").onSnapshot(snapshot => (
setdates(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
})))
))
const filter = db.collection("Dates").where("involv", "==", auth.currentUser.displayName).onSnapshot(snapshot => (
setMeineTermine(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
})))
))
return unsubscribe && filter;
}, [])
let markedDayAll = {};
let markedDayMe = {};
{dates.map(({data: {anfang}}) => (
markedDayAll[anfang] = {
selected: true
}
))}
{meineTermine.map(({data: {anfang}}) => (
markedDayMe[anfang] = {
selected: true
}
))}
<View>
<Button onPress={setMarkedFinal(Object.assign({}, markedDayAll))}/>
<Button onPress={setMarkedFinal(Object.assign({}, markedDayMe))}/>
<CalendarList
markedDates={ markedFinal }
onDayPress={() => navigation.navigate("ViewDate")}
/>
</View>
I tried to map the data to objects in useEffect which didn't work. I also tried to have a default value in state, which also didn't work. What am I missing? In which order do I need to set the code up, since it works, just not on load. Where do I need to implement the "setMarkedFinal" so it shows on load?

Render Data in Cloud Firestore with a ReactJS Hook

How can I render data retrieved from Cloud Firestore using ReactJS native functional component?
I want to do something like this:
const result=[]
db.collection("collection").where("parameter", "==", "value")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
result.push(doc.data());
});
export default function foo(){
return(
<View>
{result.map((item) =>
<CustomComponent data=item />
)}
</View>
)
}
Obviously the code does not work because the rendering happens before the promise resolving. I have read a lot on the internet. A possible solution is to update the state in the ComponentDidMount, in which the request to db happens.
So my question is, how can I do it with a functional component? Is this the only and best solution? The data which I want to retrieve from Firestore and I want to display not change so fast.
Hers is a code snippet hope it works for you....
const useData = () => {
const [list, setList] = useState([]);
const [loading, setLoading] = useState(false);
const getData = () => {
const data = [];
db.collection('collection')
.where('parameter', '==', 'value')
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
data.push(doc.data());
});
setList(data);
});
};
return [loading, list, getData];
};
export default function App() {
const [loading, list, getData] = useData();
useEffect(() => {
getData();
}, []);
return (
<View style={styles.container}>
{loading ? (
<ActivityIndicator />
) : (
{
// renderList
}
)}
</View>
);
}
It might be because you are trying to render an array of data. I suggest taking a look at this good example here. I have added a few examples of ReactJS map that you might find helpful:
render() {
return (<div>
{this.state.people.map((person, index) => (
<p key={index}>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
You might find these tutorials and userguides helpful:
Getting started with Cloud Firestore and React Native.
Create React Native Firebase CRUD App with Firestore.

React Native Chat App, Flatlist useRef is null

Im building a chat app with React Native using Expo and I use a Flatlist as child of KeyboardAvoidingView to render the list of messages, the problem is I want to scroll to the bottom when the keyboard is triggered
So I used the Flatlist method ( scrollToEnd ) with useRef hook and my code looks like this :
const ChatBody = ({ messages }) => {
const listRef = useRef(null);
useEffect(() => {
Keyboard.addListener("keyboardWillShow", () => {
setTimeout(() => listRef.current.scrollToEnd({ animated: true }), 100);
});
return () => Keyboard.removeListener("keyboardWillShow");
}, []);
return (
<FlatList
ref={listRef}
keyboardDismissMode="on-drag"
data={messages}
keyExtractor={(item) => item.id || String(Math.random())}
renderItem={({ item }) => <Message {...item} />}
/>
}
The code works just fine at the first render, but when I leave the screen and get back again and trigger the keyboard I get this error :
TypeError : null in not an object (evaluating 'listRef.current.scrollToEnd')
*The reason I added setTimout was because the scrollToEnd for some reason does not work when the keyboard event is triggered. adding setTimeout solved that issue.
The component tree is kinda like this :
StackNavigatorScreen => KeyboardAvoidingView => FlatList
You need to pass your event handler as a second parameter to Keyboard.removeListener. Since you're only passing in the first argument, your handler is run anyway and before your ref could be set.
const ChatBody = ({ messages }) => {
const listRef = useRef(null);
useEffect(() => {
Keyboard.addListener("keyboardWillShow", onKeyboardWillShow);
return () => Keyboard.removeListener("keyboardWillShow", onKeyboardWillShow);
}, []);
function onKeyboardWillShow() {
setTimeout(() => {
listRef.current.scrollToEnd();
}, 100);
}
return (
<FlatList
ref={listRef}
keyboardDismissMode="on-drag"
data={messages}
keyExtractor={(item) => item.id || String(Math.random())}
renderItem={({ item }) => <Message {...item} />}
/>
)
}
const listRef = useRef(null); <-- this line is the one causing the problem.
You need to assign an object, null in this case cannot be put there as it is not an object.