like-gold
like-gold19h ago

How to use a custom sidebar to filter data instead of provide navigation

Hello. I am a bit confused on how to sync table filter state when using a custom sidebar. I want my sidebar to have table filters. e.g App.tsx <Route element={ <ThemedLayout Header={() => null} Sider={() => <FilterSidebar />} > <TableProvider> <Outlet /> </TableProvider> </ThemedLayout> } > list.tsx const { tableProps, setFilters, filters } = useTable<IPost>({ resource: 'tasks', queryOptions: { enabled: true, }, pagination: { currentPage: 1, pageSize: 5, }, syncWithLocation: true, }) FilterSidebar.tsx const { tableProps, setFilters, filters } = useTable<IPost>({ resource: 'tasks', queryOptions: { enabled: false, }, syncWithLocation: true, }) // Trying to call setFilters via a button does not apply the filters on my table // Despite that component rerendering but the url does get correctly updated with the filter // and even after re-render filters from list.tsx is an empty [] // Is there a better way to share state in my app? I thought url updates would handle this
8 Replies
kapa.ai
kapa.ai19h ago
Hi @dashboard-dev-777. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the knowledge sources, I can help clarify the issue you're experiencing with syncing filter state between your sidebar and table. The Problem The issue you're encountering is related to how syncWithLocation works in refine. According to the documentation on syncWithLocation behavior, refine's syncWithLocation works in one direction only and only infers parameters at initial render to avoid syncing issues: - When useTable is mounted with a routerProvider present, filters are inferred from query params - Those values are stored in state within the useTable hook - Changes in those states will update the query params - However, if there are changes in query params not caused by that specific useTable instance, and those changes don't re-mount the hook, they are not synced with the internal states This explains why your URL updates correctly when you call setFilters in your FilterSidebar, but the table in list.tsx doesn't reflect those changes - the useTable hook in list.tsx doesn't automatically react to URL changes after its initial mount. Recommended Solution The recommended workaround is to add an effect in your list.tsx to monitor URL parameter changes and manually sync them. Here's the approach suggested in the GitHub issue discussion:
const { params } = useParsed();
const { setFilters, setSorters } = useTable();

React.useEffect(() => {
// actual implementation might be different and slightly more complex depending on the use case.
if (!params.filters) setFilters([], "replace");
if (!params.sorters) setSorters([]);
}, [params]);
const { params } = useParsed();
const { setFilters, setSorters } = useTable();

React.useEffect(() => {
// actual implementation might be different and slightly more complex depending on the use case.
if (!params.filters) setFilters([], "replace");
if (!params.sorters) setSorters([]);
}, [params]);
Alternative Approach Instead of using two separate useTable hooks, you could create a shared state management solution. As mentioned in a related discussion, you can create a custom hook to manage filter state that both components can access, though this would require manual handling of the filter logic. The refine team has acknowledged that this behavior is confusing and has discussed implementing proper two-way binding for syncWithLocation, but it hasn't been prioritized yet. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai19h ago
<@1438181786856460318> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
like-gold
like-goldOP19h ago
@kapa.ai What would the shared state management solution look like to solve this problem
kapa.ai
kapa.ai19h ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the knowledge sources, I can provide some guidance on creating a shared state management solution, though the sources don't contain a complete implementation example. Approach Using Context/Custom Hook From the discussion about breaking filters into their own hook, a maintainer suggested that you can create a custom hook to keep your filter state. Here's the concept:
const { result, query } = useList<IProduct, HttpError>({
resource: "products",
filters: [
{
field: "material",
operator: "eq",
value,
},
],
});
const { result, query } = useList<IProduct, HttpError>({
resource: "products",
filters: [
{
field: "material",
operator: "eq",
value,
},
],
});
The maintainer noted: "You can actually create a custom hook to keep your filter state if you need to, that should work out of the box." Practical Implementation Pattern Based on the filtering examples in the useTable documentation, you could structure a shared solution like this: TableProvider.tsx (your context):
const TableContext = React.createContext<{
filters: CrudFilters;
setFilters: (filters: CrudFilters) => void;
}>({
filters: [],
setFilters: () => {},
});

export const TableProvider = ({ children }) => {
const [filters, setFilters] = React.useState<CrudFilters>([]);

return (
<TableContext.Provider value={{ filters, setFilters }}>
{children}
</TableContext.Provider>
);
};
const TableContext = React.createContext<{
filters: CrudFilters;
setFilters: (filters: CrudFilters) => void;
}>({
filters: [],
setFilters: () => {},
});

export const TableProvider = ({ children }) => {
const [filters, setFilters] = React.useState<CrudFilters>([]);

return (
<TableContext.Provider value={{ filters, setFilters }}>
{children}
</TableContext.Provider>
);
};
list.tsx:
const { filters: sharedFilters } = React.useContext(TableContext);

const { tableProps } = useTable<IPost>({
resource: 'tasks',
filters: {
initial: sharedFilters,
},
});
const { filters: sharedFilters } = React.useContext(TableContext);

const { tableProps } = useTable<IPost>({
resource: 'tasks',
filters: {
initial: sharedFilters,
},
});
FilterSidebar.tsx:
const { setFilters } = React.useContext(TableContext);
// Update shared filters
const handleFilterChange = () => {
setFilters([{ field: "status", operator: "eq", value: "active" }]);
};
const { setFilters } = React.useContext(TableContext);
// Update shared filters
const handleFilterChange = () => {
setFilters([{ field: "status", operator: "eq", value: "active" }]);
};
Important Limitation However, I should note that the knowledge sources don't provide a complete, tested implementation of this pattern. The sources primarily focus on using syncWithLocation or the workaround with useParsed() that I mentioned in my previous response. The custom hook approach is mentioned as a possibility but without detailed implementation guidance. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai19h ago
<@1438181786856460318> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
like-gold
like-goldOP19h ago
@kapa.ai Could mixing refine and jotai work well together to implement this
kapa.ai
kapa.ai19h ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the knowledge sources, I can provide some insight into using Jotai with refine, though the sources don't contain specific implementation examples for your filtering use case. Jotai Overview According to the React Ecosystem article, Jotai is described as:
"a primitive and flexible state management library for React. It is built on top of React Hooks and the Context API. It takes an atomic approach to global React state management."
The article shows that Jotai allows you to create atoms for managing state:
import { atom } from "jotai";

const countAtom = atom(0);
const countryAtom = atom("Japan");
const citiesAtom = atom(["Tokyo", "Kyoto", "Osaka"]);
import { atom } from "jotai";

const countAtom = atom(0);
const countryAtom = atom("Japan");
const citiesAtom = atom(["Tokyo", "Kyoto", "Osaka"]);
Jotai is noted as being very lightweight with a bundle size of just 2.78kB and is used by companies like Adobe, TikTok, and Uniswap. Compatibility with Refine The knowledge sources confirm that Jotai is a legitimate state management solution in the React ecosystem, and since refine is a React-based framework with a headless architecture that decouples business logic from UI components, there's no inherent incompatibility mentioned. Important Limitation However, I must note that the knowledge sources do not contain any specific examples or documentation about integrating Jotai with refine's useTable hook for managing filter state. While Jotai could theoretically be used to create shared atoms for your filter state that both your sidebar and table components could access, the sources don't provide implementation guidance or best practices for this specific scenario. The sources primarily focus on refine's built-in state management through hooks like useTable and the syncWithLocation feature, along with the workaround using useParsed() that I mentioned in my previous response. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai19h ago
<@1438181786856460318> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?