React native retrieving data in real time with firebase - javascript

i'm retrieving a document collection as this
const retrieveCollection = async () => {
let questionDocs: Questions[] = [];
const questionCol = collection(db, "questionsdb");
const colQuery = query(questionCol);
const querySnapshot = await getDocs(colQuery);
querySnapshot.forEach((doc: DocumentData) => questionDocs.push(doc.data()));
setQuestions(questionDocs);
};
useEffect(() => {
retrieveCollection();
}, []);
Each question has an author, when i click a button i want it to show just my questions, if i click it again, it will show all the other questions. This is my function to do it
const [myQuestions, setMyQuestions] = useState(false);
useEffect(() => {
const showMyQuestions = async () => {
await retrieveCollection();
console.log(questions);
const myQuestionFilter = questions?.filter(
(item) => item.author.uid === user?.uid
);
setQuestions(myQuestionFilter!);
};
const showAllQuestions = async () => {
await retrieveCollection();
const myQuestionFilter = questions?.filter(
(item) => item.author.uid !== user?.uid
);
setQuestions(myQuestionFilter!);
};
myQuestions ? showMyQuestions() : showAllQuestions();
}, [myQuestions]);
And this is the button
<TouchableOpacity onPress={() => setMyQuestions(!myQuestions)}>
<Text>
{myQuestions ? "My Questions" : "All Questions"}
</Text>
</TouchableOpacity>
I only have questions asked by myself, so when MyQuestions is true i should see all questions, and when it's false i should see nothing. But once everything disappears it doesn't comeback. Before i filter the questions array, i call the function retrieveCollection to get all questions and then filter it.

You only fetch all your data when the component mounts and store it in questions. Its value is further manipulated by myQuestions: when true filters questions for items you've posted, and when false filter for ones not posted by you. If you toggled it twice, then you would delete everything in questions and require another api call to be made.
To avoid this, you should have another variable for myQuestions to manipulate, which is used to render the visible questions:
// update your JSX to use this variable
const [visibleQuestions, setVisibleQuestions] = useState(questions || []);
return (
{/*
.
.
.
*/}
<TouchableOpacity
onPress={() => {
const newVal = !myQuestions;
setMyQuestions(newVal);
const myQuestionFilter = questions?.filter((item) =>{
const isMyPost = item.author.uid === user?.uid
return newVal ? isMyPost : !isMyPost
);
setVisibleQuestions(myQuestionFilter)
}}
}>
<Text>
{myQuestions ? "My Questions" : "All Questions"}
</Text>
</TouchableOpacity>
)
Doing it this way you arent actually deleting the data sent from firebase and thus you wont have to retrieve it again

Related

React-native application hanging when updating a parent from a child

I've a weird behavior here.
I'm trying to update a parent component from a child.
I've thus something like this for the child:
const LabelList = ({editable, boardLabels, cardLabels, size='normal', udpateCardLabelsHandler}) => {
return (
<DropDownPicker
labelStyle={{
fontWeight: "bold"
}}
badgeColors={badgeColors}
showBadgeDot={false}
items={items}
multiple={true}
open={open}
onChangeValue={(value) => udpateCardLabelsHandler(value)}
value={value}
setOpen={setOpen}
setValue={setValue} />
)
}
And, for the parent, something like this:
const CardDetails = () => {
const [updatedCardLabels, setUpdatedCardLabels] = useState([])
const [card, setCard] = useState({})
const [editMode, setEditMode] = useState(false)
// Handler to let the LabelList child update the card's labels
const udpateCardLabelsHandler = (values) => {
const boardLabels = boards.value[route.params.boardId].labels
const labels = boardLabels.filter(label => {
return values.indexOf(label.id) !== -1
})
console.log('updated labels',labels)
setUpdatedCardLabels(labels)
}
return (
<View style={{zIndex: 10000}}>
<Text h1 h1Style={theme.title}>
{i18n.t('labels')}
</Text>
<LabelList
editable = {editMode}
boardLabels = {boards.value[route.params.boardId].labels}
cardLabels = {card.labels}
udpateCardLabelsHandler = {udpateCardLabelsHandler} />
</View>
)
And, this just doesn't work: As soon as I try changing something in the DropDownPicker the application hangs. The console.log statement isn't even executed and no errors show up in my expo console.
What's strange is that if I change the updateCardLabels state to be a boolean for example, everything works ok (eg: the console.log statement is executed):
const [updatedCardLabels, setUpdatedCardLabels] = useState(false)
// Handler to let the LabelList child update the card's labels
const udpateCardLabelsHandler = (values) => {
const boardLabels = boards.value[route.params.boardId].labels
const labels = boardLabels.filter(label => {
return values.indexOf(label.id) !== -1
})
console.log('updated labels',labels)
setUpdatedCardLabels(true)
}
Please note that updatedCardLabels isn't used anywhere: it's a dummy variable that I'm just using to debug this issue (to make sure I was not ending in some endless render loop or something similar).
For the sake of completeness, here's what labels looks like at line console.log('updated labels',labels) (please not that I can only see this value when doing setUpdatedCardLabels(true) as otherwise, when the code does setUpdatedCardLabels(labels), the console.log statement is not executed, as mentioned earlier):
updated labels Array [
Object {
"ETag": "a95b2566521a73c5edfb7b8f215948bf",
"boardId": 1,
"cardId": null,
"color": "CC317C",
"id": 9,
"lastModified": 1621108392,
"title": "test-label",
},
]
Does anybody have an explanation for this strange behavior?
Best regards,
Cyrille
So, I've found the problem: It was a side effect of the DrowpDownPicker.
I've solved it by changing my child as follow:
const LabelList = ({editable, boardLabels, cardLabels, size='normal', udpateCardLabelsHandler}) => {
const [open, setOpen] = useState(false);
const [value, setValue] = useState(cardLabels.map(item => item.id));
const theme = useSelector(state => state.theme)
// Updates parent when value changes
useEffect(() => {
if (typeof udpateCardLabelsHandler !== 'undefined') {
udpateCardLabelsHandler(value)
}
}, [value])
return (
<DropDownPicker
labelStyle={{
fontWeight: "bold"
}}
badgeColors={badgeColors}
showBadgeDot={false}
items={items}
multiple={true}
open={open}
value={value}
setOpen={setOpen}
setValue={setValue} />
)

Firebase only fetches data on second or third attempt

I'm building an app with React and Firestore.
In one feature, I need to use some specific user data to query data from another collection, and show that on the app.
Specifically I want to use users.books, which returns an array, to query the collection books.
However, for some reason the users.books doesn't load on first render. It typically takes 2-3 renders to fetch the books.user data. This is despite the currentUserUID being loaded right away.
I've tried using a loading state as specified in How to wait for Firebase data to be fetched before progressing?, but to no avail.
Do I need to use the onSnapShot method?
Thanks for reading
My code
import 'firebase/firestore'
import { booksRef} from '../../App';
const ProfileScreen = ({ navigation }) => {
const currentUserUID = firebase.auth().currentUser.uid;
const [firstName, setFirstName] = useState('');
const [userBookTitles, setUserBookTitles] = useState([]);
const [userBooks, setUserBooks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function getUserInfo(){
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
setFirstName(dataObj.firstName)
setUserBookTitles(dataObj.books)
console.log(userBookTitles)
}
}
getUserInfo();
}, [])
useEffect(() => {
async function getUserBooks() {
booksRef.where("title", "in", userBookTitles).onSnapshot(snapshot => (
setUserBooks(snapshot.docs.map((doc) => ({id: doc.id, ...doc.data()})))
))
}
setLoading(false);
getUserBooks()
}, [])
if (!loading) {
return (
<View style={styles.container}>
<Text> Hi {firstName} </Text>
<TouchableOpacity onPress={handlePress}>
<Text> Log out </Text>
</TouchableOpacity>
<Row
books={userBooks}
/>
</View>
);
} else {
return (
<View style={styles.container}>
<Text> Test </Text>
</View>
);
}
};
So it's worth noting that your setX methods may, or may not, complete in the sequence you have them in your code. Therefore, your booksRef call could be being made even though userBookTitles is an empty array. Which would explain why you're not getting any data on load.
You're setting userBookTitles in your first useEffect and the only other place I see you're using it is in your booksRef call. One easy fix would be to simple move booksRef inside the else statement of the first useEffect and simply pass it the userBookTitles there. This should help in solving your issue, if I understood it correctly.

useEffect re-render different values making impossible build setting screen using async storage

i'm quite new to react-native, i'm trying to implementing a setting screen in my recipe search app. basically the user can choose different filter to avoid some kind of food (like vegan or no-milk ecc.), i thought to make an array with a number for each filter and then in the search page passing the array and apply the filter adding piece of strings for each filter. the thing is: useEffect render the array i'm passing with async-storage empty on the first render, it fulfill only on the second render, how can i take the filled array instead of the empty one?
const [richiesta, setRichiesta] = React.useState('');
const [data, setData] = React.useState([]);
const [ricerca, setRicerca] = React.useState("");
const [preferenza, setPreferenza] = React.useState([]);
let searchString = `https://api.edamam.com/search?q=${ricerca}&app_id=${APP_ID}&app_key=${APP_KEY}`;
useEffect(() => {
getMyValue();
getPreferences(preferenza);
},[])
const getMyValue = async () => {
try{
const x = await AsyncStorage.getItem('preferenza')
setPreferenza(JSON.parse(x));
} catch(err){console.log(err)}
}
const getPreferences = (preferenza) => {
if(preferenza === 1){
searchString = searchString.concat('&health=vegan')
}
else { console.log("error")}
}
//useEffect
useEffect(() => {
getRecipes();
}, [ricerca])
//fetching data
const getRecipes = async () => {
const response = await fetch(searchString);
const data = await response.json();
setData(data.hits);
}
//funzione ricerca (solo scrittura)
const onChangeSearch = query => setRichiesta(query);
//funzione modifica stato di ricerca
const getSearch = () => {
setRicerca(richiesta);
}
//barra ricerca e mapping data
return(
<SafeAreaView style={styles.container}>
<Searchbar
placeholder="Cerca"
onChangeText={onChangeSearch}
value={richiesta}
onIconPress={getSearch}
/>
this is the code, it returns "error" because on the first render the array is empty, but on the second render it fills with the value 1. can anyone help me out please?
By listening to the state of the preferenza. You need to exclude the getPreferences(preferenza); out of the useEffect for the first render and put it in it's own useEffect like this:
...
useEffect(() => {
getMyValue();
}, [])
useEffect(() => {
if( !preferenza.length ) return;
getPreferences(preferenza);
}, [preferenza])
i forgot to put the index of the array in
if(preferenza === 1){
searchString = searchString.concat('&health=vegan')
}
else { console.log("error")}
}
thanks for the answer tho, have a nice day!

Pass data from API to another component with TypeScript and ReactJS

i'am learning TS yet and I trying to create an application where I get data from API, show results and if someone click on item, it shows a modal with more details, but i'am trouble cause basically my component doesn't render... Look at my code =) !
import IMovie from "../../models/movie.model";
import Modal from "../modal/Modal";
import "./style";
import {
ResultsBody,
ResultsContainer,
TitleResult,
MovieStats,
MovieCover,
MovieStatsDescription,
} from "./style";
interface ISearch {
search?: string;
}
const URL =
"#";
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const currentData = data.slice(indexFirstData, indexLastData);
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
const filteredData = data.filter((results) => {
return results.title.toLowerCase().includes(search!.toLocaleLowerCase());
});
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{!search
? currentData.map((item) => (
<ResultsBody
key={item.id}
// onClick={() => {
// selectedMovie(item);
// }}
>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))
: filteredData.map((item) => (
<ResultsBody key={item.id}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
<Modal data={data} /> //HERE IS WHERE I'AM CALLING MY MODAL, I want to pass data here
<Pagination
dataPerPage={dataPerPage}
totalData={data.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
export default Results;
This is my MODAL component
import React from "react";
import { ModalContainer } from "./style";
import IMovie from "../../models/movie.model";
interface IData {
data: IMovie[];
}
const Modal = ({ data }: IData) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>; //HERE IS NOT WORKING
};
export default Modal;
As you can see guys, I can show all results on console.log, but when I put inside the return the log says ''TypeError: Cannot read property 'title' of undefined''
If someone could help me I'd really appreciate! Thanks a lot =)
Movie vs Array
You are getting the error
'Property 'title' does not exist on type 'IMovie[]'. TS2339
in your Modal component because data is an array of movies. An array doesn't have a title property.
You want the modal to show one movie, so you should only pass it one movie.
interface IData {
data: IMovie;
}
Current Selection
Changing the IData interface fixes the issues in Modal, but creates a new error in Results because we are still passing an array. The correct prop is the data for the movie that was clicked. What movie is that? We need to use a useState hook in order to store that data.
Depending on where you control the open/closed state of the Modal, you may also want to pass an onClose callback that clears the selected movie state.
the state:
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
in the movie:
onClick={() => setSelected(item)}
the modal:
{selected === null || (
<Modal
data={selected}
onClose={() => setSelected(null)}
/>
)}
Avoid Duplicated Code Blocks
You are rendering a movie the same way whether it's from currentData or filteredData, so we want to combine those. We could create a shared renderMovie callback or ResultsMovie component to use in both loops, but I think we can actually handle it higher up and just have one loop.
You also want your pagination to reflect the pages of just the matching movies when we are filtering based on a search.
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// total for the pagination should be based on matchingMovies instead of data
const totalData = matchingMovies.length;
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
There might be some bugs or potential additional improvements but I can't actually run this without your components :)
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{currentData.map((item) => (
<ResultsBody key={item.id} onClick={() => setSelected(item)}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>{item.title}</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
{selected === null || (
<Modal data={selected} onClose={() => setSelected(null)} />
)}
<Pagination
dataPerPage={dataPerPage}
totalData={matchingMovies.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
interface ModalProps {
data: IMovie;
onClose: () => void;
}
const Modal = ({ data, onClose }: ModalProps) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>;
};

I need help fixing the Bug in my list array

I code community I have a bug in my search bar. The app is very easy... Ever time a user creates a name it displays in a list array. My problem is that when the user creates a name that name is not displaying my the list but when I search the name, it does show the list.
Here the video so you can have a better understanding
https://www.youtube.com/watch?v=WIM-H4xXqMw
and the code. I need help in my search bar and display the list when clicking the button
FolderHome.js - component
const [folder, emptyFolder] = useState([]);
const data = folder;
const [searchTerm, setSearchTerm] = useState("");
let [filteredData, setFilteredData] = useState(data);
//this function is when the user press the button we want to use the folderName(value) and display in a list.
//then when we add a foldername we update and add a new folder name.
//To understand I am just adding a folder name into my empty array. Now my array as a Folder inside.
const addFolder = (folderName) => {
emptyFolder((currentFolder) => [
...currentFolder,
{ id: Math.random().toString(), value: folderName },
]);
};
//Search
const _searchFilterFunction = (event, data) => {
let newData = [];
setSearchTerm({ searchTerm: event });
if (event) {
newData = data.filter((item) => {
const textData = event.toUpperCase();
const nameFolder = item.value.toUpperCase();
return nameFolder.includes(textData);
});
setFilteredData([...newData]);
} else {
setFilteredData([...data]);
}
};
return (
<View style={styles.HomeContainer}>
<TextInput
underlineColorAndroid="transparent"
autoCapitalize="none"
placeholderTextColor="#9a73ef"
style={styles.search}
placeholder="Search"
onChangeText={(value) => {
_searchFilterFunction(value, data);
}}
/>
<FolderInput myFolder={addFolder} />
{filteredData.map((item, index) => {
return <Text key={item.id}>{item.value}</Text>;
})}
</View>
);
};
FolderInput.js - Component
const FolderInput = (props) => {
//This function we handle the input and output off Folder
const [outputFolder, inputFolder] = useState("");
//This arrow function is handling the folder name on onChangeText in TextInput
const folderName = (entereName) => {
inputFolder(entereName);
};
//Function to clear input when done enter folder name
const clearInput = () => {
props.myFolder(outputFolder);
inputFolder("");
};
return (
// TouchableWithoutFeedback allow to register a touche withou nothing happen to it
<View style={styles.outputContainer}>
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss();
}}
>
<View style={styles.containerFolder}>
<TextInput
blurOnSubmit
placeholder="Create Folder"
style={styles.containerTextInput}
onChangeText={folderName}
//we pass the name folder into is value by biding and store into outputFolder
value={outputFolder}
/>
<TouchableOpacity>
<AntDesign
onPress={clearInput}
name="addfolder"
backgroundColor="black"
size={30}
/>
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
</View>
);
};
export default FolderInput;
Thank you so much for all hard work your in advance
:)
You should not have filteredData as state. it is a computed result of searchTerm and folder.
just declare it as:
const filteredDate = folder.filter((item) => {
const textData = searchTerm.toUpperCase();
const nameFolder = item.value.toUpperCase();
return nameFolder.includes(textData);
});
You can take a look at a simple implementation of search in this snack:
https://codesandbox.io/s/cocky-cori-n704s?file=/src/App.js

Categories