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]);
Related
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
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>
);
};
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!
I am using React context to pass down data, following the docs, however I am stuck with the initial value and am not sure what I did wrong.
This is what my context file looks like:
export const ItemsContext = createContext([]);
ItemsContext.displayName = 'Items';
export const ItemsProvider = ({ children }) => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const setData = async () => {
setLoading(true);
setItems(await getItemsApiCall());
setLoading(false);
};
useEffect(() => {
setData();
}, []);
console.warn('Items:', items); // This shows the expected values when using the provider
return (
<ItemsContext.Provider value={{ items, loading }}>
{children}
</ItemsContext.Provider>
);
};
Now, I want to feed in those items into my app in the relevant components. I am doing the following:
const App = () => {
return (
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
);
}
However, as my comment states, items will always be empty. On the other hands, if I do just use the ItemsProvider, I do get the proper value, but at this point need to access it directly on the app, so the ItemsContext.Consumer seems to make more sense.
Am I doing something wrong here?
Edit: A way around it seems to be to wrap the Consumer with the Provider, but that feels wrong and didn't see that at the docs. Is that perhaps the case?
So essentially, something like this:
const App = () => {
return (
<ItemsProvider>
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
</ItemsProvider>
);
}
You have to provide a ItemsContext provider above the App component hierarchy,otherwise the default value of the context will be used.
something in this form:
<ItemsContext.Provider value={...}>
<App/>
</ItemsContext.Provider>
When state is in a hook it can become stale and leak memory:
function App() {
const [greeting, setGreeting] = useState("hello");
const cb = useCallback(() => {
alert("greeting is " + greeting);
}, []);
return (
<div className="App">
<button onClick={() => cb()}>Click me</button>
<p>
Click the button above, and now update the greeting by clicking the one
below:
</p>
<button onClick={() => setGreeting("bye")}>
Update greeting
</button>
<p>Greeting is: {greeting}</p>
<p>
Now click the first button again and see that the callback still has the
old state.
</p>
</div>
);
}
Demo: https://codesandbox.io/s/react-hook-stale-datamem-leak-demo-9pchk
The problem with that is that we will run into infinite loops in a typical scenario to fetch some data if we follow Facebook's advice to list all dependencies always, as well as ensure we don't have stale data or memory leaks (as the example showed above):
const [state, setState] = useState({
number: 0
});
const fetchRandomNumber = useCallback(async () => {
if (state.number !== 5) {
const res = await fetch('randomNumber');
setState(v => ({ ...v, number: res.number }));
}
}, [setState, state.number]);
useEffect(() => {
fetchRandomNumber();
}, [fetchRandomNumber]);
Since Facebook say we should list fetchRandomNumber as a dependency (react-hooks/exhaustive-deps ESLint rule) we have to use useCallback to maintain a reference, but it regenerates on every call since it both depends on state.number and also updates it.
This is a contrived example but I've run into this many times when fetching data. Is there a workaround for this or is Facebook wrong in this situation?
Use the functional form of the state setter:
const fetchData = useCallback(async () => {
const res = await fetch(`url?page=${page}`);
setData((data) => ([...data, ...res.data]));
setPage((page) => page + 1);
}, [setData, setPage]);
Now you don't need data and page as your deps
You can also use a ref to run the effect only on mount :
const mounted = useRef(false);
useEffect(() => {
if(!mounted.current) {
fetchSomething();
mounted.current = true;
}
return () => { mounted.current = false }
}, [fetchSomething]);
And
const fetchSomething = useCallback(async () => {
...
}, [setData, setPage, data, page]);
fetchSomething is not a dependency here. You don't want to retrigger the effect, you only cause it once when the component mounts. Thats what useEffect(() => ..., []) is for.