Cannot access value of state inside function body that's inside component - javascript

I am trying to access the state variable val inside my function addToFeed(). In the first line of code inside addToFeed(), the line console.log("Value of val: " + val) always prints out "Value of val:" (indicating val can't be found, or it doesn't exist at that point). Why does this happen? And how can I get the value of val to be found at that point in the code? (the value of val does exist - I just can't access it).
Overall goal of code: Create an infinite scrolling list of posts from MongoDB that loads in as the user scrolls to the bottom of the page (this functionality is working, except it won't load the newest posts if my val variable is never passed to my API).
Here is my code (below the code is a brief explanation of what it does to help with context):
import { useState, useEffect, useRef, useCallback } from "react";
export default function Feed() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [posts, setPosts] = useState([]);
const [val, setVal] = useState(""); // VALUE I WANT TO ACCESS
const [hasMore, setHasMore] = useState(true);
const observer = useRef()
const lastItemRef = useCallback(node => {
if (loading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
addToFeed();
}
});
if (node) observer.current.observe(node);
}, [loading]);
useEffect(() => {
return () => {
setLoading(true);
addToFeed();
setLoading(false);
}
}, [])
async function addToFeed() {
console.log("Value of 'val': " + val) // 'val' CAN'T BE FOUND HERE (need it below for the body of my api call)
try {
if (hasMore) {
await fetch("http://localhost:3000/api/load-forever-api", {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(val)
}).then(res => res.json())
.then(data => {
setVal(data.posts[data.posts.length - 1].id)
setPosts(prev => {
return [...prev, ...data.posts]
})
setHasMore(data.hasMore)
});
}
}
catch (error) {
setError(true);
}
}
// UI RETURNED BELOW
if (loading) return <p>Loading...</p>
if (error) return <p>Error</p>
return (
<>
<h2>{`Value: ${val}`}</h2>
{posts.map((item, index) => {
if (posts.length === index + 1) {
return (
<div className="shaded-post" ref={lastItemRef} key={index}>
<h3>{item.title}</h3>
<p>{item.body}</p>
<p>{item.id}</p>
<br />
</div>
)
} else {
return (
<div className="shaded-post" key={index}>
<h3>{item.title}</h3>
<p>{item.body}</p>
<p>{item.id}</p>
<br />
</div>
)
}
})}
</>
)
}
Additional info:
My API call returns a list of objects with title, body, and id fields (MongoDB call). (ex: [{title: "ex1", body: "ex1", id: "123k8al29slkd"}, {title: "ex2", body: "ex2", id: "823jald8sj22"}]
If my API call receives a body with a blank val variable, it returns the first 4 items from my MongoDB collection, if it has an id variable, I use that to return just the items from my MongoDB collection that were posted later than that (basically, this allows incremental infinite scrolling; loads the first 4, then if the user scrolls low enough it'll load the next 4, etc. etc.)
My val variable updates every time I trigger the addToFeed() function, assigning it the value of the id from the last item on screen (infinite scrolling).
My addToFeed() function adds to my posts state, which is rendered as a list of items from my MongoDB database. Calling it multiple times when the user scrolls to the bottom of the pages creates the effect of an "infinite scroll".
I have 3 states that the UI can render: loading, error, and my infinite scrollable list of items from MongoDB.

The reason why you are not getting the val value is that when component renders, lastItemRef still have the old addToFeed function reference. You should pass the addToFeed in lastItemRef useCallback dependency array. By this when ever the component renders, it will have the latest addToFeed function reference

you can access val but the default value of val is equal to empty string''
try to change the default value to another value to see it
const [val, setVal] = useState("default val");
playground for your code

Related

"Partial" re-render after useState updates state and mapping array

I have a "weird" problem. I have a code in which after downloading data from backend, I update the states, and simply display information about logged user.
The logic downloads user data from server, updates state, and shows current information.
The problem is - only parts of information changes, like user score, but not his his position (index) from array (index is currentPosition in DOM structure)
It looks like this - logic file:
const [usersScoreList, setUsersScoreList] = useState([])
const [liderScore, setLiderScore] = useState('')
const [idle, setIdle] = useState(true)
const fetchUsersScore = async () => {
setIdle(false)
try {
const { data, error } = await getUsersRank({
page: 1,
limit: 0,
sort: usersSort,
})
if (error) throw error
const scoreData = data?.data
const HighestScore = Math.max(...scoreData.map((user) => user.score))
setUsersScoreList((prevData) => [...prevData, ...scoreData])
setLiderScore(HighestScore)
} catch (err) {
console.error(err)
}
}
useEffect(() => {
const abortController = new AbortController()
idle && fetchUsersScore()
return () => abortController.abort()
}, [idle])
Main file -
const { usersScoreList, liderScore } = useScoreLogic()
const [updatedList, setUpdatedList] = useState(usersScoreList)
useEffect(() => setUpdatedList(usersScoreList), [usersScoreList])
const { user } = useAuth()
const { id } = user || {}
const current = updatedList.map((user) => user._id).indexOf(id) + 1
<ScoreBoard
id={id}
score={user.score}
updatedList={updatedList}
currentPosition={current}
liderScore={liderScore}
/>
and component when information is displayed, ScoreBoard:
const ScoreBoard = ({
updatedList,
id,
liderScore,
score,
currentPosition,
}) => {
const { t } = useTranslation()
return (
<ScoreWrapper>
{updatedList?.map(
(user) =>
user._id === id && (
<div>
<StyledTypography>
{t('Rank Position')}: {currentPosition}
</StyledTypography>
<StyledTypography>
{score} {t('points')}
</StyledTypography>
{user.score === liderScore ? (
<StyledTypography>
{t('Congratulations, you are first!')}
</StyledTypography>
) : (
<StyledTypography>
{t('Score behind leader')}: {liderScore - score}
</StyledTypography>
)}
</div>
)
)}
</ScoreWrapper>
)
}
and when the userScoreList in logic is updated (and thus,updatedList in Main file, by useEffect) everything is re-rendered in ScoreBoard (score, score to leader) but not the current position, which is based on index from updatedList array, (const current in main file).
This is a little bit weird. Why the updatedList and usersScoreList arrays changes, user score changes, but not the user index from array while mapping ? (i checked in console.log, user index is based upon score, and yes, during mounting state, the index in arrays are also changed)
If so, why currentPosition is not re-rendered like user score ?
It works only when i refresh the page, THEN the new user index is displayed like other informations.
Can you please refactor your useEffect and write it like that?
useEffect(() =>{
setUpdatedList(usersScoreList)
}, [usersScoreList])
I think the way you do it without curly braces it returns as a cleanup

Filter data with checkbox buttons in React

I would like to filter data based on pressing multiple checkbox buttons. Currently only the most recently pressed button works and shows the output instead of also showing outputs from other buttons which are pressed as well.
The state of checkbox buttons works correctly i.e. when clicked it is true, when unclicked it is false - however I am not sure how to connect it with my find function which fetches the data.
const JobsList = (props) => {
const pageNumber = props.pageNumber || 1;
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [page, setPage] = useState(pageNumber);
const [pages, setPages] = useState(1);
useEffect(() => {
const fetchJobs = async () => {
try {
retrieveJobs();
retrievePages();
pages = retrievePages();
setJobs(jobs);
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
setError("Some error occured");
}
};
fetchJobs();
}, [page]);
const retrievePages = () => {
JobDataService.getPage(pages)
.then((response) => {
setPages(response.data.totalPages);
})
.catch((e) => {
console.log(e);
});
};
const Checkbox = ({ type = "checkbox", name, checked = false, onChange }) => {
return (
<input
type={type}
name={name}
checked={checked}
onChange={onChange}
className="btn--position"
/>
);
};
//plain object as state
const [checkedItems, setCheckedItems] = useState({}); //plain object as state
const filteredItems = [];
const handleChange = (event) => {
// updating an object instead of a Map
setCheckedItems({
...checkedItems,
[event.target.name]: event.target.checked,
filteredItems.
});
console.log("from HANDLECHANGE: ", checkedItems)
// console.log(checkedItems[event.target.checked])
// find(event.target.name)
};
useEffect(() => {
console.log("checkedItems from UseEffect: ", checkedItems);
// console.log(checkedItems)
// find(checkedItems)
}, [checkedItems]);
const checkboxes = [
{
name: "🤵‍♀️ Finance",
key: "financeKey",
label: "financeLabel",
},
{
name: "👩‍🎨 Marketing",
key: "marketingKey",
label: "marketingLabel",
},
{
name: "👨‍💼 Sales",
key: "salesKey",
label: "salesLabel",
},
{
name: "🥷 Operations",
key: "operationsKey",
label: "financeLabel",
},
{
name: "👨‍💻 Software Engineering",
key: "softwareEngineeringKey",
label: "softwareEngineeringLabel",
},
];
const retrieveJobs = () => {
JobDataService.getAll(page)
.then((response) => {
console.log(response.data);
setJobs(response.data.jobs);
})
.catch((e) => {
console.log(e);
});
};
const refreshList = () => {
retrieveJobs();
};
const find = (query, by) => {
JobDataService.find(query, by)
.then((response) => {
console.log(response.data);
setJobs(response.data.jobs);
// setPage(response.data.total_results)
setPages(response.data.totalPages);
})
.catch((e) => {
console.log(e);
});
};
return (
<div className="hero-container">
<div>
<div className="allButtons-div">
<div className="buttons-div">
<div>
<label>
{checkedItems[""]}
{/* Checked item name : {checkedItems["check-box-1"]}{" "} */}
</label>
{checkboxes.map((item) => (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={checkedItems[item.name]}
onChange={handleChange}
/>
</label>
))}
</div>
</div>
</div>
</div>
</div>
);
The function below fetches data from the MongoDB Realm database
const find = (query, by) => {
JobDataService.find(query, by)
.then((response) => {
setJobs(response.data.jobs);
setPages(response.data.totalPages);
})
.catch((e) => {
console.log(e);
});
};
To answer your question, our find() function should be a lot like your retrieveJobs() and retrievePages() functions - they interact with the data layer of your app. That said, if all we're trying to do is filter the data we already have (let's say that retrieveJobs() and retrievePages() fetches all of the jobs and pages you'll need), then we don't need refetch the data based on what's checked in your UI - we simply need to use JavaScript to filter the results by using things you should already be familiar with like map(), sort(), reduce(), filter(), etc.
To go further, this code has a lot of problems. We're using state probably a little more than we should, we're setting state in multiple places redundantly, we're using useEffect() calls that don't do much, the list goes on. I've been there - trying to do things in a "React" way can sometimes result in the opposite effect, where you're lost in endless useState() and useEffect() calls and trying to figure out where to call what event handler and why. I've gone through and made some fairly obvious changes to your code to hopefully get you on the right track to understanding what's going on a little bit better going forward, but I highly recommend going through the React docs and reading this post by Dan Abramov until you understand it (I had to read and re-read a couple paragraphs in that article over and over before it clicked, but I think it will go a long way for you).
Here's the code, it likely still has a lot of problems but best of luck moving forward!
// Since this is a constant set of data, you don't need to include it in your component; remember
// that React components are just regular old functions, so having this constant array value in your
// component means that it's being created anew every render. Let's move it above the component.
const checkboxes = [
{
name: '🤵‍♀️ Finance',
key: 'financeKey',
label: 'financeLabel',
},
{
name: '👩‍🎨 Marketing',
key: 'marketingKey',
label: 'marketingLabel',
},
{
name: '👨‍💼 Sales',
key: 'salesKey',
label: 'salesLabel',
},
{
name: '🥷 Operations',
key: 'operationsKey',
label: 'financeLabel',
},
{
name: '👨‍💻 Software Engineering',
key: 'softwareEngineeringKey',
label: 'softwareEngineeringLabel',
},
];
// the same principle applies with this smaller component. It doesn't use
// state or props from JobsList, so we should move the component outside of
// your JobsList component to make sure it's not created over and over again
// on each render; let's move it outside of JobsList
const Checkbox = ({ type = 'checkbox', name, checked = false, onChange }) => {
return (
<input
type={type}
name={name}
checked={checked}
onChange={onChange}
className="btn--position"
/>
);
};
// Since these functions seem to interact with the data layer of your app (depending on how JobDataService works of course),
// why don't we try making them functions that return a value from the data layer? Also, it looks like we're using async/await
// syntax in our useEffect call, why don't we try that here?
const retrievePages = async (pages) => {
try {
const response = await JobDataService.getPage(pages);
return response;
} catch (e) {
console.log(e);
}
};
// as an aside, I'm not sure of the difference between pages and page, but we'll keep this the same for now
const retrieveJobs = async (page) => {
try {
const response = await JobDataService.getAll(page);
return response;
} catch (e) {
console.log(e);
}
};
// to hopefully kind of answer your question, this find() function is a lot like the retrieveJobs and retrievePages functions above:
// it just interacts with your data layer - let's try and make it an async function and pull it out of the component so it can return
// results we need. As I explained above, though, if we grabbed all of our jobs and all of our pages already and just need to filter
// the data, why do we need to make a network call for that? Surely we can just use JS functions like filter(), map(), sort(), and reduce()
// to filter the results into the structures that our app needs
const find = async (query, by) => {
try {
const response = await JobDataService.find(query, by);
return response;
} catch (e) {
console.log(e);
}
};
const JobsList = (props) => {
const pageNumber = props.pageNumber || 1;
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
// if pageNumber is passed as a prop, why do we need to store it in state? Presumably the parent component
// of <JobsList /> will handle keeping track of pageNumber, which is why we pass data as props. Let's comment
// out this useState call
// const [page, setPage] = useState(pageNumber);
const [pages, setPages] = useState(1);
useEffect(() => {
const fetchJobs = async () => {
try {
const jobsData = await retrieveJobs(props.page);
const pageData = await retrievePages(pages);
setJobs(jobsData);
setPages(pageData);
// why do we call retrievePages() twice? also, you've decided to store pages in state, so we'll want to use setPages
// for this instead of a normal assignment. let's comment out this assignment
// pages = retrievePages();
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
setError('Some error occured');
}
};
fetchJobs();
}, [props.page, pages]);
const [checkedItems, setCheckedItems] = useState({});
// this is where we could do things like filter based on the checked items instead of making another network call; we have all of our data,
// we just need to do stuff with it (this is contrived but hopfully you get the idea) - every time React re-renders the JobsList component based on a new set of state or props (think something gets checked or unchecked),
// we'll just filter the data we've already fetched based on that new reality
const filteredJobs = jobs.filter((job) => job.id === checkedItems[job.id]);
const filteredPages = pages.filter((page) => page.id === checkedItems[page.id]);
const handleChange = (event) => {
// updating an object instead of a Map
setCheckedItems({
...checkedItems,
[event.target.name]: event.target.checked,
// not sure what this is, perhaps a typo; let's comment it out
// filteredItems.
});
// this find call needs two arguments, no? let's comment it out for now
// find(event.target.name)
};
// not sure of the purpose behind this second useEffect call, let's comment it out
// useEffect(() => {
// console.log("checkedItems from UseEffect: ", checkedItems);
// // console.log(checkedItems)
// // find(checkedItems)
// }, [checkedItems]);
// we'll ignore this for now as well and comment it out, we should probably be refreshing our data based on state or prop updates
// const refreshList = () => {
// retrieveJobs();
// };
return (
<div className="hero-container">
<div>
<div className="allButtons-div">
<div className="buttons-div">
<div>
<label>
{checkedItems['']}
{/* Checked item name : {checkedItems["check-box-1"]}{" "} */}
</label>
{checkboxes.map((item) => (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={checkedItems[item.name]}
onChange={handleChange}
/>
</label>
))}
</div>
</div>
</div>
</div>
</div>
);
};

React-native: Issues when rendering info fetched from an api for each component inside a flatlist

I'm developing an app in react-native but I'm still a beginner so I think I'm doing something wrong.
The logic behing the app is the following:
First, I fetch a list of restaurants from an API
For each of the restaurant, I display a "card" with all relevant information, inside a flatlist
For each restaurant, I need to fetch data from a second API (whose url depends on the id of the restaurant) to get its opening hours. Then I want to show a label "open" or "close" for each restaurant
The last step is the one giving me troubles. I tried two different approaches, but each of them has its own issue.
First of all, the part in common:
const [data, setData] = useState([]);
useEffect(() => {
let isMounted = true;
fetch(url, {
method: 'GET',
headers:
{
'Content-Type' : 'application/json',
'Accept' : 'application/json'
}
}
)
.then((response) => {
return response.json()
})
.then((json) => {
if (isMounted)
setData(json)
})
.catch((error) => console.error(error))
.finally(() => {isMounted=false } );
}, []);
The list of restaurants is saved into the "data" state as an array of objects. Each restaurant has two fields 'site_id' and 'source' which are needed to fetch the opening hours from another api.
Restaurants are then rendered inside a flatlist:
<FlatList
data={data}
renderItem={item => renderItem(item)}
keyExtractor={restaurant => restaurant.site_id}
/>
where renderItem is as follows:
const renderItem = ({ item }) => (
<RestaurantCard
source={item.source}
site_id={item.site_id}
/>
)
And RestaurantCard shows various info about each restaurant. To simplify:
const RestaurantCard = props => (
//some other info
<View style={{ position: 'absolute', top: 0, left: 0 }}>
// THIS IS WHERE I WANT TO SHOW IF THE RESTAURANT IS OPEN OR CLOSE
</View>
So now the two approaches I used to render the opening hours info:
Approach 1
When I fetch the list of restaurant, I run over each item in the list and call a new function 'saveOpeninghours':
.then((json) => {
if (isMounted)
setData(json)
for (let item of json.results) {
saveOpeninghours(item.source,item.site_id)
}
})
This function then call the correspondent API for each restaurant in order to retrieve the opening hours info. This info is saved into a state array called 'isOpen'
const [isOpen, setIsOpen] = useState([]);
const saveOpeninghours = (source,site_id) => {
let isMounted = true;
fetch(restaurantUrl, {
method: 'GET',
headers: {
'Content-Type' : 'application/json',
'Accept' : 'application/json',
}
}
)
.then((response) => {
return response.json()
})
.then((openingHours) => {
if (isMounted)
if(openingHours) {
//Do some stuff to determine if the restaurant is open or not
setIsOpen(isOpen => [...isOpen,{site_id:site_id,is_open:is_open}])
}
}
}
}
})
.catch((error) => console.error(error))
.finally(() => isMounted=false );
};
So the array will contain a list of objects in the form
{site_id:'18',is_open:true}
Then, I use another function to render this info for each restaurant:
const renderIsOpen = useCallback( (site_id,source) => {
let index = isOpen.findIndex(obj => obj.site_id === site_id)
if(index>0) {
if(isOpen[index].is_open) {
return( <Text> Open </Text>)
}
else {
return( <Text> Close </Text>)
}
}
},[isOpen])
I used a callback with [isOpen] as dependency because I want to wait until the array is filled with all restaurants.
And then in the RestaurantCard I simply call this render function:
const RestaurantCard = props => (
//some other info
<View>{renderIsOpen(props.site_id,props.source)}</View>
)
This works fine for almost all restaurants but, the problem is that the first restaurant in the list (and sometimes the second one) never shows the 'open/close' label. I think the reason is that the 'isOpen' state is not filled yet when the first restaurant is displayed, but then for the other restaurants the info is finally there and so for them there is no such problem.
I thought that using the callback with isOpen as dependency could solve the problem and force the component to re-render and show the open/close label, but it doesn't work.
Approach 2
This is the simpler one. I created a separate component which is called for each restaurant and retrieves/renders the opening hours info:
const areEqual = (prevProps, nextProps) => true;
const OpeningHours = React.memo(props => {
const [isOpen, setIsOpen] = useState();
let isMounted = true;
fetch(restaurantUrl, {
method: 'GET',
headers: {
'Content-Type' : 'application/json',
'Accept' : 'application/json',
}
}
)
.then((response) => {
return response.json()
})
.then((openingHours) => {
if (isMounted)
if(openingHours) {
// do some stuff to determine if the restaurant is open or not
setIsOpen(is_open) //this is true or false
}
}
}
}
})
.catch((error) => console.error(error))
.finally(() => isMounted=false );
if(isOpen) {
return(
<Text> Open </Text>
)
}
else {
return(
<Text> Close </Text>
)
}
}, areEqual);
export default OpeningHours;
And then, in my RestaurantCard shown before, I simply call this component passing 'source' and 'site_id' as props:
const RestaurantCard = props => (
//some other info
<OpeningHours
source={props.source}
site_id={props.site_id}
/>
)
The problems with this approach are:
It is quite slow: the open/close labels are shown with few seconds of delay in the restaurant card. However, this is not the major problem, which is instead the following:
In the RestaurantCard I also have a Button the user can press to add/remove that restaurant from favourites. When the button is pressed, the OpeningHours is re-rendered, so the open/close labels appear/disappear, which is quite annoying to see. As you can see I tried to wrap the component inside 'React.memo' to prevent this re-rendering, but it doesn't work.
So basically I'm trying to figure out
What is the best approach? I assume it's the second one since it's simpler but, it seems quite slower compared the the first one (btw the list of restaurant is not that big, only 10 items)
How to solve the issue related to the chosen approach?
Thanks!

Reactjs variable is returning with undefined after useEffect

I am very new to Reactjs, I am working on retrieving some data in order to display it, everything gets displayed however, when I filter there is an error that comes up "Cannot read property 'filter' of undefined", after debugging I found out that dataList is returning with undefined when typing anything in the search bar.
Appreciate your assistance.
function App() {
var dataList;
useEffect(() => {
// http get request
const headers = {
'Content-Type': 'application/json',
'Authorization': '***********************',
'UserAddressId': ****,
'StoreId': *
}
axios.get('https://app.markitworld.com/api/v2/user/products', {
headers: headers
})
.then((response) => {
dataList = response.data.data.products
setData(dataList)
})
.catch((error) => {
console.log(error)
})
}, []);
const [searchText, setSearchText] = useState([]);
const [data, setData] = useState(dataList);
// exclude column list from filter
const excludeColumns = ["id"];
// handle change event of search input
const handleChange = value => {
setSearchText(value);
filterData(value);
};
// filter records by search text
const filterData = (value) => {
console.log("dataList", dataList)
const lowercasedValue = value.toLowerCase().trim();
if (lowercasedValue === "") setData(dataList);
else {
const filteredData = dataList.filter(item => {
return Object.keys(item).some(key =>
excludeColumns.includes(key) ? false :
item[key].toString().toLowerCase().includes(lowercasedValue)
);
});
setData(filteredData);
}
}
return (
<div className="App">
Search: <input
style={{ marginLeft: 5 }}
type="text"
placeholder="Type to search..."
value={searchText}
onChange={e => handleChange(e.target.value)}
/>
<div className="box-container">
{data && data.length > 0 ? data.map((d, i) => {
return <div key={i} className="box">
<b>Title: </b>{d.title}<br />
<b>Brand Name: </b>{d.brand_name}<br />
<b>Price: </b>{d.price}<br />
<b>Status: </b>{d.status}<br />
</div>
}) : "Loading..."}
<div className="clearboth"></div>
{data && data.length === 0 && <span>No records found to display!</span>}
</div>
</div>
);
}
export default App;
You're mixing up a stateful data variable with a separate non-stateful, local dataList variable. The dataList only gets assigned to inside the axios.get, so it's not defined on subsequent renders; the setData(dataList) puts it into the stateful data, but the dataList on subsequent renders remains undefined.
To make things easier to understand, remove the dataList variable entirely, and just use the stateful data.
You also probably don't want to discard the existing data when the user types something in - instead, figure out what items should be displayed while rendering; rework the filterData so that its logic is only carried out while returning the JSX.
const [searchText, setSearchText] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
// http get request
const headers = {
'Content-Type': 'application/json',
'Authorization': '***********************',
'UserAddressId': ****,
'StoreId': *
}
axios.get('https://app.markitworld.com/api/v2/user/products', {
headers: headers
})
.then((response) => {
setData(response.data.data.products);
})
.catch((error) => {
console.log(error)
})
}, []);
// handle change event of search input
const handleChange = value => {
setSearchText(value);
};
// filter records by search text
const filterData = () => {
const lowercasedValue = searchText.toLowerCase().trim();
return lowercasedValue === ""
? data
: data.filter(
item => Object.keys(item).some(
key => excludeColumns.includes(key) ? false :
item[key].toString().toLowerCase().includes(lowercasedValue)
)
);
}
And change
{data && data.length > 0 ? data.map((d, i) => {
to
{filterData().map((d, i) => {
Your searchText should also be text, not an array: this
const [searchText, setSearchText] = useState([]);
should be
const [searchText, setSearchText] = useState('');
First of all, you don't need to maintain an additional non-state variable dataList as the local state data would serve the purpose.
API Call Code:
You should directly store the response from API after null checks satisfy.
useEffect(() => {
const headers = {
// key value pairs go here
};
// http request
axios.get(endPoint, {
headers,
})
.then((response) => {
// set data directly null checks
setData(response.data.data.products);
})
.catch((error) => {
console.log(error);
});
}, []);
Filteration Code
Use useCallback hook which would return a memoized version of the callback, unless the value of data changes.
const filterData = useCallback((value) => {
console.log('data', data);
// rest of code
}, [data]);

How to use hooks in correct way?

I have some issue
1- I defined a function that gets data from API and calling it in useEffect, It's work well
But i got this warning in VScode.
React Hook React.useEffect has a missing dependency: 'getOpenOrders'.
Either include it or remove the dependency array.
2- I implement pagination in the FlatList, So when the user reached the end of data List I calling a function to increment the current page, and based on current page updated, getOpenOrders fetched again because i pass currentPage to useEffect to the dependency array
So the issue here is in getOpenOrders should be contacted previous data with the new data so I use Concat method,
It's work well But some time I got a warning tells me there an duplicated data,
And when I use spread [...old, new] not work and I got a big error because Flatlist keyExtractor issue or something,
So can any hero here review my code and tell me what the wrong here with issue 1 - 2
code snippet
const OpenedAppointments = () => {
const [openedAppointment, setOpenedAppointment] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [lastPage, setLastPage] = useState(1);
const [loading, setLoading] = useState(false);
const [isFetch, setIsFetch] = useState(false);
const loadMoreOrders = () => {
if (currentPage <= lastPage - 1) {
setLoading(true);
setCurrentPage((prevPage) => prevPage + 1);
console.log('loadMore??');
}
};
const _renderFooter = () => {
return loading ? (
<View
style={{
paddingVertical: 10,
}}>
<ActivityIndicator color="#000000" size="large" />
</View>
) : null;
};
const getOpenOrders = () => {
let AuthStr =
'Bearer ,,,,';
const headers = {
'Content-Type': 'application/json',
Authorization: AuthStr,
};
Api.post(
`/open_orders?page=${currentPage}`,
{},
{
headers,
},
)
.then((res) => {
let last_Page = res.data.open_orders.last_page;
let allOpenedOrders = res.data.open_orders.data;
console.log('res:', allOpenedOrders);
console.log('last_Page', last_Page);
setLastPage(last_Page);
setOpenedAppointment((prevOpenedOrders) =>
prevOpenedOrders.concat(allOpenedOrders),
); // issue 2
// setOpenedAppointment((prevOpenedOrders) =>[...prevOpenedOrders, allOpenedOrders]);
setLoading(false);
// For pull to refresh
setIsFetch(false);
})
.catch((err) => console.log('err', err));
};
// For pull to refresh
const _refresh = () => {
setIsFetch(true);
getOpenOrders();
};
React.useEffect(() => {
getOpenOrders();
}, [currentPage]); // warning here "issue 1"
return (
<FlatList
showsVerticalScrollIndicator={false}
contentContainerStyle={{flexGrow: 1}}
data={openedAppointment}
ListEmptyComponent={renderEmpty}
renderItem={renderItems}
keyExtractor={(item,index) => String(index)}
ListFooterComponent={_renderFooter}
onEndReached={loadMoreOrders}
onEndReachedThreshold={1}
// For pull to refresh
onRefresh={_refresh}
refreshing={isFetch}
/>
);
};
export default OpenedAppointments;
For Issue 1:
either add the dependency to the array:
React.useEffect(() => {
getOpenOrders();
}, [currentPage, getOpenOrders]);
or use eslint rule as answered by #Matt Aft, It wont make a difference for your usecase
For issue 2:
I would suggest removing duplicates with a Set:
setOpenedAppointment((prevOpenedOrders) =>
Array.from(new Set([...prevOpenedOrders, ...allOpenedOrders]))
);
This will concat your new Orders and your old Orders with spread syntax (...) and will remove duplicates by creating a new Set. (Sets allow only unique items, therefore will remove duplicates. Then you convert it back to an Array with Array.from so you can use it as before
I think you're missing the spread operator on the second array:
setOpenedAppointment((prevOpenedOrders) =>[...prevOpenedOrders, ...allOpenedOrders]);
also issue 1 is because of the react-hooks/exhaustive-deps rule you have enabled, basically there are two ways you can fix this:
wrap getOpenOrders in a useCallback and add it to the dep array in the useEffect
disable the linter for that line
React.useEffect(() => {
getOpenOrders();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage]);

Categories