How can I get dynamically updating Flatlist in react-native? - 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>
</>
);
};

Related

React Native component not re-rendering when state is updated

I have a component in my React Native app that displays a list of pending friends. This component makes a GET request to an API to retrieve the list of pending friends and then uses a useEffect hook to map over the list and render each friend as a Pressable component. I'm also using the useFocusEffect hook to make the get request when the screen renders.
Here is the relevant code for the component:
const Pending = () => {
const [pendingFriends, setPendingFriends] = useState(null)
let pendingFriendsRender = []
useEffect(() => {
if (pendingFriends !== null) {
for(let i = 0; i < pendingFriends.length; i++) {
pendingFriendsRender.push(
<Pressable key={i} style={styles.friend}>
<Text style={styles.friendText}>{pendingFriends[i].username}</Text>
</Pressable>
)
}
}
}, [pendingFriends])
useFocusEffect(
useCallback(() => {
async function fetchData() {
const accessToken = await AsyncStorage.getItem('accessToken')
try {
const res = await instance.get('/pending_friends', {
headers: { authorization: `Bearer ${accessToken}`},
})
setPendingFriends(res.data)
} catch (error) {
console.log(error.response.status)
}
}
fetchData()
}, [])
)
return(
<View style={styles.friendsContainer}>
{pendingFriendsRender}
</View>
)
}
I have tried using an empty array as the second argument in the useEffect hook but that approach has not worked. I also tried removing the useEffect hook so the if statement with the for loop stands at the top of the component without the hook, that worked but I can't update it in this way after the component rendered. I checked the API and it is returning the correct data.
The first useEffect you have really isn't needed. You can map through your state inside of your JSX. Anytime the state changes, the component will be re-rendered:
// Need a default here, could also set some loading state when fetching your data
if(pendingFriends === null) {
return <>Loading...</>
}
return(
<View style={styles.friendsContainer}>
{pendingFriends.map((friend, i) => {
return (
<Pressable key={friend.id} style={styles.friend}>
<Text style={styles.friendText}>{friend.username}</Text>
</Pressable>
)
})}
</View>
)
Also keep in mind, it's not recommended to use the index as the key, it can lead to unexpected bugs and issues. Instead use a unique string key (as shown above).
React: using index as key for items in the list
pendingFriendsRender should be the state:
const [pendingFriendsRender, setPendingFriendsRender] = useState([])
Instead of
let pendingFriendsRender = []
Then just clone the array so you lose reference to the object and add the new element
const newPendingFriendsRender = [...pendingFriendsRender, newElement]
or you can use FlatList to make it easier.

Why my input closes when new Component is showing?

I have a a Header there is a text input and I have a Main component there are the list of products of the searched text.
So If the input text is empty then I want to show him his last searched things. If he type anything then I want to show him the products with the same name as the input text. I make a condition like this:
const SearchRoot = ({ }: ISearchRoot) => {
const searchParam = useSelector((state: RootState) => state.Search.searchParam);
return (
<>
{ searchParam.length > 0 ?
<SearchList />
:
<LatestHistory />
}
</>
)
}
So if I type anything then my input are closing automatically. But if I press again the keyboard and typing then its not closing and working. So its only happend when the component changing from <LatestHistory to <SearchList only one time. So how can I make my keyboard always open when the component is chaning ?
Search
const Search = () => {
return (
<View style={s.container}>
<StatusBar backgroundColor='#fff' />
<SearchHeader />
<SearchRoot />
</View>
)
}
SearchHeader
const SearchHeader = () => {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParams>>();
const dispatch = useDispatch();
// filters
const [searchText, setSearchText] = useState<string>('');
const handleChangeText = (e: string) => {
dispatch(setSearch({searchParam: e}));
setSearchText(e)
};
const handleClearText = () => {
dispatch(setSearch({searchParam: ''}));
setSearchText('');
};
const handlePressSeach = () => {
searchText.length > 0 &&
navigation.navigate('Searched', {
searchText,
searchType: tabType
});
};
const handleGoBack = () => navigation.goBack();
return (
<View style={s.container}>
<View style={s.header}>
<View style={s.backContainer}>
<GoBackIcon
onPress={handleGoBack}
color='#555'
/>
</View>
<View style={s.inputContainer}>
<SearchInput
value={searchText}
onChangeText={handleChangeText}
onPressSearched={handlePressSeach}
onPressClearTextField={handleClearText}
autoFocus={true}
style={s.sInput}
/>
</View>
</View>
</View>
)
}
SearchRoot
const SearchRoot = ({ }: ISearchRoot) => {
const searchParam = useSelector((state: RootState) => state.Search.searchParam);
return (
<>
{ searchParam.length > 0 ?
<SearchMain />
:
<Text>Vorschläge</Text>
}
</>
)
}
I am very thankful for your help!!
I just read your problem statement and matched it with your Code.
✦First of all your sequence is wrong.
✦First Comes the Previous Searched Result.
✦Then when you press it then it should clear the previous search result.
✦Then On text change Property
✦Then OnPress Searched
Maybe that's Why it closes. If that's Not the case then!
(In my Opinion)
when then you click the search bar the previously searched result disappears and this is done by the function handleClearText. Now When the Function Completes it's Functionality the search bar closes. The Second time you open the search bar it works as their's no Previous Search result to clear.
So the Problem is with the handleClearText function.
My Proposed Solution in case 2 is
We can call the search method again (at the end) of handleClearText function.
then it will re open the search bar with the new state.
I hope it resolves your issue, Regards...
first of all const [searchText, setSearchText] = useState(''); this whole logic move to parent class search and from there pass it to siblings to avoid any confusion.whenever re-rendering happens values will be passed correctly to child.I think somewhere that is going wrong.Either use state and prop drilling or store, dont mix it up.

Update Flatlist when state array change on Functional component

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

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.

Problem saving state with set in react hook when loading data from an API?

My idea is that when someone put a text in my TextInput and clicks submit then botToc() will save the data from that API search in result and show the result in a FlatList.
But I have a problem saving data with hooks. The thing is that my setResult is not working properly because it does not save well the data from the API query. botToc() is supposed to fetch data from an API and its doing that fine but then I loop through the fetched data and I save what I want in result using setResult.
The thing is that when I click the button that uses botToc2() ( after clicking the button that uses botToc() ) my console.log(result) shows only the data of the last element and if I use again botToc() and I click one more time botToc2() I get that last element duplicated in the result array.
How can I fix this?
export default function TestScreen () {
const [querypar, setQuerypar] = useState('');
const [apipar, setApipar] = useState('https://API/search?q=');
const [result, setResult] = useState([]);
const ItemView = ({item}) => {
return (
<View>
<Text>
{item[0]+ ' ' + item[1]}
</Text>
</View>
);
};
function botToc() {
let query = apipar+querypar; //'https://API/search?q='+'TextInputText'
let cargador = [];
fetch(query) //'https://API/search?q=TextInputText'
.then( res => res.json() )
.then( res => {
// (res.results) = [{price:x,title:x},{price:x,title:x},{price:x,title:x}] structure of (res.results)
(res.results).forEach( (element) => {
cargador.push(element.title);
cargador.push(element.price); //cargador=[x,x]
setResult([...result, ...cargador]); //result=[[x,x],[x,x]...]
cargador.length = 0; //cargador=[]
});
})
};
function botToc2() {
console.log(result); //my console should return [[x,x],[x,x],[x,x],[x,x],[x,x],[x,x],...]
};
return (
<View View style={styles.container}>
<TextInput placeholder="write here" onChangeText={(val) => setQuerypar(val)} />
<View>
<Button onPress={botToc} title="Submit"/>
<Button onPress={botToc2} title="Submit"/>
<FlatList
data={result}
renderItem={ItemView}
keyExtractor={(item, index) => index.toString()}
/>
</View>
</View>
);
};
Calling setResult() multiple times in the forEach function should be avoided. In that case your botToc() function should look like this:
function botToc() {
let query = apipar+querypar; //'https://API/search?q='+'TextInputText'
let cargador = [];
fetch(query) //'https://API/search?q=TextInputText'
.then( res => res.json() )
.then( res => {
// (res.results) = [{price:x,title:x},{price:x,title:x},{price:x,title:x}] structure of (res.results)
(res.results).forEach( (element) => {
cargador.push([element.title, element.price]); //cargador=[x,x]
});
})
setResult(cargador);
};
This should do the job for you.