react native pagination with search using flatlist and hook - reactjs

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;

Related

UseEffect mutiple re-renders after async api call and make changes in UI after 1 sec of first call

I'm making a Quiz app, using API from (Trivia api),
Issues is - As soon as the api call is made the state is changes 3 times and my UI data changes 2 times in 1 second.
I think the issue is related to useEffect even though i'm adding empty dependency in useEffect.
can anybody explain why is it happening?
Layout.js
import { useEffect, useState } from 'react'
import { Outlet } from 'react-router-dom'
import Header from '../Componenets/Header/Header'
import ProgressBar from '../Componenets/ProgressBar/ProgressBar'
import QuizMain from '../Componenets/QuizMain/QuizMain'
function Layout() {
const [questionAre, setQuestionsAre] = useState([])
const [currentQuestion, setCurrentQuestion] = useState(0)
const changeCurrentQuestion = (value) => {
setCurrentQuestion(value)
}
useEffect(() => {
const QuizFetch = async () => {
try {
const res = await fetch(
'https://the-trivia-api.com/api/questions?categories=food_and_drink,general_knowledge&limit=10&region=AU&difficulty=easy',
)
const data = await res.json()
const transformData = data.map((item) => {
const newarray = item.incorrectAnswers
return {
options: newarray,
question: item.question,
correctAnswer: item.correctAnswer,
}
})
setQuestionsAre(transformData)
} catch (err) {
console.log(err, 'err in getting data')
}
}
QuizFetch()
}, [])
return (
<div className="Layout">
<Header />
<ProgressBar
changeCurrentQuestion={changeCurrentQuestion}
currentQuestion={currentQuestion}
questionAre={questionAre}
/>
{/* <QuizMain
changeCurrentQuestion={changeCurrentQuestion}
currentQuestion={currentQuestion}
questionAre={questionAre} /> */}
<Outlet context={[changeCurrentQuestion, currentQuestion, questionAre]} />
</div>
)
}
export default Layout
Since react 18 and the lifecycle in dev mode you have to use the abortController.
The signal will jump to the catch and then you will only have one successfull api call
useEffect(() => {
const abortController = new AbortController();
const QuizFetch = async () => {
try {
const res = await fetch(
'https://the-trivia-api.com/api/questions?categories=food_and_drink,general_knowledge&limit=10&region=AU&difficulty=easy',
{
signal: abortController.signal,
},
)
const data = await res.json()
const transformData = data.map((item) => {
const newarray = item.incorrectAnswers
return {
options: newarray,
question: item.question,
correctAnswer: item.correctAnswer,
}
})
setQuestionsAre(transformData)
} catch (err) {
if (abortController.signal.aborted) return;
console.log(err, 'err in getting data')
}
}
QuizFetch()
return () => {
abortController.abort();
};
}, [])

How to process data received from an AJAX request in React

I have a custom hook named "useFetch" which makes an AJAX request and stores the result in the state. I simply want to format the data received from the ajax using a function in my component but not sure how to do this since the function needs to be called only after the data is received.
An example is below:
import React, { Component, useState } from "react";
import useFetch from "../../../Hooks/useFetch";
const Main = () => {
const { data, isPending, error } = useFetch(
"http://127.0.0.1:8000/api/historic/1"
);
function formatData(data){
//Do some processing of the data after it's been received
}
//This doesn't work of course because it runs before the data has been received
const formatted_data=formatData(data);
return (
//Some display using the formatted data
);
};
export default Main;
This is the custom hook, useFetch, which is used in the above component. I'd prefer to not have to do the formatting in here because the formatting is specifically related to the above component and this custom hook is designed to have more universal utility.
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setisPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (res.ok) {
return res.json();
} else {
throw Error("could not fetch data for that resource");
}
})
.then((data) => {
setData(data);
setisPending(false);
setError(null);
})
.catch((er) => {
if (er.name === "AbortError") {
console.log("fetch aborted");
} else {
setError(er.message);
setisPending(false);
}
});
return () => abortCont.abort();
}, [url]);
return { data, isPending, error };
};
export default useFetch;
You should wrap it with useEffect hook with data as it's deps.
const [formattedData, setFormattedData] = useState();
useEffect(() => {
if (!data) return;
const _formattedData = formatData(data);
setFormattedData(_formattedData);
}, [data]);

NextJS / next-translate : get lang outside of components

We have a short question for our application (NextJS 11.0.0 + next-translate 1.0.7)
The library contains a function to make an API call (/lib/mylib.js) :
export const getDataExample = async (lang) => {
return fetch(_apiurl_/example/{lang});
};
And my component in react (/components/myComponent.js) call this function with a useEffect:
import { useEffect, useState } from 'react';
import useTranslation from 'next-translate/useTranslation';
import { getDataExample } from '/lib/mylib';
export default function MyComponent() {
const [data, setData] = useState(false);
const { lang } = useTranslation();
useEffect(() => {
const fetchData = async () => {
const response = await getDataExample(lang);
setData(response);
};
fetchData();
}, []);
[...]
}
I don't want to call getDataExample() directly with the lang parameter.
Is it possible to get the current language in the function (/lib/mylib.js) ?
Thank you for your reply !
But now imagine that my library (/lib/mylib.js) is also used to fetch data into a getServerSideProps :
export async function getServerSideProps({ locale }) {
const response = await getDataExample(locale);
[...]
}
React Hooks are not available here, so what do you do ?
You can create your custom hook. This is an example:
const useFetchWithLang = (func) => {
const { lang } = useTranslation()
return useCallback((args) => func({ ...args, lang }), [lang])
}
const fetchDataExample = ({ otherParam, lang }) => {
return { test: 'test1' }
}
const fetchDataExampleWithLang = useFetchWithLang(fetchDataExample)
After for example, you could use it in a useEffect.
useEffect(() => {
const fetchData = async () => {
const response = await fetchDataExampleWithLang({ otherParam: 'test' });
setData(response);
};
fetchData();
}, []);

setState react hook for array is not saving prior array elements

I have an API request that uses aysnc and await, grabs the data, then makes a second request with Promise.all, which makes multiple API requests with the id's. That part works out fine.
However, when I go to save the data inside a React hook called, "setItem", it only saves that one and over writes the others. I have a spread operator inside the setItem()
setItems(...items, data)
data being the response from the API request.
My API request is in the top layer of my react app, so I pulled it out into it's own little helper file, that's why "items" and "setItems", are arguments passed through.
import axios from 'axios';
import BottleNeck from 'bottleneck'
const limiter = new BottleNeck({
maxConcurrent: 1,
minTime: 333
})
export const Request = (items, setItems) => {
const getData = () => {
const options = 'newstories'
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 () => {
const {data} = await getData()
Promise.all(
data.map(async (d) => {
const {data} = await limiter.schedule(() => getIdFromData(d))
//****************** issue here ************************//
setItems([...items, data]);
})
)
}
runAsyncFunctions()
}
just in case you want to see the app.js file for reference
import React, { useState, useEffect } from 'react';
import './App.css';
import { SearchBar } from './search-bar';
import { Results } from './results';
import { Request } from './helper/request'
function App() {
const [input, setInput] = useState('');
const [items, setItems] = useState([]);
const handleChange = val => {
setInput(val)
}
// console.log(input)
// console.log(results)
// API calls
// call useEffect here, calls Request(), put results in useEffect
useEffect(() => {
Request(items, setItems)
}, [])
return (
<div className="App">
<SearchBar handleChange={handleChange}/>
<Results items={items} />
</div>
);
}
export default App;
At your Promise.all return each data, after you can chain with then that passes an array with all resolved data. This way you only need to call it once setItems:
Promise.all(
data.map(async (d) => {
const { data } = await limiter.schedule(() => getIdFromData(d));
return data;
})
).then((dataResults) => setItems((results) => [...results, ...dataResults]));

How to pass state data from custom hooks to react component?

I have custom hook(useFetch) that takes URL as input and fetch data from that URL and returns data. I want to implement spinner (already made Spinner component ) on my other components and I tried by making state for the isLoading and setIsLoading of spinner.
my custom hook code:
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [dataArray, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const dataArray = await res.json();
setData(dataArray.data);
};
fetchData();
} catch (err) {
console.error(err);
}
setIsLoading(false);
}, [url]);
return dataArray;
};
export default useFetch;
This is the component that I want to implement spinner.
import React, { useState } from 'react';
import CONSTANTS from '../../constants/constants';
import CompanyLists from '../../components/company-lists/CompanyLists';
import Pagination from '../../components/pagination/Pagination';
import useFetch from '../../components/effects/use-fetch.effect';
import Spinner from '../../components/spinner/Spinner';
const CompanyListing = () => {
const [counter, setCounter] = useState(1);
const companies = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
return (
<>
<Container>
<div style={userStyle}>
{companies ? companies.map((company) => <CompanyLists key={company.id} {...company} />) : 'No companies'}
</div>
<Pagination props={companies} counter={counter} name="companies" setCounter={setCounter} />
<Spinner />
</Container>
</>
);
};
const userStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(1, 1fr)',
gridGap: '1rem',
};
export default CompanyListing;
Problem here is: How can I send those loading state from hook to CompanyListing component. Any help will be appreciated.
EDIT:
I have other component that also calls same hook and I want them not to be broken. As I didn't mention on original question .
My another case:
const jobsUrl = `${CONSTANTS.BASE_URL}/jobs?page=${counter}`;
const jobs = useFetch(jobsUrl);
AND
const { city, company_name, company_id, department, description, job_type, position, posted_at, url } = useFetch(
`${CONSTANTS.BASE_URL}/jobs/${id}`
);
How can I destructure in these two cases ?
You do not need anything special here. Just return the isLoading state with the dataArray from the useFetch hook.
As mentioned from the edit you need the useFetch to be more reusable and return data in different formats depending on the API response, hence the state should be initialized as null.
import { useState, useEffect } from 'react';
const useFetch = (url) => {
// it is best to initialize the state as null because response.data
// may be an object or an array depending on the API response
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const value = await res.json();
setData(value.data);
};
fetchData();
} catch (err) {
console.error(err);
}
setIsLoading(false);
}, [url]);
return {data, isLoading};
};
export default useFetch;
In the components you want to use the custom hook, you can destructure
the value for data and isLoading but to futher destructure values from the returned data we have to check if data is null
// destructure the values
const {data, isLoading} = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
// in this case data will be an array based on your API response
// please make sure to check data.length before trying to loop over
// and render the content, for example
return (
<div>
{
data.length && data.map(company => (
<CompanyLists key={company.id} {...company} />
));
}
</div>
)
For the second case where you will be fetching data using useFetch(`${CONSTANTS.BASE_URL}/jobs/${id}`); you have to check if the returned data is not null before destructuring further. Example
const { data, isLoading } = useFetch(`${CONSTANTS.BASE_URL}/jobs/${id});
if (isLoading) {
return <div>Loading...</div>
}
if (data) {
const {
city,
company_name,
company_id,
department,
description,
job_type, position, posted_at, url } = data;
return (
// your jsx code
// for example
<h3>{company_name}</h3>
<p>{department}</p>
)
}
You need to return isLoading as well from the hook.
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [dataArray, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const dataArray = await res.json();
setData(dataArray.data);
};
fetchData();
} catch (err) {
console.error(err);
}
setIsLoading(false);
}, [url]);
return {dataArray, isLoading};
};
export default useFetch;
And use this in your component like this
import React, { useState } from 'react';
import CONSTANTS from '../../constants/constants';
import CompanyLists from '../../components/company-lists/CompanyLists';
import Pagination from '../../components/pagination/Pagination';
import useFetch from '../../components/effects/use-fetch.effect';
import Spinner from '../../components/spinner/Spinner';
const CompanyListing = () => {
const [counter, setCounter] = useState(1);
const {dataArray: companies, isLoading} = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
return (
<>
<Container>
<div style={userStyle}>
{companies ? companies.map((company) => <CompanyLists key={company.id} {...company} />) : 'No companies'}
</div>
<Pagination props={companies} counter={counter} name="companies" setCounter={setCounter} />
{isLoading && <Spinner />}
</Container>
</>
);
};
const userStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(1, 1fr)',
gridGap: '1rem',
};
export default CompanyListing;
From your custom hook you can return both like
return {companies: dataArray, isLoading };
And destruct both
const {companies, isLoading} = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
First, you might need to change your useFetch effect a bit to update isLoading correctly. Then you could return both dataArray and isLoading :
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [dataArray, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const dataArray = await res.json();
setData(dataArray.data);
};
await fetchData();
} catch (err) {
console.error(err);
} finally {
setIsLoading(false);
}
}, [url]);
return [dataArray, isLoading];
};
export default useFetch;
And use it like the following :
const [companies, isLoading] = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);

Resources