I have a very basic prototype of app that allows to book a seat. User selects the seat/seats, clicks book, patch request with available: false is sent to the fake api (json-server) with React Query, library invalidates the request and immediately shows response from the server.
Database structure looks like this:
{
"hallA": [
{
"id": 1,
"seat": 1,
"available": false
},
{
"id": 2,
"seat": 2,
"available": true
},
{
"id": 3,
"seat": 3,
"available": false
}
]
}
and the logic for selecting, booking seats looks like this:
const App = () => {
const { data, isLoading } = useGetHallLayout("hallA");
const [selected, setSelected] = useState<
{ id: number; seat: number; available: boolean }[]
>([]);
const handleSelect = useCallback(
(seat: { id: number; seat: number; available: boolean }) => {
const itemIdx = selected.findIndex((element) => element.id === seat.id);
if (itemIdx === -1) {
setSelected((prevState) => [
...prevState,
{ id: seat.id, seat: seat.seat, available: !seat.available },
]);
} else {
setSelected((prevState) =>
prevState.filter((element) => element.id !== seat.id)
);
}
},
[selected]
);
const takeSeat = useTakeSeat({
onSuccess: () => {
useGetHallLayout.invalidate();
},
});
const sendRequest = useCallback(() => {
selected.forEach((item) =>
takeSeat.mutateAsync({ id: item.id, hall: "hallA" })
);
setSelected([]);
}, [selected, takeSeat]);
return (
<>
{!isLoading && (
<ConcertHall
layout={data}
onSeatSelect={handleSelect}
activeSeats={selected}
/>
)}
<button disabled={isLoading} onClick={sendRequest}>
Take selected
</button>
</>
);
};
Queries look like this:
export const useGetHallLayout = (hall: string) => {
const { data, isLoading } = useQuery(["hall"], () =>
axios.get(`http://localhost:3000/${hall}`).then((res) => res.data)
);
return { data, isLoading };
};
export const useTakeSeat = (options?: UseMutationOptions<unknown, any, any>) =>
useMutation(
(data: { hall: string; id: number }) =>
axios.patch(`http://localhost:3000/${data.hall}/${data.id}`, {
available: false,
}),
{
...options,
}
);
useGetHallLayout.invalidate = () => {
return queryClient.invalidateQueries("hall");
};
The problem of the above code is that I perform very expensive operation of updating each id in a for each loop (to available: false) and query invalidates it after each change not once all of them are updated.
The question is: is there any better way to do this taking into account the limitations of json-server? Any batch update instead of sending request to each and every id seperately? Maybe some changes in a logic?
Thanks in advance
You can certainly make one mutation that fires of multiple requests, and returns the result with Promise.all or Promise.allSettled. Something like:
useMutation((seats) => {
return Promise.allSettled(seats.map((seat) => axios.patch(...))
})
then, you would have one "lifecycle" (loading / error / success) for all queries together, and onSuccess will only be called once.
Another gotcha I'm seeing is that you'd really want the hall string to be part of the query key:
- useQuery(["hall"], () =>
+ useQuery(["hall", hall], () =>
Related
I have comment section where people can talk and currently when I post comment, I have to refresh a page to see my new comment.
I’m really new to meteor so I’d like to know what steps should I take to invest this behavior.
client-side
const CommentList = ({ cardId }) => {
const query = useMemo(
() => ({
$or: [
{ commentType: COMMENT_TYPE.smsComment },
{
$and: [
{ commentType: COMMENT_TYPE.cardComment },
{ createdAt: { $gte: System.getChatCommentsDate() } }
]
}
]
}),
[]
);
const isCommentsLoading = useSubscribe("card.comments", cardId);
const comments = useFind(
() =>
CardComments.find(
{
cardId,
...query
},
{ sort: { createdAt: 1 } }
),
[cardId, isCommentsLoading()]
);
const memoizedComments = useMemo(
() =>
comments?.map(({ _id, userId }) => <Comment key={_id} commentId={_id} />),
[comments, reporterId]
);
return <>{isCommentsLoading() ? <LoadingIcon /> : memoizedComments}</>;
};
serverside/publication
Meteor.publish(
'card.comments',
(
cardId,
query = {
commentType: {$in: [COMMENT_TYPE.cardComment, COMMENT_TYPE.smsComment]},
},
) => {
check(cardId, String);
check(query, Object);
return CardComments.find(
{
cardId,
...query,
},
{
sort: {createdAt: -1},
},
);
},
);
So far, I read an article about useSubscribe and useFind
https://blog.meteor.com/how-to-use-meteor-with-react-hooks-7e1791daabab
I made sure after I hit a submit comment button, comment is store in a database.
Also I looked at the meteor extension on chrome and Subscriptions tab. I see card.comments is subscribed.
in Minimongo tab, I only see three card_comments documents existing. but I have 8 comments displayed on the screen.
After I submitted the comment, I see nothing happens in DDP tab.
What do you expect happening here? or what else can I do?
When I run my react-project on my local machine, useQuery doesn't fetch data multiple-time, it renders data only on first-time loading.
const [todayAttendance, setTodayAttendance] = React.useState([]);
const {
data: allAttendance,
isLoading: attendanceIsLoading,
isError: attendanceIsError,
} = useQuery(
["todaysAbsent", branchId],
async () =>
myAxios(`/all_attendance/${moment().format("MM")}/${moment().format(
"YYYY"
)}/?branch_id=${branchId ?? ""}`
),
{
onSuccess: (e) => {
setTodayAttendance([]);
Object.values(e.data).filter(
({ attendance, name, position, information_user }, i) =>
setTodayAttendance((prev) => [
...prev,
{
attendance: attendance[`${moment().format("YYYY-MM-DD")}`],
id: i,
status:
attendance[`${moment().format("YYYY-MM-DD")}`]["status"],
name: name,
position: position,
information_user: information_user,
},
])
);
},
}
I want to display by default only data where the status are Pending and Not started. For now, all data are displayed in my Table with
these status: Good,Pending, Not started (see the picture).
But I also want to have the possibility to see the Good status either by creating next to the Apply button a toggle switch : Show good menus, ( I've made a function Toggle.jsx), which will offer the possibility to see all status included Good.
I really don't know how to do that, here what I have now :
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
Here my CodeSandbox
Here a picture to get the idea:
Here's the second solution I proposed in the comment:
// Setting up toggle button state
const [showGood, setShowGood] = useState(false);
const [menus, setMenus] = useState([]);
// Simulate fetch data from API
useEffect(() => {
async function fetchData() {
// After fetching data with axios or fetch api
// We process the data
const goodMenus = dataFromAPI.filter((i) => i.taste === "Good");
const restOfMenus = dataFromAPI.filter((i) => i.taste !== "Good");
// Combine two arrays into one using spread operator
// Put the good ones to the front of the array
setMenus([...goodMenus, ...restOfMenus]);
}
fetchData();
}, []);
return (
<div>
// Create a checkbox (you can change it to a toggle button)
<input type="checkbox" onChange={() => setShowGood(!showGood)} />
// Conditionally pass in menu data based on the value of toggle button "showGood"
<Table
data={showGood ? menus : menus.filter((i) => i.taste !== "Good")}
/>
</div>
);
On ternary operator and filter function:
showGood ? menus : menus.filter((i) => i.taste !== "Good")
If button is checked, then showGood's value is true, and all data is passed down to the table, but the good ones will be displayed first, since we have processed it right after the data is fetched, otherwise, the menus that doesn't have good status is shown to the UI.
See sandbox for the simple demo.
I have been working with React-table for a couple of days now, my first time using it, and I have ran into some issues I can't quite seem to resolve. I am trying to build a table where I can show data from two API get requests at the same time, and since I don't know if there is a way to connect the two requests data into one object, and I wouldn't know how to do it, I was trying to access some of the data with get requests inside the react-table Column Cell itself.
My case being: I have two objects, Contacts and Institutions, contacts have in their data the institution ID as parameter, and I need to show in the table both the contact information and some information of the institution that is linked to it, getting it from the institution ID that is present in the contact data.
Here is one example of contact:
{
"contact_id": "34378a25-fe8c-4c64-bd35-59eab3f30863",
"institution_id": "ae1d0fe8-cce1-40ef-87d7-729dfbe9716d",
"name": "Contato 2",
"role": "Cargo 1",
"phone_numbers": [],
"emails": [],
"createdAt": "2021-03-09T20:40:26.6863764Z",
"updatedAt": "2021-03-09T20:40:26.686376448Z",
"deleted": false
}
And here is the institution data:
{
"institution_id": "ae1d0fe8-cce1-40ef-87d7-729dfbe9716d",
"name": "Matheus Salles Blanco",
"socialReason": "teste",
"cnpj": "99999999999999",
"abbreviation": "Matheus",
"website": "teste.com",
}
This is the code that is being implemented, reduced to only the parts that matter and that is working, but only showing the info that is being fetched from the contact object:
const Contacts = ({ match }) => {
const [data, setData] = useState([]);
const [institution, setInstitution] = useState();
const dataRecoil = useRecoilValue(contactData);
const handleContact = useCallback(async () => {
const response = dataRecoil.data;
if (response) {
setData(response.filter((contact) => !contact.deleted));
}
}, [setData, dataRecoil]);
useEffect(() => {
handleContact();
}, [handleContact]);
const columns = useMemo(
() => [
{
Header: 'Nome',
accessor: 'name',
},
{
Header: 'Sigla',
accessor: 'abbreviation',
},
{
Header: 'Nome Fantasia',
accessor: 'institution_id',
},
],
[editToggle, handleDelete],
);
return (
<>
<Table columns={columns} data={data} />
</>
);
};
And a print of it:
And here is what I have been trying to do:
const Contacts = ({ match }) => {
const [data, setData] = useState([]);
const [institution, setInstitution] = useState();
const dataRecoil = useRecoilValue(contactData);
const handleContact = useCallback(async () => {
const response = dataRecoil.data;
if (response) {
setData(response.filter((contact) => !contact.deleted));
}
}, [setData, dataRecoil]);
useEffect(() => {
handleContact();
}, [handleContact]);
const columns = useMemo(
() => [
{
Header: 'Nome',
accessor: 'name',
},
{
Header: 'Sigla',
accessor: 'abbreviation',
},
{
Header: 'Nome Fantasia',
accessor: 'institution_id',
Cell: async ({ cell }) => {
const response = await getInstitutionById(cell.row.values.institution_id);
const result = [response.data];
const inst = result.map((inst) => {return inst.name});
const institution_name = inst[0];
console.log(institution_name);
return institution_name;
},
},
],
[editToggle, handleDelete],
);
return (
<>
<Table columns={columns} data={data} />
</>
);
};
Which works at the level of fetching the right data, but does not render the page and shows errors:
The error
The right data being shown in the console.log
The expected output would be to show those names on the console.log on place of that long ID of the first picture.
So, is it possible to do what I am trying to do? And if so, what might am I be doing wrong?
I believe the issue is that you are providing an async function for your cell, which will return a Promise, not the institution name as you are expecting.
A potential solution is to instead create a custom Cell component that uses state to store the institution name. I have provided an example below, which was guided by this example, however I have not tested the code at all, so use it as more of a guide.
const MyCell = ({ cell }) => {
const [institutionName, setInstitutionName] = useState('fetching...')
useEffect(() => {
const getInstitutionName = async (id) => {
const response = await getInstitutionById(id);
const result = [response.data];
const inst = result.map((inst) => {return inst.name});
const institution_name = inst[0];
console.log(institution_name);
setInstitutionName(institution_name)
}
getInstitutionName(cell.row.values.institution_id)
}
return institutionName
}
const Contacts = ({ match }) => {
const [data, setData] = useState([]);
const [institution, setInstitution] = useState();
const dataRecoil = useRecoilValue(contactData);
const handleContact = useCallback(async () => {
const response = dataRecoil.data;
if (response) {
setData(response.filter((contact) => !contact.deleted));
}
}, [setData, dataRecoil]);
useEffect(() => {
handleContact();
}, [handleContact]);
const columns = useMemo(
() => [
{
Header: 'Nome',
accessor: 'name',
},
{
Header: 'Sigla',
accessor: 'abbreviation',
},
{
Header: 'Nome Fantasia',
accessor: 'institution_id',
Cell: MyCell
},
],
[editToggle, handleDelete],
);
return (
<>
<Table columns={columns} data={data} />
</>
);
};
So i want to subscribe to multiple events for the current logged user.
I've extracted the subscriptions to a separate function that update my logged user state from inside and returns an array of subscriptions.
Now i wanted to know is there a different / better way of doing this ?
Is this the correct / recommended way of approaching this problem ?
Current implementation
export const subscribeToCurrentUserUpdates = (setLoggedUser) => {
const friendRequestObserver$ = apolloClient.subscribe(
{ query: queries.NEW_FRIEND_REQUEST },
);
const followersUpdatesObserver$ = apolloClient.subscribe(
{ query: queries.FOLLOWERS_UPDATES },
);
const acceptedFriendRequestObserver$ = apolloClient.subscribe(
{ query: queries.ACCEPTED_FRIEND_REQUEST },
);
const friendRequestSubscription = friendRequestObserver$.subscribe({
next: ({ data: { newFriendRequest } }) => {
Alert.success(`${newFriendRequest.username} just sent you a friend request`);
setLoggedUser((loggedUser) => {
loggedUser.incomingFriendRequests.unshift(newFriendRequest._id);
});
},
error: err => console.error(err),
});
const followersUpdatesSubscription = followersUpdatesObserver$.subscribe({
next: ({ data: { followersUpdates: { follower, isFollow } } }) => {
if (isFollow) {
Alert.success(`${follower.username} is now following you`);
}
setLoggedUser((loggedUser) => {
isFollow
? loggedUser.followers.unshift(follower._id)
: loggedUser.followers.splice(loggedUser.followers.indexOf(follower._id), 1);
});
},
error: err => console.error(err),
});
const acceptedFriendRequestSubscription = acceptedFriendRequestObserver$.subscribe({
next: ({ data: { acceptedFriendRequest: newFriend } }) => {
Alert.success(`${newFriend.username} just accepted your friend request!`);
setLoggedUser((loggedUser) => {
loggedUser.friends.push(newFriend._id);
loggedUser.sentFriendRequests.splice(
loggedUser.sentFriendRequests.indexOf(newFriend._id), 1,
);
});
},
error: err => console.error(err),
});
return [
friendRequestSubscription,
followersUpdatesSubscription,
acceptedFriendRequestSubscription,
];
};
The way i subscribe from my component
const App = () => {
const currentUserSubscriptionRef = useRef();
useEffect(() => {
if (loggedUser && !currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current = subscribeToCurrentUserUpdates(
setLoggedUser,
);
}
if (!loggedUser && currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current.forEach((subscription) => {
subscription.unsubscribe();
});
currentUserSubscriptionRef.current = null;
}
}, [loggedUser, setLoggedUser]);
}