Create a reusable complex table component - reactjs

I am mainly a backend developer trying to do some React and I have the following question for you:
The website I’m working on has a lot of pages that are very similar between them. Most of them have one or two tables that also have pagination, filtering, sorting on some rows and row selection (there are some tables that do not have pagination or filtering or sorting or row selection). Parameters coming from the url should be allowed and applied to the filters (and thus to the first data fetch), selected rows have batch actions and the button shows at the top of the table, each row has buttons in the last column to do actions like edit, delete, etc. Also one final think that complicates all this a bit more is that some tables have cells that have more than just text, they can have icons to the left, rignt or have 2 lines of text.
The tables look something like this:
I created hooks to handle the query params, filters and pagination, which I thought was going to be enough but I still have a lot of duplication. In my components I have code similar to this:
const [dataLoading, setDataLoading] = useState(false);
const [data, setData] = useState([]);
const {
queryParams, clearQueryParams, setQueryParams,
} = useQuery();
const {
filters, updateFilters, clearFilters,
} = useFilters(queryParams);
const {
paging, setPaging, setPage, setRowsPerPage,
} = usePagination();
// A lot of funtions that use state (less than before though)
I thought of creating a Table component that contains all of this logic and receives the functions it needs via props, but there is also a problem which is that there are certain actions outside the table that can make the data dirty and a reload is required (like a new element is present on the list or an item was deleted for example).
What would be a good approach to solve this while making the code reusable (easy to read and simple is a huge plus)?
Do you think having one complex Table component is a good idea or is it better to have a PaginatedTable, FilterableTable (or maybe there is a way to nicely compose those)?
Thanks

Related

How to manage states of a shared component where we want to retain the states based on the place the component is being used?

For example: let's say we have a table component used on multiple pages or in a page multiple times. Now, we want to maintain the selected items from the table for each table and highlight them.
So, currently, I am using an object to store the selected items based on the place it's being used:
{
place1: {selectedItemId1: selectedItem1, selectedItemId2: selectedItem2},
place2: {selectedItemId1: selectedItem1, selectedItemId2: selectedItem2},
place3: {selectedItemId1: selectedItem1, selectedItemId2: selectedItem2}
}
Please let me know if you've any other better solution.

How to re-fetch single items of a collection in react-query

Let's say i have a query to fetch a collection of movies:
useQuery(['movies'], getMovies)
Now, if I want to re-fetch only one movie instead of all I can write something like this:
useQuery(['movies', movieId], () => getMovie(movieId))
Problem is that I use a different query key and that it will duplicate the data. I'll have the movie in my cache twice.
So, what's the react-query way of updating a single item of a fetched collection? All components that use useQuery(['movies']) should be updated automatically when the single item was fetched.
I think there are two ways:
like you described with a new key. yes, the data will be "twice" in the cache, but oftentimes, the "list" data has a different structure than the "detail" data. movies-list might just have id and name, while the details have a description etc. as well. If you do that, make sure to give them both the same prefix ('movies'), so that you can utilize the fuzzy matching when invalidating: queryClient.invalidateQueries(['movies']) will then invalidate the list and all details, which is nice.
you can use the select option to build a detail query on:
const useMovies = (select) => useQuery(['movies'], getMovies, { select })
const useMovie = (id) => useMovies(movies => movies.find(movie => movie.id === id))
with that, you can do useMovie(5) and thus will only subscribe to the movie with id 5. This is really nice and also render optimized - if the movie with id 4 updates, the component that subscribes to movie with id 5 will not re-render.
the "drawback" is of course that for the data / network perspective, there is only one query - the list query, so everytime you do background refetches, the whole list will be queried from the backend. But it's still a nice approach to avoid data duplication in the cache, and it makes optimistic updates easier as well, because you only have to write to update one cache entry.

How to use ArrayInput to populate data in react-admin list view

I have a react-admin Create View that I want to display and populate using ArrayInput which however seems to ignore the data of the list.
I tried to load the data with
const { data } = useQueryWithStore({
type: 'getList',
resource: 'comments',
});
and to set the source to it manually, but the ArrayInput seems unimpressed by all efforts to feed it some data.
I can iterate through the data array and render TextFields and remove buttons accordingly, while leaving the ArrayInput for adding new data, but that defeats the purpose and the beautiful interaction with ArrayInput. Is there something I'm missing? How should I pass the data for it to work as one would expect? I made a sandbox example for this HERE
For creating multiple rows of the entity with one form submit you will need a custom implementation or a work-around.
One of the most esiest ways could be to handle manually the submission of the form and alter the data and send separate requests for each entered entity.
Here in the docs is shown an example how you can pass the Create view a custom toolbar where you can implement your logic.

Can you update firestore query dynamically based on router location?

So basically, I have a simple React app connected to Firebase that lists different types of food from firestore collections.
Example:
I have a few categories. The default one is "All" that displays top 8 popular dishes from all other available categories and this part is easy but I want an user to be able to click on other category and update my query.
Category is actually a NavLink that updates location on click so: if user click on "Pizza" category the url looks like this localhost:3000/Pizza if he clicks on Salad it is localhost:3000/Salad etc.
I have a "Wall" component that is a section and it displays those items from firestore.
My query ref in this wall component look like this: const foodRef = db.collection("food").doc("all").collection("items");
But I want to set .doc dynamically and make query on every update so I changed the query to something like that:
const location = useLocation();
const foodRef = db.collection("food").doc('${location.pathname}').collection("items");
And when user click on different Card (NavLink) url updates but query does not.
I know it is a bad solution but I actually have no idea how to do that.
I have read about Recursive Paths in react router but I do not know if it is what I am looking for.
If you know how to approach that please let me know.
Thanks for your time.
Firestore does not support wildcards or replacements in queries and Query objects are fully immutable (they can't be changed). You have to know the names of the documents and collections ahead of time to build a query. If you want to change some part of a query, you have to rebuild a whole new query object every time, and run the query again to get a new set results.

ui-grid export all filtered data to csv

I have some fairly large data (~100k rows) with filtering on every column and paging enabled. It's all displayed and manipulated client side.
I'd like to be able to export all the filtered results, not just what's displayed in the first page.
The workaround would be to change the pagination size to something larger than the resulting filtered result, but that just seems cumbersome. Is there a better way to do this?
Someone had asked this question in https://github.com/angular-ui/ui-grid/issues/3954 but never got an answer.
There isn't really a way to do it. Export visible exports the currently visible rows. Pagination works by changing which rows are visible - so the visible rows are those on the current page. Export all data works by exporting all the data whether it's visible or not, so it exports rows that have been filtered out.
In theory this could be implemented, we'd need to reinstitute the "invisibleReason" flag which I think is still mostly implemented but not used in the code, and then set the visibleReason to "pagination" on those rows that the pagination feature makes invisible. We could then have exporter manually process the rows and include those for which the only invisibleReason was pagination.
It's quite a lot of fiddling around, but it's plausible someone could submit a PR with that functionality.
I think this should work.
onRegisterApi: function (gridApi) {
gridApi.core.on.rowsVisibleChanged($scope, function () {
// match export enabled per row to visible property. This is in order to force export only of filtered data.
gridApi.grid.rows.forEach(function (row) {
row.exporterEnableExporting = row.visible;
});
});
}

Resources