I have the next hook in my react js application:
const {
data,
loading
} = fetchData(info)({
variables: {
id: myId,
},
fetchPolicy: 'no-cache',
});
//
const fetchData = (info) => {
if (a > 1) {
return useGetCars;
}
return useGetColors;
};
The issue appear in the first render when the myId is empty, but it is required. Due this fact i get an error from the server.
Question: How to create a condition for the hook above to be able to run it only when the myId is not empty?
Use the effect hook:
useEffect(() => {
if (myId) {
fetchData() // after this, you can set the required states.
}
}, [myId])
Related
I have a custom hook to redirect users to edit page. On index page I can duplicate items and delete. I can redirect users after duplicate, but the problem is when I delete an item, this custom hook redirects users to edit page again. So I need to find a way to make it work conditionally.
Custom hook:
export default function useDuplicateItem(url: string) {
const { sendRequest: duplicate } = useHttpRequest();
const duplicateItem = useCallback(
(data) => {
duplicate([
{
url: `/api/server/${url}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data,
},
]);
},
[duplicate, url]
);
useRedirectEditPage(url); // This causes the problem
return duplicateItem;
}
index page:
const duplicateItem = useDuplicateItem('documents');
// custom hook rendered here, which is not correct. I want to run it when duplicate function runs.
const duplicate = useCallback(() => {
const data = {
name: copiedName,
sources: singleDocument?.sources,
document: singleDocument?.document,
tool: singleDocument?.tool,
access: singleDocument?.access,
};
duplicateItem(data);
}, [copiedName, duplicateItem, singleDocument]);
useRedirectEditPage:
export default function useRedirectEditPage(slug: string) {
const { saveResponses, setSaveResponses, setHeaderStates } =
useAdminContext();
const router = useRouter();
useEffect(() => {
const statusCodes: number[] = [];
let id;
saveResponses.forEach((value) => {
statusCodes.push(value?.status);
id = value?.id;
});
if (statusCodes.length && id) {
if (statusCodes.includes(404)) {
setHeaderStates((prev) => ({
...prev,
canBeSaved: false,
}));
} else {
router.push(`/admin/${slug}/edit/${id}`);
setSaveResponses(new Map());
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveResponses, router, setSaveResponses]);
}
saveResponses state is coming after I make any request to server, and I am able to get id to redirect users. I use new Map() to set data inside saveResponses.
From the react docs:
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls. (If you’re curious,
we’ll explain this in depth below.)
React relies on the order in which Hooks are called to know which setState corresponds to which state, calling them inside a condition will mess up the previous mechanism.
I would recommend to read the following: https://reactjs.org/docs/hooks-rules.html#explanation
I have error like in title. But I don't know how my dependencie change every render because it is in useEffect. I suspected it was related to the map function so I moved it higher in the hierarchy to "injectEndpoints"->"transformResponse" but that didn't help. I don't know how to change it or maybe that's not the point?
export const parametersApi = myApi
.injectEndpoints({
endpoints: (builder) => ({
getParameters: builder.query<
ExtendedParameter[],
string
>({
query: (id) =>
`/${id}/parameters`,
providesTags: (_result, _error, id) => [
{
type: `Parameters`,
id: `PLIST_${id}`,
},
],
transformResponse: (response: parameter[]) => {
return response.map((param) => {
return { id: param.name, ...param };
});
},
}),
....
export const {
useGetParametersQuery } = parametersApi;
The file in which the error occurs:
const selectedParameters =
useGetParametersQuery(master.id, {
skip: !master,
}).data || [];
...
useEffect(() => {
console.log("selectedParameters", selectedParameters);
setParameters(selectedParameters);
}, [selectedParameters]);
console.log("selectedParameters", selectedParameters);
|| []
creates a new array every render while there is no .data yet - so selectedParameters will be a new reference and trigger an useEffect every time.
Do something like const EMPTY_ARR = [] outside your component and do || EMPTY_ARR to keep a stable reference here.
I have created a function which adds a specific item to my diary. 9/10 times everything works, which means that there is nothing wrong with the code?
However rarely I add the item to my diary, but I don't see the update values, even thought I activated queryClient.invalidateQueries() method, the value is updated on my server, because when I manually refresh I see the updated diary again.
Does this mean that by the time I activate invalidatequeries method, the update has not reached my server and that is why I am seeing stale data? But what would I do in that case?
Here is the function:
const newAddItemFunction = () => {
const day = newDiary?.[currentDay];
if (day && selectedMealNumber && selectedItem) {
setSavingItem(true);
NewAddItemToDiary({
day,
selectedMealNumber,
selectedItem,
});
queryClient.invalidateQueries(["currentDiary"]).then(() => {
toast.success(`${selectedItem.product_name} has been added`);
});
router.push("/diary");
}
};
Here is my custom hook(useFirestoreQuery is just custom wrapped useQuery hook for firebase):
export const useGetCollectionDiary = () => {
const user = useAuthUser(["user"], auth);
const ref = collection(
firestore,
"currentDiary",
user.data?.uid ?? "_STUB_",
"days"
);
return useFirestoreQuery(
["currentDiary"],
ref,
{
subscribe: false,
},
{
select: (data) => {
let fullDaysArray = [] as Day[];
data.docs.map((docSnapshot) => {
const { id } = docSnapshot;
let data = docSnapshot.data() as Day;
data.documentId = id;
fullDaysArray.push(data);
});
fullDaysArray.sort((a, b) => a.order - b.order);
return fullDaysArray;
},
enabled: !!user.data?.uid,
}
);
};
NewAddItemToDiary function is just firebase call to set document:
//...json calculations
setDoc(
doc(
firestore,
"currentDiary",
auth.currentUser.uid,
"days",
day.documentId
),
newDiaryWithAddedItem
);
9/10 times everything works, which means that there is nothing wrong with the code?
It indicates to me that there is something wrong with the code that only manifests in edge cases like race conditions.
You haven't shared the code of what NewAddItemToDiary is doing, but I assume it's asynchronous code that fires off a mutation. If that is the case, it looks like you fire off the mutation, and then invalidate the query without waiting for the query to finish:
NewAddItemToDiary({
day,
selectedMealNumber,
selectedItem,
});
queryClient.invalidateQueries(["currentDiary"]).then(() => {
toast.success(`${selectedItem.product_name} has been added`);
});
Mutations in react-query have callbacks like onSuccess or onSettled where you should be doing the invalidation, or, if you use mutateAsync, you can await the mutation and then invalidate. This is how all the examples in the docs are doing it:
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries('todos')
queryClient.invalidateQueries('reminders')
},
})
I have read several cases on stackoverflow regarding missing dependencies in useEffect:
example :
How to fix missing dependency warning when using useEffect React Hook?
Now the case I have is, I use useEffect for pagination:
Here's the source code:
react-router-dom configuration
{ path: "/note", name: "My Note", exact: true, Component: Note },
Note Component
const Note = (props) => {
const getQueryParams = () => {
return window.location.search.replace("?", "").split("&").reduce((r, e) => ((r[e.split("=")[0]] = decodeURIComponent(e.split("=")[1])), r),
{}
);
};
const MySwal = withReactContent(Swal);
const history = useHistory();
// Get Queries On URL
const { page: paramsPage, "per-page": paramsPerPage } = getQueryParams();
// Queries as state
const [queryPage, setQueryPage] = useState(
paramsPage === undefined ? 1 : paramsPage
);
const [queryPerPage, setQueryPerPage] = useState(
paramsPerPage === undefined ? 10 : paramsPerPage
);
// Hold Data Records as state
const [notes, setNotes] = useState({
loading: false,
data: [],
totalData: 0,
});
useEffect(() => {
console.log(queryPage, queryPerPage);
setNotes({
...notes,
loading: true,
});
// Fetching data from API
NoteDataService.getAll(queryPage, queryPerPage)
.then((response) => {
setNotes({
loading: false,
data: response.data,
totalData: parseInt(response.headers["x-pagination-total-count"]),
});
return true;
})
.catch((e) => {
MySwal.fire({
title: e.response.status + " | " + e.response.statusText,
text: e.response.data,
});
});
return false;
}, [queryPage, queryPerPage]);
const { loading, data, totalData } = notes;
...
So there are two problems here:
There is a warning React Hook use Effect has missing dependencies: 'MySwal' and 'notes'. Either include them or remove the dependency array. You can also do a functional update 'setNotes (n => ...)' if you only need 'notes' in the 'setNotes' call. If I add notes and MySwal as dependencies, it gives me a continuous loop.
When I access the "note" page, the Note component will be rendered.
Then, with pagination: / note? Page = 2 & per-page = 10, it went perfectly.
However, when returning to "/ note" the page does not experience a re-render.
Strangely, if a route like this / note? Page = 1 & per-page = 10, returns perfectly.
Does my useEffect not run after pagination?
First of all, move your API call inside of useEffect. After your data is fetched, then you can change the state.
useEffect(() => {
//Fetch the data here
//setState here
},[]) //if this array is empty, you make the api call just once, when the `component mounts`
Second Argument of useEffect is a dependancy array, if you don't pass it, your useEffect will trigger in every render and update, which is not good. If you parss an empty array, then it makes just one call, if you pass a value, then react renders only if the passed value is changed.
I'm using Vuex to handle my application state.
I need to make an Ajax Get request to a rest api and then show some objects list.
I'm dispatching an action that loads this data from the server but then I don't know how to handle it on the component.
Now I have this:
//component.js
created(){
this.$store.dispatch("fetch").then(() => {
this.objs = this.$store.state.objs;
})
}
But I don't think that the assignment of the incoming data to the local property is the correct way to handle store data.
Is there a way to handle this better? Maybe using mapState?
Thanks!
There are many ways you can do it, you must experiment and find the one that fits your approach by yourself. This is what I suggest
{ // the store
state: {
something: ''
},
mutations: {
setSomething (state, something) {
// example of modifying before storing
state.something = String(something)
}
},
actions: {
fetchSomething (store) {
return fetch('/api/something')
.then(data => {
store.commit('setSomething', data.something)
return store.state.something
})
})
}
}
}
{ // your component
created () {
this.$store
.dispatch('fetchSomething')
.then(something => {
this.something = something
})
.catch(error => {
// you got an error!
})
}
}
For better explanations: https://vuex.vuejs.org/en/actions.html
Now, if you're handling the error in the action itself, you can simply call the action and use a computed property referencing the value in the store
{
computed: {
something () { // gets updated automatically
return this.$store.state.something
}
},
created () {
this.$store.dispatch('loadSomething')
}
}