Search is not being updated - reactjs

I am using the meal database.The data from the link is not being updated after search. But if I console.log the search input, I can see the new link.
That's my API for searching:
API_URL_SEARCH="https://www.themealdb.com/api/json/v1/1/search.php?s="
Thats search page:
function Meals({ navigation}) {
const [searchInput, setSearchInput] = useState('');
const handleChange = (inputText) => {
setSearchInput(inputText);
};
const { loading, error, data } = useFetch(config.API_URL_SEARCH + searchInput);
const handleMealSelect = idMeal => {
navigation.navigate("MealDetail", {idMeal})
}
const renderMeals = ({item}) => <Meal meal={item} onSelect={() => handleMealSelect(item.idMeal)}/>
if(loading) {
return <Loading/>;
}
if(error) {
return <Error/>;
}
return(
<View>
<SearchBar
placeholder="Type Here..."
onChangeText={handleChange}
value={searchInput} />
<FlatList keyExtractor={(meals) => meals.id} data={data.meals} renderItem={renderMeals}/>
</View>
)
}
Thats meal component:
const Meal= ({meal, onSelect}) => {
return(
<TouchableOpacity style={styles.container} onPress={onSelect}>
<ImageBackground
style={styles.image}
source={{uri: meal.strMealThumb}}
imageStyle={{borderTopLeftRadius:10, borderTopRightRadius:10}} />
<Text style={styles.title}>{meal.strMeal}</Text>
</TouchableOpacity>
)
}
Here is useFetch for getting data and getting loading and error situations just in case of.
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const fetchData = async () => {
try {
const {data: responseData} = await axios.get(url);
setData(responseData);
setLoading(false); }
catch (error) {
setError(error.message);
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {error, loading, data};
};

Related

React Context loosing state on page refresh

So basicaly i have a subreddit context where i get a bunch of subreddis from the api
import axios from "axios";
import { createContext, useCallback, useEffect, useState } from "react";
import { getUniqueObjects } from "../Helpers/Helpers";
import { ChildrenType } from "../Types/ProviderChildrenType";
import { Subreddit, SubredditsResponse } from "../Types/Subreddits";
type InitialState = {
subredditsData?: Subreddit[];
subredditsLoading?: boolean;
subredditsError?: boolean;
subredditsHasMore?: boolean;
getSubreddits?: (arg0: string) => void;
};
export const SubredditContext = createContext<InitialState>({});
export const SubredditContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Subreddit[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [hasMore, setHasMore] = useState(true);
const UseSubreddits = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response: SubredditsResponse) => {
setData(getUniqueObjects([...data, ...response.data]));
setHasMore(response.data.length > 0);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => apiCall(), [apiCall]);
};
return (
<SubredditContext.Provider
value={{
subredditsData: data,
subredditsLoading: loading,
subredditsError: error,
subredditsHasMore: hasMore,
getSubreddits: UseSubreddits,
}}
>
{children}
</SubredditContext.Provider>
);
};
In my home page I trigger the custom hook of the context "UseSubreddits" which I pass it as the "getSubreddits" prop,
function Homepage() {
const navigate = useNavigate();
const [pageNumber, setPageNumber] = useState(1);
const {
subredditsData,
subredditsLoading,
subredditsError,
subredditsHasMore,
getSubreddits,
} = useContext(SubredditContext);
getSubreddits!(`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=${pageNumber}&limit=16`)
window.addEventListener("scroll", () => {
if (
window.scrollY + window.innerHeight >=
document.documentElement.scrollHeight &&
subredditsHasMore
) {
setPageNumber(pageNumber + 1);
}
});
return (
<>
<Navbar pageTitle="subreddits" />
<div className="homepage">
<div className="homepage__subreddits">
{subredditsData?.map((item) => {
return (
<div key={item.id}>
<SubredditCard
key={item.id}
onClick={() => navigate(`/posts/${item.id}`)}
title={item.title}
description={item.description}
/>
</div>
);
})}
</div>
</div>
<div className="homepage__text">
{subredditsLoading && <h2>Loading...</h2>}
{subredditsError && (
<h2>An error has occured please refresh your page.</h2>
)}
</div>
</>
);
}
export default Homepage;
I have the same kind of context file where I get the posts of the selected subreddit
type InitialState = {
postData?: Post[];
postLoading?: boolean;
postError?: boolean;
getPost?: (arg0: string) => void;
voteHandler?: (
arg0: string,
arg1: string,
arg2: boolean,
arg3: boolean
) => void;
};
export const PostContext = createContext<InitialState>({});
export const PostContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const UsePosts = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => {
apiCall();
}, [apiCall]);
};
return (
<PostContext.Provider
value={{
postData: data,
postLoading: loading,
postError: error,
getPost: UsePosts,
}}
>
{children}
</PostContext.Provider>
);
};
and then I do the same thing in the Post component as I do in the Homepage
function Posts() {
const { subredditId } = useParams();
const [urlParam, setUrlParam] = useState("");
const {
postData,
postLoading,
postError,
getPost,
voteHandler,
} = useContext(PostContext);
const { subredditsData } = useContext(SubredditContext);
const selectedSubreddit = useMemo(
() => subredditsData!.find((subreddit) => subreddit.id === subredditId),
[subredditsData]
);
const navigate = useNavigate();
const sortByTitle = "?sortBy=title";
getPost!(
`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits/${subredditId}/posts${urlParam}`
);
return (
<>
<Navbar pageTitle={selectedSubreddit!.title} />
<div className="posts-screen">
<div className="posts-screen__left-panel">
<div className="posts-screen__left-panel-content">
<SortBy onClick={() => setUrlParam(sortByTitle)} />
<div className="posts-screen__posts-container">
{postData?.map((post) => {
return (
<PostCard
key={post.id}
id={post.id}
title={post.title}
description={post.body}
user={post.user}
voteCount={post.upvotes - post.downvotes} ...
Everything works fine except that in the post screen if I reload the page the subreddit state is lost and the posts screen gives and error. I know that the state is lost on refresh but shouldn't it make the api call again for the subreddits?
I'm new at Context so I don't know how to handle it better

How to display ActivityIndicator untill all elements are mapped

I have this screen in which I want to see ActivityIndicator untill all devices are mapped (not fetched):
const MyScreen = () => {
const [devices, setDevices] = useState();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getDevices();
}, []);
const getDevices = async () => {
const pulledDevices = await fetchDevices();
setDevices(pulledDevices)
setIsLoading(false)
};
if (isLoading)
return (
<ActivityIndicator />
);
return (
<View >
{devices?.map((device) => {
return (
<View>
<Text>{device.name}</Text>
</View>
);
})}
</View>
);
};
Mapping these devices takes some time.
How could I implement here ActivityIndicator untill all devices are mapped.
I suggest you to use a bit more sophisticated async await hook to handle this.
useAsyncHook.js
const useAsync = asyncFunction => {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(async () => {
setLoading(true);
setResult(null);
setError(null);
try {
const response = await asyncFunction();
setResult(response);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { loading, result, error };
};
This is a raw use async hook that can be enhanced many way but it handles the loading state correctly in this state.
Usage:
const { loading, result, error } = useAsync(yourFunction);
if (loading) return null;
return <Component />;

Cannot get datas on json with flatlist

I am using the Free Meal API with flatlist. I have Category component, Categories page, useFetch hook. I can't see Flatlist on screen. I can get console log of data but I can't reach datas with flatlist.
.env folder:
API_URL_CATEGORIES="https://www.themealdb.com/api/json/v1/1/categories.php"
API_URL_FILTER="https://www.themealdb.com/api/json/v1/1/filter.php?"
useFetch hook for getting the data in URL and returning Loading icon, Error if URL doesn't work and data for data in URL.
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const fetchData = async () => {
try {
const {data: responseData} = await axios.get(url);
setData(responseData);
setLoading(false); }
catch (error) {
setError(error.message);
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {error, loading, data};
};
export default useFetch;
Category component:
const Category= ({category, onSelect}) => {
return(
<TouchableOpacity style={styles.container} onPress={onSelect}>
<Image
style={styles.image}
source={{uri:category.strCategoryThumb}} />
<Text style={styles.title}>{category.strCategory}</Text>
</TouchableOpacity>
)
}
export default Category;
Categories page:
const Categories = ({navigation}) => {
const { error, loading, data } = useFetch(config.API_URL_CATEGORIES);
console.log(data)
const handleCategorySelect = strCategory => {
navigation.navigate("Detail", {strCategory})
}
const renderCategory = ({item}) => <Category category={item} onSelect={() => handleCategorySelect(item.strCategory)}/>;
if(loading) {
return <Loading/>;
}
if(error) {
return <Error/>;
}
return(
<View style={styles.container}>
<FlatList data={data} renderItem={renderCategory}/>
<Text>Categorises</Text>
</View>
)
}
export default Categories;
I think data is actually object that contains a property categories , which holds an array.
try data.categories and I believe this should work fine.

Search flatlist

I am displaying a list of contacts from my address book in a flatlist and want to be able to search the list. The issue is that initially the list is empty and I get an error because it is trying to filter undefined.
If I type a name though it works and if I delete my search query it then shows all users. I would like it do this from the start. I am not sure why it is undefined intially, perhaps because the state has not yet been set.
const AddContactScreen = ({ navigation }) => {
const [contacts, setContacts] = useState();
const [query, setQuery] = useState("");
const [filteredContactList, setFilteredContactList] = useState(contacts);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
sort: Contacts.SortTypes.FirstName,
});
if (data.length > 0) {
setContacts(data);
}
const newContacts = contacts.filter((item) =>
item.name.includes(query)
);
setFilteredContactList(newContacts);
}
})();
}, [query]);
return (
<Screen>
<FlatList
ListHeaderComponent={
<View style={styles.searchContainer}>
<TextInput
style={styles.searchField}
placeholder="Search"
onChangeText={setQuery}
value={query}
/>
</View>
}
data={filteredContactList}
ItemSeparatorComponent={ListItemSeparator}
keyExtractor={(contact) => contact.id.toString()}
renderItem={({ item }) => (
<ListItem
title={item.name}
onPress={() => console.log("contact selected", item)}
/>
)}
/>
</Screen>
);
};
const styles = StyleSheet.create({
searchContainer: {
padding: 15,
},
searchField: {
borderRadius: 15,
borderColor: "gray",
borderWidth: 1,
padding: 10,
},
});
export default AddContactScreen;
The best practice would be to set your initial value of contacts to an empty array []
So your state would be
const [contacts, setContacts] = useState([]);
const [contacts, setContacts] = useState();
const [query, setQuery] = useState("");
const [filteredContactList, setFilteredContactList] = useState(contacts);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
setLoading(true);
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
sort: Contacts.SortTypes.FirstName,
});
setLoading(false);
setContacts(data.length ? data : []);
}
})();
}, [query]);
useEffect(() => {
if(contacts.length) {
const newContacts = contacts.filter((item) =>
item.name.includes(query)
);
setFilteredContactList(newContacts);
}
}, [contacts])
if(loading) return <p>loading...</p>;
if(!contacts.length) return <p>No data found</>;
// your rest of the code.
NOTE: i have written the code from the imagination. You might need to do some tweak.
I would use useEffect when component mounts to get initial contacts and handle the query in a separate function:
useEffect(() => {
getInitialContacts()
}, []);
const getInitialContacts = async () => {
setLoading(true);
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
sort: Contacts.SortTypes.FirstName,
});
setLoading(false);
setContacts(data.length ? data : []);
}
});
}
you can use another function that you call when text is entered:
const handleQuery = (query) => {
const newContacts = contacts.filter((item) =>
item.name.includes(query)
);
setFilteredContactList(newContacts);
}

ReactJS sending data to modal from map items

I have a map that render few items and i need when one element from map slected modal should load data about this selected items' id inside modal.
Like that:
<ListGroup>
{userinfo.map(item =>
(
<>
<ListGroup.Item key={item.id} onClick={handlePassInfoShow}>
{item.name}</ListGroup.Item>
</>
)
)}
</ListGroup>
<ModalPassInfo
modelClose={() => handlePassInfoClose()}
modelShow={showPaaInfo}
//id={item.id}
setshowPaaInfo={setshowPaaInfo}
/>
Here I am mapping through the user's array and adding a listgroup item to each of them with onClick modal. Now, whenever something is clicked inside map, the modal should be opened and read data about selected item.
And my modal like that.
const ModalPassInfo = ({modelShow, modelClose, id, showPaaInfo}) => {
const ref = React.createRef();
const [isError, setError] = useState(false);
const [isLoading, setLoading] = useState(true);
const [country_list, setCountries] = useState([]);
const [message, setMessage] = useState("");
const [data, setData] = useState({
//data about user
});
useEffect(() => {
loadNetwork();
}, []);
const loadNetwork = () => {
setLoading(true);
setError(false);
const selector = api.getItems("selector", {
tables: "country_list"
}).then(res => {
let response = res.data;
setCountries(response.country_list);
});
const data = api.getItems(`user-info/${id}`, {
}).then(res => {
let response = res.data;
setData(response);
});
Promise.all([selector, data]).then(res => {
console.log(res);
setError(false);
setLoading(false);
}).catch(e => {
console.log(e);
setMessage(e.toString());
setLoading(false);
setError(true);
});
};
const onRefresh = () => {
loadNetwork();
};
if (isError) {
return <ErrorMessage message={message} onRefresh={onRefresh}/>
}
if (isLoading) {
return <Loader/>
}
If I go to the page, the modal is loading immediately. And during onClick, only the last item id is retrieved.
And moy consts
const [showPaaInfo, setshowPaaInfo] = useState(false);
const handlePassInfoClose = () => setshowPaaInfo(false);
const handlePassInfoShow = () => {
setshowPaaInfo(true)
};
My question is. Any item on the map should send an id to the modal when the item is clicked. Where am I wrong?
Define one state
const [show, setShow] = React.useState(false);
function
const handlePassInfoShow = (data){
setShow(true);
console.log(data);
}
Change this to
<ListGroup>
{userinfo.map(item =>
(
<>
<ListGroup.Item key={item.id} onClick={()=>handlePassInfoShow(item)}>
{item.name}</ListGroup.Item>
</>
)
)}
</ListGroup>
{show && ( <ModalPassInfo
modelClose={() => handlePassInfoClose()}
modelShow={showPaaInfo}
//id={item.id}
setshowPaaInfo={setshowPaaInfo}
/>
)}

Resources