How to display data from firestore using react native - reactjs

I'm trying to display data from firestore on the home page but i dont seem to get it working. I want to show all the data in the collection. the collection includes name, id, location etc...
import Firebase from "./lib/firebase";
import { useEffect, useState } from "react";
import { SnapshotViewIOS } from "react-native";
// export async function getRestaurants(restaurantsRetrieved) {
// var restaurantList = [];
// var snapshot = await firebase.firestore().collection("Restaurants").get();
// snapshot.forEach((doc) => {
// restaurantList.push(doc.data());
// });
// restauantsRetrieved(restaurantList);
// }
export default () => {
const [restaurantsList, setRestaurantsList] = useState([]); //Initialise restaurant list with setter
const [errorMessage, setErrorMessage] = useState("");
const getRestaurants = async () => {
try {
const list = [];
var snapshot = await Firebase.firestore().collection("Restaurants").get();
console.log("Here");
snapshot.forEach((doc) => {
list.push(doc.data());
});
setRestaurantsList(list);
} catch (e) {
setErrorMessage(
"There's nae bleeding restaurants, I told you to upload them!"
);
}
};
//Call when component is rendered
useEffect(() => {
getRestaurants();
}, []);
return (
<View style={tailwind('py-10 px-5')}>
<Text style={tailwind('text-4xl font-bold')}>
{restaurantsList}
</Text>
};

The State is not updating becuase it just taking the reference of list. So you need to update the like code below
.....
setRestaurantsList([...list]);
......

Related

List Items Always Blinking Every Swipe Screen How to Prevent in React Native

Im new in React, and im trying to get data from Firebase and it work.
Im having sidebar that i move/hide to left. Everytime i navigate just slide a bit, my data like blinking(rerender). even nothing todo with changing the firestore data.
My sidebar having function to change state color of the stausbar.
`
import {View, Text} from 'react-native';
import React from 'react';
import {collection, getDocs} from 'firebase/firestore';
import {auth, db} from '../../firebase';
const RecentTransaction = () => {
const userId = auth.currentUser.uid;
const expenseRef = collection(db, 'users', userId, 'expense');
const [expenses, setExpenses] = React.useState([]);
React.useEffect(() => {
(async () => {
const fetch = await getDocs(expenseRef);
const docs = fetch.docs.map(doc => {
const data = doc.data();
data.id = doc.id;
return data;
});
setExpenses(docs);
})();
}, []);
return expenses?.map(item => {
return (
<View key={item.id}>
<Text>{item.memo}</Text>
<Text>{item.amount}</Text>
<Text>{item.category}</Text>
<Text>{item.createdAt}</Text>
</View>
);
});
};
export default RecentTransaction;
`
How to handle this?
How to use memo or something to get this fine? trying useMemo but didnt get to wok

react native pagination with search using flatlist and hook

react native search using search key send as parameter for Api call which was text input in search filled and along with it react native pagination using Flatlist and hook , problem faced that i have added data when next page loaded, but during seach key enter it stored its previous value
When you type on input search, you first should reset data state: setData([]).
setData is async and api request also, then, it is possible than reset failed some times.
For this reason, I use flags with useRef, to write and read value synchronous way, example:
const resetData = useRef(false)
when type on filter:
resetData.current=true
and on .then api req:
if(resetData.current){
setData(response.data);
resetData.current = false
}
else{
setData([...data,response.data])
}
Edit(after your comments):
import React, { useState, useEffect, useRef } from "react";
//..other imports
function App() {
const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [searchKey, setSearchKey] = useState();
const resetData = useRef(false);
const getData = () => {
let pageToReq = page;
if (resetData.current) {
pageToReq = 0;
}
const headers = { Authorization: "token" };
axios
.get("baseurl" + "getdata?page=" + pageToReq, { searchingKey: searchKey })
.then(async function (response) {
if (resetData.current) {
setData(response.data);
resetData.current = false;
} else {
setData([...data, ...response.data]);
}
})
.catch(function (error) {
console.log(error);
});
};
useEffect(() => {
getData();
}, [page, searchKey]);
const handleOnChangeText = (val) => {
resetData.current = true;
setSearchKey(val);
};
const handleOnEnd = () => {
if (resetData.current) {
return;
}
setPage(page + 1);
};
return (
<View>
<TextInput onChangeText={handleOnChangeText} />
<FlatList
data={data}
onEnd={handleOnEnd}
onEndReachThreshold={0.1}
></FlatList>
</View>
);
}
export default App;

useState in context provider loses data when a child components gets removed

So I am using a context provider to give the base data to my app for example: [{id: "a", name: "a"}].
Now I have a component that required the data portion of this object, I check if this data property is not yet there, I get it from my api, fill it and then it should not have to recall the api to get it again.
My provider:
import { createContext, useState, useCallback, useMemo, useContext } from "react";
import axios from "axios";
export const StockContext = createContext();
export const useStock = () => useContext(StockContext);
export const StockProvider = ({ children }) => {
const [stocks, setStocks] = useState();
const getDataFromStock = useCallback(
async ({ id }) => {
let method = "GET";
let url = `${config.base_url}data/${id}`;
try {
const { data: response } = await axios({ method, url });
const { succes, data, error } = response;
let updated = stocks;
updated.find((s) => s.id === id).data = data;
console.log("set stocks", updated);
setStocks(updated);
}
return succes;
}
},
[stocks]
);
const value = useMemo(
() => ({
getDataFromStock,
stocks,
}),
[getDataFromStock, stocks]
);
return <StockContext.Provider value={value}>{children}</StockContext.Provider>;
};
Note: I removed some error handling for simplicity sake.
After the function getDataFromStock with the id is called. The stocks object should look like this: [{id: "a", name: "a", data: [{id: "c", ...}]}].
Now I have my component to show the details (data) from this object. It first checks if it is not already in the 'stocks' object and if not gets it.
export default function StockDetailPage() {
const { id } = useParams();
const [currentStock, setCurrentStock] = useState({});
const { stocks, getDataFromStock, testStocks } = useContext(StockContext);
// TODO find why data keep disappearing
useEffect(() => {
const getData = async () => {
if (!stocks.find((s) => s.id === id).data) {
console.log("getting");
await getDataFromStock({ id });
// TODO Indication on loading?
} else {
console.log("not getting");
}
};
getData();
}, [getDataFromStock, id, stocks]);
return (
<div>
Stock: {currentStock?.owner?.username}
{stocks
?.find((s) => s.id === id)
.data?.map((p) => (
<ProductPreview key={p.product_id} {...p} />
))}
</div>
);
}
Now if I look at my react debugger, I see the state of the stocks has this data object, but once I navigate away from the details, this property seems to disappear. Now how could I implement this in the right way or fix the problem?
Kind regards

paginating requests to an API, using HackerNews API

*** The question is quite simple, I just wrote a lot to be specific. ***
I've been looking online for a few hours, and I can't seem to find answer. Most pagination is about after you have received the data from the API call, or for backend node.js built with it's own server.
My issue, I have an API request that returns an array of 500 ID's. Then a second multi API call, looping through each ID making a promise API call. I use the Promise.all method.
It takes 2-3 minutes to complete this request.
Currently, I made a quick filter to get the first ten results, so it'll display and I can render the data to work on other things like the render component and styling.
My question, I'd like to be able to paginate the data while API calls are still being made.
Basically, Promise.all send an array of 10 id's (ten API calls), get continually. But after the first set of ten, I'd like to start receiving the data to render.
Right now, I can only get ten with my filter method. Or wait 2-3 min for all 500 to render.
Here is my request.js file, (it's part of my App component, I just separated it for clarity).
import axios from 'axios';
import BottleNeck from 'bottleneck'
const limiter = new BottleNeck({
maxConcurrent: 1,
minTime: 333
})
export const Request = (setResults, searchBarType, setLoading) => {
const searchBar = type => {
const obj = {
'new': 'newstories',
'past': '',
'comments': 'user',
'ask': 'askstories',
'show': 'showstories',
'jobs': 'jobstories',
'top': 'topstories',
'best': 'beststories',
'user': 'user'
}
return obj[type] ? obj[type] : obj['new'];
}
let type = searchBar(searchBarType)
const getData = () => {
const options = type
const API_URL = `https://hacker-news.firebaseio.com/v0/${options}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const getIdFromData = (dataId) => {
const API_URL = `https://hacker-news.firebaseio.com/v0/item/${dataId}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const runAsyncFunctions = async () => {
setLoading(true)
const {data} = await getData()
let firstTen = data.filter((d,i) => i < 10);
Promise.all(
firstTen.map(async (d) => {
const {data} = await limiter.schedule(() => getIdFromData(d))
console.log(data)
return data;
})
)
.then((newresults) => setResults((results) => [...results, ...newresults]))
setLoading(false)
// make conditional: check if searchBar type has changed, then clear array of results first
}
runAsyncFunctions()
}
and helps, here's my App.js file
import React, { useState, useEffect } from 'react';
import './App.css';
import { SearchBar } from './search-bar';
import { Results } from './results';
import { Request } from '../helper/request'
import { Pagination } from './pagination';
function App() {
const [results, setResults] = useState([]);
const [searchBarType, setsearchBarType] = useState('news');
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [resultsPerPage] = useState(3);
// Select search bar button type
const handleClick = (e) => {
const serachBarButtonId = e.target.id;
console.log(serachBarButtonId)
setsearchBarType(serachBarButtonId)
}
// API calls
useEffect(() => {
Request(setResults, searchBarType, setLoading)
}, [searchBarType])
// Get current results
const indexOfLastResult = currentPage * resultsPerPage;
const indexOfFirstResult = indexOfLastResult - resultsPerPage;
const currentResults = results.slice(indexOfFirstResult, indexOfLastResult);
// Change page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
return (
<div className="App">
<SearchBar handleClick={handleClick} />
<Results results={currentResults} loading={loading} />
<Pagination resultsPerPage={resultsPerPage} totalResults={results.length} paginate={paginate} />
</div>
);
}
export default App;
I hope it's generic looking enough to follow guide lines. Please ask me anything to help clarify. I've spent 8-10 hours searching and attempting to solve this...
You can continue with your filter, but you have to do some changes, for totalResults props of the component Pagination you have to set 500 rows so the user can select the page he wants because if you set 10 rows, the pages a user can select are 1,2,3,4, but we don't need that we need to put all pages 1 to 34 pages because we have 500 ids. The second point, we need to fetch data from the server by page with a page size equal to 3 we need to pass to Request startIndex and lastIndex to Request.
Request.js
import axios from 'axios';
import BottleNeck from 'bottleneck'
const limiter = new BottleNeck({
maxConcurrent: 1,
minTime: 333
})
export const Request = (setResults, searchBarType, setLoading, startIndex, lastIndex) => {
const searchBar = type => {
const obj = {
'new': 'newstories',
'past': '',
'comments': 'user',
'ask': 'askstories',
'show': 'showstories',
'jobs': 'jobstories',
'top': 'topstories',
'best': 'beststories',
'user': 'user'
}
return obj[type] ? obj[type] : obj['new'];
}
let type = searchBar(searchBarType)
const getData = () => {
const options = type
const API_URL = `https://hacker-news.firebaseio.com/v0/${options}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const getIdFromData = (dataId) => {
const API_URL = `https://hacker-news.firebaseio.com/v0/item/${dataId}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const runAsyncFunctions = async () => {
setLoading(true)
const {data} = await getData()
let ids = data.slice(firstIndex, lastIndex+1) // we select our ids by index
Promise.all(
ids.map(async (d) => {
const {data} = await limiter.schedule(() => getIdFromData(d))
console.log(data)
return data;
})
)
.then((newresults) => setResults((results) => [...results, ...newresults]))
setLoading(false)
// make conditional: check if searchBar type has changed, then clear array of results first
}
runAsyncFunctions()
}
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import { SearchBar } from './search-bar';
import { Results } from './results';
import { Request } from '../helper/request'
import { Pagination } from './pagination';
function App() {
const [results, setResults] = useState([]);
const [searchBarType, setsearchBarType] = useState('news');
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [resultsPerPage] = useState(3);
// Select search bar button type
const handleClick = (e) => {
const serachBarButtonId = e.target.id;
console.log(serachBarButtonId)
setsearchBarType(serachBarButtonId)
}
// API calls
useEffect(() => {
Request(setResults, searchBarType, setLoading, 0, 2) //we fetch the first 3 articles
}, [searchBarType])
// Change page
const paginate = (pageNumber) => {
// Get current results
const indexOfLastResult = currentPage * resultsPerPage;
const indexOfFirstPost = indexOfLastResult - resultsPerPage;
Request(setResults, searchBarType, setLoading, indexOfFirstPost , indexOfLastResult) //we fetch the 3 articles of selected page
setCurrentPage(pageNumber);
}
return (
<div className="App">
<SearchBar handleClick={handleClick} />
<Results results={results} loading={loading} />
<Pagination resultsPerPage={resultsPerPage} totalResults={500} paginate={paginate} />
</div>
);
}
export default App;

React Hooks + Mobx => Invalid hook call. Hooks can only be called inside of the body of a function component

I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}

Resources