Problem when I try to run two react-query in a row - javascript

I have two different endpoints, one that is called with getProjectMapping and one with getStaffing. The getProjectMapping query must be run first in order to set the project variable, which will then be used to make the getStaffing request. But I get the following error:
Uncaught TypeError: project is null
I get that error in the getStaffing request, although before activating it I check that the project is not null. Does anyone know what is wrong?
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const [project, setProject] = useState(null);
const {
data: projectMapping,
isLoading: projectMappingIsLoading,
isFetching,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
onSuccess: () => {
if (projectMapping != null && projectMapping.length !== 0) {
setProject(projectMapping[0]);
}
},
});
const { data, isLoading } = useQuery(
[project.value, "Staffing"],
() => getStaffing(project.value, tokenApi),
{
enabled: !isFetching && project != null,
dependencies: [project],
}
);
}

This isn't how you structure dependent queries.. Instead of setting state you should derive it. If you have dependent queries it might also make sense to wrap them in a custom hook
e.g.
const useProjectStaffing = (tokenApi) => {
const {
data: [project] = [],
isLoading: projectMappingIsLoading,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
},
});
const projectValue = project && project.value
return useQuery(
[projectValue, "Staffing"],
() => getStaffing(projectValue, tokenApi),
{ enabled: !!projectValue }
);
}
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const {isLoading, data: staffing} = useProjectStaffing(tokenApi);
// ... do stuff with the staffing data when it comes back.

Related

React Native Firestore: How do I listen for database changes at the same time as using the .where() query?

I have made a FlatList that gets populated from a firestore database. I can currently do all the CRUD operations, but when I edit an entry, it doesn't change in the FlatList. It does change in the firestore database.
I suspect it's because I'm not using .onSnapshot(). My problem is that I need to filter the data using .where() and I haven't been able to find out how to combine the two operations.
My code looks like this:
export const Coach = () => {
const navigation = useNavigation();
const [user, setUser] = useState();
const [userName, setUserName] = useState('');
const [workoutIds, setWorkoutIds] = useState([]);
const [workouts, setWorkouts] = useState([]);
const userRef = firestore().collection('Users');
const workoutRef = firestore().collection('Workouts');
// Setting the user state
auth().onAuthStateChanged(userInstance => {
if (userInstance) {
setUser(userInstance);
}
});
// Getting coach id's from firestore - Started out at individual workout id's
useEffect(() => {
if (user) {
const subscriber = userRef.doc(user.uid).onSnapshot(userSnap => {
if (userSnap) {
setUserName(userSnap.data().Name);
setWorkoutIds(userSnap.data().Workouts);
}
});
return () => subscriber();
}
}, [user]);
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
The problem should lie in the last useEffect block.
So how do I get it to listen for changes and update the FlatList, while still using the .where()?
----------------------------------------- Edit -----------------------------------------
I have tried to add an onSnapshot to my query:
Before:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
It still doesn't update the view straight away and now I get an error about encountering two of the same keys.
To solve the issue I had to add .onSnapshot() to my query for it to listen to changes in the database. On top of that I accidentally put the temporary list that I added objects to, outside the onSnapshot(), so it just kept adding on. After moving the temporary list into the onSnapshot(), it now updates.
Before:
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
useEffect(() => {
if (workoutIds.length != 0) {
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
let workoutList = [];
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);

Rendered fewer hooks than expected. This may be caused by an accidental early return statement

I'm getting this error when triggering a setState inside of a custom React hook. I'm not sure of how to fix it, can anyone show me what I'm doing wrong. It is getting the error when it hits handleSetReportState() line. How should I be setting the report state from inside the hook?
custom useinterval poll hook
export function usePoll(callback: IntervalFunction, delay: number) {
const savedCallback = useRef<IntervalFunction | null>()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
function tick() {
if (savedCallback.current !== null) {
savedCallback.current()
}
}
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}
React FC
const BankLink: React.FC = ({ report: _report }) => {
const [report, setReport] = React.useState(_report)
if ([...Statues].includes(report.status)) {
usePoll(async () => {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}, 5000)
}
... rest
This is because you put usePoll in if condition, see https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
You can put the condition into the callback
usePoll(async () => {
if ([...Statues].includes(report.status)) {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}
}, 5000)
And if the delay will affect report.status, use ref to store report.status and read from ref value in the callback.

How to setup default value of fetched data

I am fetching data from my "backend" CMS - everything works fine, but when I want to setup default value I am getting error of undefined data.
My content is divided into some categories e.g.
const [category1, setCategory1] = useState([]);
const [category2, setCategory2] = useState([]);
Then I am fetching data from backend
useEffect(() => {
const fetchData = async () => {
const result = await client.query(
Prismic.Predicates.at('document.type', 'post'),
{ pageSize: 100 }
);
if (result) {
const category1Arr = [];
const category2Arr = [];
result.results.forEach((post) => {
switch (post.data.category[0].text) {
case 'Category1':
category1Arr.push(post);
break;
case 'Category2':
category2Arr.push(post);
break;
default:
console.warn('Missing blog post category.');
}
});
setCategory1(category1Arr);
setCategory2(category2Arr);
return setDocData(result);
} else {
console.warn(
'Not found'
);
}
};
fetchData();
}, []);
Code above works without any issues, BUT chosen category should have one post opened by default.
I am having menu when you can pick category and therefore I am using activeComponent function.
const [activeComponent, setActiveComponent] = useState('category1');
const modifyActiveComponent = React.useCallback(
(newActiveComponent) => {
setActiveComponent(newActiveComponent);
},
[setActiveComponent]
);
So category1 is active on default, therefore the category should also have default post.
This is what I tried:
const [postTitle, setPostTitle] = useState('');
const [postText, setPostText] = useState([]);
{activeComponent === 'category1' &&
category1.length > 0 && category1.map((post) => {
return ( <button onClick={()=> {setPostTitle(post.data.title[0].text); setPostText(post.data.body)}}
And data are shown typical just as a {postTitle} & {postText}
I tried to put default value in each category like this
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category1[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
But the code above gives me an error or undefined data even though it should be correct.
How can I achieve to make a default value with this logic above? Everything works like charm, just the default data does not work :(
This is array of objects:
In your last piece of code you have a typo, here:
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
it should be:
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category1[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
in the first if statement, in second setPostText, you have category instead of category1.

Reactjs functional component common logic extraction

I have a few functional components with a few common variables and functions. Please see below.
const Customer = () => {
const [isReleased, setIsReleased] = useState(false)
const release = () => {
setIsReleased(true)
}
}
const Order = () => {
const [isReleased, setIsReleased] = useState(false)
const release = () => {
setIsReleased(true)
}
}
As you can see from the above code fragment, the release() function has common logic. It accesses component's variables/functions.
Is there a way to move this release() function to a common file and import it from each component?
Please note that the release() method should be able to access scoped variables and functions of the caller.
Update
Below is the actual content of the release() function. I have put this. to denote that it refers to the variables/functions in the caller.
const release = () => {
if (action === "new") {
history.push(`/customers/new`)
} else if (action === "save") {
(async () => {
try {
if (this.dataMode === "new") {
this.setMessage()
this.setFormStatus("updating")
let _res = await this.customer_api_create(this.formData)
if ((_res.status === 200) && (_res.data.status === "success")) {
this.setFormStatus()
history.replace({ pathname: `/customers/${_res.data.data[0].id}` })
}
} else if (this.dataMode === "edit") {
this.setFormStatus("updating")
this.setMessage()
let _res = await this.customer_api_update(this.formData)
if ((_res.status === 200) && (_res.data.status === "success")) {
this.setFormStatus()
this.setMessage({ type: "info", text: "Saved" })
this.setFormData(_res.data.data[0])
}
}
} catch (e) {
this.openMessageBox({
prompt: e.response.data.message,
type: this.constants.app.MessageBoxType.MB_Error,
buttons: this.constants.app.MessageBoxButton.MB_Ok,
show: true,
setResult: () => console.log(this.constants.app.MessageBoxResult.MB_Ok)
})
this.setFormStatus("updating_error")
}
})();
} else if (action === "del") {
this.openMessageBox({
prompt: `{ "": ["Are you sure you want to delete context customer?"] }`,
type: this.constants.app.MessageBoxType.MB_Warning,
buttons: this.constants.app.MessageBoxButton.MB_YesNo,
show: true,
setResult: (val) => {
this.openMessageBox({ show: false })
this.setShouldRecordDeleted(val)
}
})
}
}
You can declare the function release in a different file and pass the setIsReleased method as an argument
import {release} from 'release';
const Customer = () => {
const [isReleased, setIsReleased] = useState(false)
release(setIsReleased)
}
And in your release.js file
export const release = (setIsReleased) => {
setIsReleased(true)
}
Maybe callback solve the problem, how about this?
anotherfile.js
const release = (param, setter) => {
if (true){
setter(true)
}
}
yourfile.js
import {release} from "anotherfile"
const Customer = () => {
const [isReleased, setIsReleased] = useState(false)
release([parameter],setIsReleased)
}

Reactjs callback get error before error is returned

Hi I'm a beginner in react and I have the following code
import { useCallback } from 'react';
import { useMutation } from '#apollo/client';
import { useCartContext } from '#magento/peregrine/lib/context/cart';
export const useWishlistAllItems = props => {
const { items,mutations } = props;
const { addSimpleWishListItemToCartMutation } = mutations;
const [{ cartId }] = useCartContext();
const [addWishlistItemToCart, { error, loading }] = useMutation(addSimpleWishListItemToCartMutation);
const handleAddAllItemsToCart = useCallback(async () => {
try {
items.map((item,index) => {
if (item.product.__typename !== 'ConfigurableProduct'){
const cartItem = {
data: {
quantity: 1,
sku: item.product.sku
}
};
addWishlistItemToCart({
variables: {
cartId,
cartItem
}
});
}
})
} catch {
return;
}
}, [addWishlistItemToCart, cartId, items]);
const saveWishList = useCallback(async () => {
try {
window.location.reload(false);
}catch {
return;
}
}, []);
return {
handleAddAllItemsToCart,
saveWishList,
hasError: !!error,
isLoading: loading
};
};
The problem is that the error is displayed before the error is returned to my main component.I get the following
I have handled the returned error in my component properly but the problem is I get the error as above before returned to the component.I think It is because of the callback function.Please give me a solution.
Running async method without await is like just starting Task without
monitoring it's progress and results.
So, once again check your code using await and return the appropriate output.

Categories