export const useDeleteArticles = ({ ids, onSuccess }) => {
const queryResult = useQueries(
ids.map(id => ({
queryKey: ["article-delete", id],
queryFn: () => articlesApi.destroy(id),
}))
);
const isLoading = queryResult.some(result => result.isLoading);
if (!isLoading) {
onSuccess();
}
return { isLoading, queryResult };
};
This customHook will simply delete some articles.
I tried to use enabled with a state as following.
export const useDeleteArticles = ({ ids, onSuccess, enabled }) => {
const queryResult = useQueries(
ids.map(id => ({
queryKey: ["article-delete", id],
queryFn: () => articlesApi.destroy(id),
enabled,
}))
);
const isLoading = queryResult.some(result => result.isLoading);
if (!isLoading) {
onSuccess();
}
return { isLoading, queryResult };
};
const [enabled, setEnabled] = useState(false);
useDeleteArticles({ ids, onSuccess: refetch, enabled });
enabled && setEnabled(false); //to avoid api call after deleting the articles
const handleArticleDelete = () => { //this function will invoke onClick button
setEnabled(true);
};
But this not making the api call.
could anyone help me to implement this in correct way.
Thank you.
This customHook will simply delete some articles.
a delete operation is almost never a query, but a mutation. Mutations can be fired imperatively by calling the mutate function returned from useMutation. A query is the wrong thing to use for this.
Related
so im doing a little application of a pokedex utilizing the pokeapi to test react query, what i am trying to do is first fetch the data on mount and after that, if i use the search button the data already fetched to change something like this
fetch on mount
and the search
search fetch
something easy to do with useState but i am having problems with react query
i got something like this
pokedex
at the mount of the component i have this
export const fetchPokemon = async (URL: string) => {
const result = await axios.get<pokemon>(URL);
return result;
};
export const fetchPokemons = async () => {
const URL = "https://pokeapi.co/api/v2/pokemon?limit=20&offset=0s";
const { data } = await axios.get<pokemons>(URL);
const result = await Promise.all(
data.results.map(async (pokemon) => {
return fetchPokemon(pokemon.url);
})
);
return result;
};
export const useAllPokemons = () => {
return useQuery({
queryKey: ["pokemons"],
queryFn: fetchPokemons,
});
};
const { data, isLoading } = useAllPokemons();
works great but now i want to search pokemons with a search button like in the image and to replace the initial data that i already fetch so only the data that i searched appears so i did this
export const fetchAllPokemons = async () => {
const URL = "https://pokeapi.co/api/v2/pokemon?limit=100";
const { data } = await axios.get<pokemons>(URL);
const result = await Promise.all(
data.results.map(async (pokemon) => {
return fetchPokemon(pokemon.url);
})
);
return result;
};
let { data, refetch } = useQuery({
queryKey: ["pokemons"],
queryFn: fetchAllPokemons,
enabled: false,
select: (data) => {
const pokemonData = data.map((pokemon) => {
if (pokemon.data.name.startsWith("char")) {
return pokemon;
}
});
return pokemonData;
},
});
<button
onClick={() => {
refetch();
}}
>
asd
</button>
and nothing happens, but when i open the console the data is changing but then again returns to the initial fetch
I guess you should use the onSuccess:
const {data} = useQuery("fetchData", fetchData, {
onSuccess: (data) => {
// do something with your data and return
}
});
Here is my setup:
const ParentComponent = ({ post_id }) => {
const { data: post } = useGetPost(post_id); // custom hook with useQuery under the hood
return (
<div>
{post.status === 'published' && (
<div className="margin-bottom">
<ChildComponent post_id={post_id} />
</div>
)}
</div>
);
};
const ChildComponent = ({ post_id }) => {
const { data: post } = useGetPost(post_id);
return <div>{post.title}</div>;
};
Whenever i change post status to 'published' – React Query spawns two getPost requests, first of which, for some reason, returns previous post status and second returns the right one. Problem is gone if i move post.status === 'published' check to ChildComponent, but it's not an option because of ChildComponent wrapper element ("margin-bottom") inside ParentComponent. Is there something i don't get?
UPDATE:
useGetPost hook:
export const useGetPost = (post_id) => {
return useQuery<IDiscussion, Error>(
['posts', post_id],
async () =>
await axios
.get(`/api/posts/${post_id}`)
.then((response) => response.data),
);
};
And my mutation function:
export const useUpdatePost = (post_id) => {
const queryClient = useQueryClient();
return useMutation(
async (data) => await axios.put(`/api/posts/${post_id}`, data),
{
onMutate: (data) => {
// updating react query cache before actual save
queryClient.setQueryData(['posts', post_id], (oldData) => ({
...oldData,
data,
}));
},
onSuccess: () => {
queryClient.refetchQueries(['posts', post_id]);
},
},
);
};
ChildComponent mounts after you manually set new query data in "onMutate". The query refetches on mount (default behavior), but mutation is not yet finished, so you see the old status. Then, on mutation success query refetches again and you see updated status.
That is why if your ChildComponent gets mounted unconditionally, you see only one request. You can just get rid of onMutate function and refetch the query only once, after mutation success.
Problem
I am new to React and am trying to build an application whereby logged in users can view posts they have created. I am having issues with asynchronous functions causing variables to be accessed before they are loaded in. I am using a Firestore database.
Code
I followed this tutorial to set up authentication. I have created an AuthContext.js file, which contains this code (reduced):
const AuthContext = createContext();
export const AuthContextProvider = ({children}) => {
const [user, setUser] = useState({});
// const googleSignIn = () => {...}
// const logOut = () => {...}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
}
}, []);
return (
<AuthContext.Provider value={{ googleSignIn, logOut, user }}>
{children}
</AuthContext.Provider>
)
};
export const UserAuth = () => {
return useContext(AuthContext);
}
I then wrap my application with a AuthContextProvider component and import UserAuth into any component that I want to be able to access the user object from. I have a PostPage component, and in it I want to ONLY render posts created by the logged in user. Each post has a user property containing the uid of the author. Here is my code:
import { UserAuth } from './context/AuthContext'
const PostsPage = () => {
const { user } = UserAuth();
const [posts, setPosts] = useState([]);
const postsRef = collection(db, 'posts');
useEffect(() => {
const getData = async () => {
if (user) {
const q = query(postsRef, where('user', '==', user.uid));
const data = await getDocs(q);
const filtered = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
setPosts(filtered);
}
}
return () => {
getData();
}
}, [user]);
return (
// Display posts
)
}
export default PostsPage;
Upon immediately refreshing the page, getData is executed. However, the code wrapped in the if statement does not run because the user has not yet been loaded in. Yet despite the dependancy array, getData is not executed again once the user data loads in, and I can't figure out why. If I render the user's uid, e.g. <p>{ user.uid }</p>, it will soon appear on the screen after the data has been loaded. But, I cannot figure out how to trigger getData after the user has been loaded. Any help with this would be much appreciated, thanks.
You have an issue just because you put getData() call to the cleanup function of a hook. Cleanup function will execute on depsArray change but it will be executed with old data, closure captured. So when user changes from undefined => any - getUser will be called and will still have a closure-captured user set to undefined. You can clear the array instead in it, so if user logs out - dont show any messages
useEffect(() => {
const getData = async () => {
if (!user) return;
const q = query(postsRef, where("user", "==", user.uid));
const data = await getDocs(q);
const filtered = data.docs.map((doc) => ({
...doc.data(),
id: doc.id
}));
setPosts(filtered);
};
getData().catch(console.error);
return () => {
setPosts([]);
};
}, [user]);
React code
import React, { useEffect, useState } from "react";
import { getDocs, collection } from "firebase/firestore";
import { auth, db } from "../firebase-config";
import { useNavigate } from "react-router-dom";
function Load() {
const navigate = useNavigate();
const [accountList, setAccountList] = useState([]);
const [hasEmail, setHasEmail] = useState(false);
const accountRef = collection(db, "accounts");
Am i using useEffect correctly?
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
checking whether email exists
const emailCheck = () => {
if (accountList.filter((e) => e.email === auth.currentUser.email)) {
setHasEmail(true);
} else {
setHasEmail(false);
}
};
Redirecting based on current user
const direct = () => {
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
return <div></div>;
}
The code compiles but doesn't redirect properly to any of the pages.
What changes should I make?
First question posted excuse me if format is wrong.
There are two problems here:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
In order:
Since getAccounts is asynchronous, you need to use await when calling it.
But even then, setting state is an asynchronous operation too, so the account list won't be updated immediately after getAccounts completes - even when you use await when calling it.
If you don't use the accountList for rendering UI, you should probably get rid of it as a useState hook altogether, and just use regular JavaScript variables to pass the value around.
But even if you use it in the UI, you'll need to use different logic to check its results. For example, you could run the extra checks inside the getAccounts function and have them use the same results as a regular variable:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
const result = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setAccountList(result);
emailCheck(result);
direct();
};
getAccounts();
}, []);
const emailCheck = (accounts) => {
setHasEmail(accounts.some((e) => e.email === auth.currentUser.email));
};
Alternatively, you can use a second effect that depends on the accountList state variable to perform the check and redirect:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
});
useEffect(() => {
emailCheck();
direct();
}, [accountList]);
Now the second effect will be triggered each time the accountList is updated in the state.
I have a question about useEffect. My useEffect is not fetching the data the first time, I have to switch route for it to have the data I needed
const Comments = ({ ...rest }) => {
const theme = useTheme();
const classes = useStyles({ theme });
const [users, setUsers] = useState([]);
const { push } = useHistory();
const { token, loading } = useContext(AuthContext)
const dispatch = useDispatch();
const allUsers = useSelector(state => state.allUsers);
const comments = useSelector(state => state.listCommentsByBookId);
const listBooks = useSelector((state) => state.userListBooks);
const isFetching = useSelector((state) => state.isFetching);
const [stateReady, setReadyForRender] = useState(false)
const redirectTo = ( rowData ) => {
push({
pathname: ROUTE.USERS_DETAILS,
user: rowData
});
}
const options = {
filterType: 'checkbox',
selectableRowsHeader: false,
selectableRowsHideCheckboxes: false,
selectableRowsOnClick: false,
onRowClick: redirectTo,
};
const getAllComments = async () => {
var allusersId = [];
//get all ids
await allUsers.map((user) => {
allusersId.push(user.uid);
})
//get all books from users
await allusersId.map(async (id) => {
await dispatch(getUserListBooks(apiURL + `api/bdd/userListBooks/${id}`, token))
})
var listArray = [];
//filter the array and delete empty rows
listArray.push(listBooks);
var newArray = listArray.filter(e => e);
//map every user and stock the list of books in string
await newArray.forEach(async (book)=> {
await book.map(async (book) => {
await dispatch(getCommentsByBookId(apiURL + `api/bdd/ratingByBook/${book.id}`, token));
})
})
setReadyForRender(true)
}
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
}, [stateReady])
console.log('COM', comments);
return (
<div>
{stateReady &&
<Card>
<Box className={classes.tableContainer} sx={{ minWidth: 1050 }}>
<MUIDataTable
data={comments}
columns={columns}
options={options}
/>
</Box>
</Card>}
</div>
);
};
Why? It might be related to async await but I'm stuck here.
If you want to fetch these informations on the first render, you'll have to pass an empty array as the second parameter of your useEffect.
The reason your useEffect is not called is because stateReady does not change during the course of your current code.
See this link, particularly the note section, it explains way better than me how the empty array as second parameter works.
Can you replace the useEffect section to the below code:
useEffect(() => {
(async () => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
})()
}, [stateReady])
You can read more about this in this link
You can use eslint to show errors when coding with hooks. In this case if you want useEffect to handle stateReady, please provide it in the function getAllComments() => getAllComments(stateReady) and when you call this function in useEffect with [stateReady] as dependencies, it'll work.
You should remove stateReady from your dependency array in the useEffect hook. Adding variables in the dependency array means that the use Effect hooks fires only when one of the dependencies changes. Here's how to use useEffect as lifecycle methods https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
});