REACT- Displaying and filtering specific data - reactjs

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.

Related

MUIDataTable server side pagination - "no matching records exist"- when I move to the next page. Even though data is returned on the network tab

I am using MUIDatatable on a next js app. I have implemented server side pagination where I send the offset value and the limit as url params , and the data is returned as it should by the api -on each page change. However, the data is only displayed on the first page. When I navigate to the next page, i shows -"no matching records exist" - despite the data being returned by the api for that specific page.
Also when I click to go back to the first page, the page 2 data displays in a flash on the first page before it defaults to the actual page 1 data. Could someone point me to what I have missed ?
Datatable.jsx
const Datatable = () => {
const [data, setData] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [page, setPage] = useState(0);
const offset = page * 10
const getData = async () => {
try {
const response = await axios.get(`${process.env.url}/provider/api/v1/data&offset=${offset}&limit=10`)
setData(response.data.items)
setIsLoaded(true);
} catch (error) {
console.log(error)
setIsLoaded(true);
}
}
}
const columns = [name: "xxx", label: "XXXX", options: {}]
const options = {
viewColumns: true,
selectableRows: "multiple" ,
fixedSelectColumn: true,
tableBodyHeight: 'auto',
onTableChange: (action, tableState) => {
if (action === "changePage") {
setPage(tableState.page);
setIsLoaded(false);
} else {
console.log("action not handled.");
}
},
serrverSide: true,
count: 100,
textLabels: {
body: {
noMatch: isLoaded?"Sorry, no matching records exist"
:<div className="flex-grow-1 d-flex align-items-center justify-content-end">
<FadeLoader />
</div>,
toolTip: "Sort",
columnHeaderTooltip: column => `Sort for ${column.label}`
},
pagination: {
next: "Next Page",
previous: "Previous Page",
rowsPerPage: "Rows per page:",
displayRows: "of",
},
viewColumns: {
title: "Show Columns",
titleAria: "Show/Hide Table Columns",
},
}
}
useEffect(() => {
getData();
}, [page])
return (
<MUIDataTable
columns={columns}
data={data}
options={options}
/>
)
export default Datatable
setData(response.data.items) overrides the previous data, that is why you only see the data on the first page. You could fix this by changing it to.
setData(prevData => [...prevData, ...response.data.items]);
What worked for me here was I had not set serverSide: true, in the options

Patch multiple id's with one request with React Query

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], () =>

How can I delete an item inside a nested array with Hooks?

I am trying to remove a single item from state inside a nested array, but i am really struggling to understand how.
My data looks as follows, and I'm trying to remove one of the 'variants' objects on click.
const MYDATA = {
id: '0001',
title: 'A good title',
items: [
{
itemid: 0,
title: 'Cheddar',
variants: [
{ id: '062518', grams: 200, price: 3.00},
{ id: '071928', grams: 400, price: 5.50},
]
},
{
itemid: 1,
title: 'Edam',
variants: [
{ id: '183038', grams: 220, price: 2.50},
{ id: '194846', grams: 460, price: 4.99},
]
},
{
itemid: 2,
title: 'Red Leicester',
variants: [
{ id: '293834', grams: 420, price: 4.25},
{ id: '293837', grams: 660, price: 5.99},
]
}
]
}
Against each variant is a button which calls a remove function, which (should) remove the deleted item and update the state. However, this is not happening and I'm not sure what I am doing wrong.
const [myCheeses, setMyCheeses] = useState(MYDATA);
const removeMyCheese = (variantID, itemindx) => {
console.log(variantID);
setMyCheeses((prev) => {
const items = myCheeses.items[itemindx].variants.filter(
(variant) => variant.id !== variantID
);
console.log(items, itemindx);
return {
...myCheeses.items[itemindx].variants,
items
};
});
};
An example of the issue I'm facing can be seen here
https://codesandbox.io/s/funny-dan-c84cr?file=/src/App.js
Any help would be truly appreciated.
The issue is that, setMyCheeses function not returning the previous state including your change(removal)
Try one of these functions;
1st way
const removeMyCheese = (variantID, itemindx) => {
setMyCheeses((prev) => {
const items = myCheeses.items[itemindx].variants.filter(
(variant) => variant.id !== variantID
);
const newState = prev;
newState.items[itemindx].variants = items;
return {...newState};
});
};
https://codesandbox.io/s/bold-worker-b12x1?file=/src/App.js
2nd way
const removeMyCheese = (variantID, itemindx) => {
setMyCheeses((prev) => {
const items = myCheeses.items.map((item, index) => {
if (itemindx === index) {
return {
...item,
variants: item.variants.filter(
(variant) => variant.id !== variantID
)
};
} else {
return item;
}
});
return { ...prev, items: items };
});
};
https://codesandbox.io/s/sharp-forest-qhhwd
try this function, it's work for me :
const removeMyCheese = (variantID, itemindx) => {
//console.log(variantID);
const newMyCheeses = myCheeses;
const newItems = newMyCheeses.items.map((item) => {
return {
...item,
variants: item.variants.filter((variant) => variant.id !== variantID)
};
});
setMyCheeses({ ...newMyCheeses, items: newItems });
};
https://codesandbox.io/s/jolly-greider-fck6p?file=/src/App.js
Or, you can do somthing like this if you don't like to use the map function :
const removeMyCheese = (variantID, itemindx) => {
//console.log(variantID);
const newMyCheeses = myCheeses;
const newVariants = newMyCheeses.items[itemindx].variants.filter(
(variant) => variant.id !== variantID
);
newMyCheeses.items[itemindx].variants = newVariants;
setMyCheeses({ ...newMyCheeses });
};

How to setState for react useState hook, array of objects?

Here, using an array I have mapped the input boxes, and the value which has entered in that array should be stored in an usestate hook and on click of submit button it should console the array of objects. How to acheive this? Thank You.
const AddCheckPoint = ({ master }) => {
const [addChecklist, setAddChecklist] = useState([])
const [addChecklistValues, setAddChecklistValues] = useState({})
const submit = () => {
setShow(false)
console.log(JSON.stringify(addChecklist))
}
const getFormDetails = (val, id) => {
setAddChecklistValues(prevState => ({
...prevState,
ch_id: id,
value: val
}));
setAddChecklist(prevArray => [...prevArray, addChecklistValues])
}
return (
<>
<Row><Col><b>SLno</b></Col><Col><b>Checkpoint</b></Col><Col><b>Value</b></Col></Row>
{master && master.map((r, i) => {
return (<Form.Group key={i} className="mb-3" controlId={i}>
<Row><Col><Form.Label>{r.slno}</Form.Label></Col><Col><Form.Label>{r.checkPoint}</Form.Label></Col><Col><Form.Control type="text" placeholder="Enter Username" onChange={e => getFormDetails(e.target.value, r._id)} /></Col></Row>
</Form.Group>)
})}
<Button variant="primary" onClick={submit}>Submit</Button>
</>
);
}
export default AddCheckPoint
And the prop "master" looks like the follows
[
{
"_id": "61028558b45073399077becd",
"slno": "A1",
"checkPoint": "Position of adaptor CAT",
"cellName": "PC - 1",
"createdAt": "2021-07-29T10:39:20.902Z",
"updatedAt": "2021-07-29T10:39:20.902Z",
"__v": 0
},
{
"_id": "61028567b45073399077becf",
"slno": "A2",
"checkPoint": "Flush height of Adaptor CAT position",
"cellName": "PC - 1",
"createdAt": "2021-07-29T10:39:35.752Z",
"updatedAt": "2021-07-29T10:39:35.752Z",
"__v": 0
}
]
Just add simple by create a new value and pass it into setAddChecklistValues and setAddChecklist
const getFormDetails = (val, id) => {
const newValue = {
ch_id: id,
value: val,
};
setAddChecklistValues(newValue);
setAddChecklist((prevArray) => [...prevArray, newValue]);
};
Here as you are using onchange u must use debouncing technique to get correct the array of the objects. Refer Following url:
https://www.telerik.com/blogs/debouncing-and-throttling-in-javascript or you can replace below function in your code :
const getFormDetails = (val, id) => {
setAddChecklistValues((prevState) => ({
...prevState,
ch_id: id,
value: val
}));
setAddChecklist((prevArray) => {
const k = prevArray.filter((e) => {
return e.ch_id === addChecklistValues.ch_id;
});
if (k.length > 0) {
prevArray.map((e) => {
if (e.ch_id === addChecklistValues.ch_id) {
e.value = addChecklistValues.value;
}
return e;
});
return prevArray;
}
return [...prevArray, addChecklistValues];
});
};

In react-table with React Hooks, how to make a get request inside Cell?

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} />
</>
);
};

Resources