loading

v32.1.1

select option

useLedger hook

Leverage ledger features without the Ledger component

useLedger hook vs Ledger component

Ledger is a core component in the Carta ecosystem. It is used all over the platform to display and interact with complex data.

Though Ledger offers many amazing features (including searching, sorting, pagination, selecting/bulk actions, and filtering), it is also a flawed component - it tightly couples behavioral logic with display logic, which means that even small changes to its visuals may be complicated and result in breaking changes. Moreover, the component is built using the deprecated Table component, rather than NewTable, which means it is missing key functionality that users have come to expect with Ink table components.

Rather than upgrade Ledger to use NewTable under the hood, Ink opted to create the useLedger hook, which will provide a flexible way to leverage Ledger functionality without tightly coupling those features to a specific UI.

Meet the useLedger hook

The useLedger hook uses a headless API design pattern to separate Ledger logic from it's visual representation. This means that Ink users can leverage Ledger sorting, searching, selecting, pagination, and filtering, without being tied to the UI of the Ledger component.

useLedger manages all of the stateful logic that is necessary to make a functional ledger, without tying it directly to the Ledger component.

Additionally, while the Ledger component was built specifically to work with carta-web-style Django Rest Framework endpoints, the useLedger hook offers the flexibility to call other kinds of APIs.

Getting started with the useLedger hook

The useLedger hook is instantiated with a configuration object. Getter props and state variables can be destructured from the hook's result and passed into Ink components in order to recreate Ledger functionalities without the Ledger component.

The props passed into the configuration object help the hook understand how to parse the API response for your ledger to do sorting, filtering, searching, and pagination. Many of these props are not necessary while using a standard carta-web style DRF endpoint.

The following props can be passed into the configuration object:

namedescriptionrequired
urlThe url to which the hook will make API callstrue
getNumPagesA function used by the hook to parse the API response to find the total number of pagesfalse
getCurrentPageA function used by the hook to parse the API response to find the current page numberfalse
getTotalItemsA function used by the hook to parse the API response to find the total items returned, i.e. countfalse
pageSizeThe number of results to fetch per pagefalse
sortConfigObject used by the hook to manage sortingfalse
dataKeyThe key in the API response that correlates to the data that will populate the ledger. Defaults to resultsfalse
selectableConfigThe same config object passed into the useSelectable hookfalse
debounceTimeoutDebounce timeout for the API callfalse
defaultQueryParamsObject determining the default query params for the API requestfalse
stateToSearchParamFunction mapping the state to a search parameter in the API callfalse
stateToOrderingParamFunction mapping the state to an ordering parameter in the API callfalse
stateToFilterParamsFunction mapping the state to filter parameters in the API callfalse
makeRequestFunction that returns a promise, allowing the user to completely override the hook's built-in API call logic with custom logicfalse
externalsObject containing values to be added to the API call URL as additional query parametersfalse

In addition to the internal state of the hook, the following getter props and state variables are returned:

namedescription
getFilterPropsProps used for filtering
getSearchPropsProps used for searching. Unpack into LedgerWrapper.Search for the simplest implementation
getPaginationPropsProps used for pagination. Unpack into LedgerWrapper.Pagination for the simplest implementation
getSortingPropsProps used for sorting. Can be unpacked into NewTable.Pin for the simplest implementation
selectedObject containing information about which rows are selected. Correlates to selected in the useSelectable hook
selectablePropsObject containing selectable actions. Correlates to actions in the useSelectable hook
getTopBarPropsProps that can be unpacked into the LedgerWrapper.TopBar component
getBottomBarPropsProps that can be unpacked into the LedgerWrapper.BottomBar component

useLedger subcomponents

Ink offers two sets of subcomponents that can be used in conjunction with the useLedger hook for a simple ledger implementation. Low-level subcomponents such as Filter, Search, Pagination, and Actions give users the flexibility and building blocks they need to create a customized ledger as per their unique specifications. However, these low-level subcomponents do not account for certain behaviors that are baked into the original Ledger component - for example, hiding pagination when rows are selected.

To make it easier to maintain visual and behavioral parity as closely as possible with the original Ledger component, Ink also offers two high-level components: TopBar and BottomBar. These components handle not only visuals, but also the behavioral logic that most people associate with being intrinsic to Ledger.

How to use useLedger

In its simplest implementation, useLedger is used with a standard carta-web style DRF endpoint. In such a case, the code used to instantiate the useLedger hook would look something like the following:

const { data, loading, getTopBarProps, getBottomBarProps, selected, selectableProps } = useLedger({
url: "http:///www.mycoolapi.com",
})

The destructured objects can then be used as follows for a fully functional ledger using the high-level subcomponents LedgerWrapper.TopBar and LedgerWrapper.BottomBar:

return (
<Ink.Page>
<Ink.Page.Body>
<Ink.Page.Content>
<Ink.LedgerWrapper.TopBar
actions={
<>
<Ink.Button>Draft shares</Ink.Button>
<Ink.Button>Manage stakeholders</Ink.Button>
</>
}
bulkActions={<Ink.Button>Resend emails</Ink.Button>}
filter={{
groups: [
{
label: 'Type',
key: 'type',
items: [
{
id: 'corporation',
value: 'Corporation',
label: 'Corporation',
},
{
id: 'personal',
value: 'Personal',
label: 'Personal',
},
],
},
],
}}
{...getTopBarProps()}
/>
<Ink.NewTable>
<Ink.NewTable.Head>
<Ink.NewTable.Row>
<Ink.NewTable.HeadCell>
<Ink.NewCheckbox id="select-all" checked={selected.allRows} onChange={selectableProps.toggleAllRows} />
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin {...getSortingProps().sortByKey('legal_name')}>Stakeholder</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin {...getSortingProps().sortByKey('id')}>ID</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
</Ink.NewTable.Row>
</Ink.NewTable.Head>
<Ink.NewTable.Body>
{data.map(d => (
<Ink.NewTable.Row key={d.id} selected={selected.rows[d.id]}>
<Ink.NewTable.Cell preset="checkbox">
<Ink.NewCheckbox id={d.id} onChange={selectableProps.toggleRow} checked={selected.rows[d.id]} />
</Ink.NewTable.Cell>
<Ink.NewTable.Cell>{loading ? <Ink.Shimmer /> : d.legal_name}</Ink.NewTable.Cell>
<Ink.NewTable.Cell>{loading ? <Ink.Shimmer /> : d.id}</Ink.NewTable.Cell>
</Ink.NewTable.Row>
))}
</Ink.NewTable.Body>
</Ink.NewTable>
<Ink.LedgerWrapper.BottomBar {...getBottomBarProps()} />
</Ink.Page.Content>
</Ink.Page.Body>
</Ink.Page>
)

Alternatively, the same result can be achieved with the low-level subcomponents LedgerWrapper.Filter, LedgerWrapper.Search, LedgerWrapper.Pagination, and LedgerWrapper.Actions:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
})
const rowsAreSelected = Boolean(Object.entries(selected.rows).filter(([key, val]) => val).length);
const filters = {
groups: [
{
label: 'Type',
key: 'type',
items: [
{
id: 'corporation',
value: 'Corporation',
label: 'Corporation',
},
{
id: 'personal',
value: 'Personal',
label: 'Personal',
},
],
},
],
};
return (
<Ink.Page>
<Ink.Page.Body>
<Ink.Page.Content>
<Ink.Box bottom="gutter">
<Ink.HStack spacing="medium">
{rowsAreSelected ? (
<Ink.LedgerWrapper.Actions>
<Ink.Button>Resend emails</Ink.Button>
</Ink.LedgerWrapper.Actions>
) : (
<Ink.LedgerWrapper.Actions>
<Ink.Button>Resend emails</Ink.Button>
<Ink.LedgerWrapper.Search {...getSearchProps()} />
<Ink.LedgerWrapper.Filter
id="filter"
items={filters}
trigger={triggerProps => <Ink.Button type="actions" {...triggerProps} />}
{...getFilterProps()}
/>
<Ink.Button>Draft shares</Ink.Button>
<Ink.Button>Manage stakeholders</Ink.Button>
</Ink.LedgerWrapper.Actions>
)}
</Ink.HStack>
</Ink.Box>
<Ink.NewTable>
<Ink.NewTable.Head>
<Ink.NewTable.Row>
<Ink.NewTable.HeadCell>
<Ink.NewCheckbox id="select-all" checked={selected.allRows} onChange={selectableProps.toggleAllRows} />
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin {...getSortingProps().sortByKey('legal_name')}>Stakeholder</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin {...getSortingProps().sortByKey('id')}>ID</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
</Ink.NewTable.Row>
</Ink.NewTable.Head>
<Ink.NewTable.Body>
{data.map(d => (
<Ink.NewTable.Row key={d.id} selected={selected.rows[d.id]}>
<Ink.NewTable.Cell preset="checkbox">
<Ink.NewCheckbox id={d.id} onChange={selectableProps.toggleRow} checked={selected.rows[d.id]} />
</Ink.NewTable.Cell>
<Ink.NewTable.Cell>{loading ? <Ink.Shimmer /> : d.legal_name}</Ink.NewTable.Cell>
<Ink.NewTable.Cell>{loading ? <Ink.Shimmer /> : d.id}</Ink.NewTable.Cell>
</Ink.NewTable.Row>
))}
</Ink.NewTable.Body>
</Ink.NewTable>
{!rowsAreSelected && <Ink.LedgerWrapper.Pagination {...getPaginationProps()} />}
</Ink.Page.Content>
</Ink.Page.Body>
</Ink.Page>
)

For more complex implementations, see the following sections.

API requests

By default, useLedger makes requests meant for a standard carta-web DRF-style endpoint. Based on the configuration object, it assembles query arguments such as page, search, and ordering, as well as any filter parameters, and makes a request to the url provided by the user.

The hook sets the following default query parameters to be passed into the url:

{ page: '1', search: '', ordering: '' }

However, you can change those by using the defaultQueryParams prop. For example, if you want the initial state of the dataset to be sorted by the investor_name property, the defaultQueryParams would look like this:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url,
defaultQueryParams: { page: "1", search: "", ordering: "investor_name" },
})

Note that page size is not controlled by defaultQueryParams - it is controlled by the pageSize argument. pageSize defaults to 50, but can be overridden:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
pageSize: 10,
})

You can pass additional query parameters to the hook with the externals prop, for example:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url,
defaultQueryParams: { page: "1", search: "", ordering: "investor_name" },
externals: { from_portfolios: "4,5,6,7", request_initiated_before: "2022-01-01" },
})

useLedger with non-DRF APIs

Though useLedger is built to expect DRF endpoints, the hook does give users the flexibility to make other kinds of API requests as well. If the user doesn't want to use a standard DRF-style API, they can use the makeRequest prop to override the hook's internal request logic and write their own function that returns a promise. One important note about this is that the request function must be defined outside of the component body, or else the hook will hit an infinite recursive loop due to the value of the function changing between renders.

The following is an example that uses the makeRequest prop:

const response = {
results: [
{ legal_name: "Momo", pk: 1 },
{ legal_name: "Pepita", pk: 2 },
{ legal_name: "Bunny", pk: 3 },
],
};
const makeRequest = (url, queryString) => {
return new Promise()<typeof response>((resolve, reject) => setTimeout(() => resolve(response), 1000));
};
const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
makeRequest,
})

The hook expects the API response to be an object with a key called "results". If you are using the makeRequest prop and your expected API response doesn't contain a results key which correlates to the data that will populate the ledger, use the dataKey prop to specify which key in your response maps to that data set. That value will be attributed to the data state variable.

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url,
dataKey: "data",
makeRequest,
})

Alternatively, if you are working with an API that returns an array (as opposed to an object that can be mapped with the dataKey prop), you can map that returned value into an object in your custom makeRequest function. Simply wrap the response in an object, with a key called results:

const makeRequest = async (url, querystring) => {
const data = await Axios.get(url);
return {
results: data,
};
}

Debounce

By default, all requests (whether they use the hook's internal logic or are custom requests with the makeRequest prop) are debounced at 250ms. However, you can override that amount with the debounceTimeout prop in the configuration object.

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
debounceTimeout: 1000,
})

Handling request errors

Sometimes API requests don't go according to plan. If your API request returns an error status, you can use the error state variable to conditionally render something other than the Ledger content:

const { data, error, message } = useLedger({
url: 'http:///www.mycoolapi.com',
});
return (error ? <EmptyState text={message} /> : <NewTable>{ledger content goes here}</NewTable>)

This variable comes directly from the hook's internal state. Message is the request payload - you can choose whether or not to expose this to users.

Custom parameters in URL arguments

useLedger is built to expect carta-web DRF-style endpoints, which send query arguments through url parameters. For example, if you want to search by the keyword "Meetly," DRF endpoints expect &search=Meetly to be appended to the url. This behavior is baked into useLedger with three functions: stateToSearchParam, stateToOrderingParam, and stateToFilterParams, which respectively map out search, ordering, and filter query arguments into the url params.

The default stateToSearchParam takes in a search query string and returns an object, with a value mapped to the key search:

const defaultStateToSearchParam = (searchQuery: string) => {
return { search: searchQuery };
}

The object this function returns is later unpacked into the query string.

Likewise, stateToOrderingParam takes in an orderingString (either ascending or descending) as well as an orderingKey string (the key by which you want to order) and returns an object with a value mapped to the key ordering:

const defaultstateToOrderingParam = (orderingDirection: string, orderingKey: string): OrderingParamType | undefined => {
if (!orderingKey || !orderingDirection) {
return { ordering: "" };
}
if (orderingDirection === "descending") {
return { ordering: `-${orderingKey}` };
}
return { ordering: orderingKey };
}

And stateToFilterParams simply returns an object with key/value pairs corresponding to the query filters.

const defaultStateToFilterParam = (filters: GetFilterPropsType) => {
return filters;
}

If you are using a non-DRF endpoint, you may need to override the way that query arguments are passed into the url. You can override any of these default props in the useLedger configuration object to create your own logic to map query arguments to the url.

Refetching data

useLedger handles requests internally, however, you may want to build UI that refetches data after an external event (such as clicking a button or submitting a form).

In order to accomplish this, you can use the refetch parameter from the hook:

const { refetch } = useLedger({
url: "http:///www.mycoolapi.com",
});
<Ink.Button onClick={() => refetch()}>Reload</Ink.Button>

Sorting

Much like the useSort hook, the useLeger hook can control sorting - however, unlike the useSort, which is used for front-end sorting, useLedger handles sorting on the backend through the API request (as is standard with DRF endpoints).

Sorting the initial state

To implement sorting in the initial state of the ledger, pass a sortConfig into the main configuration object. If you do not want the data to be sorted in the initial state, no sortConfig is necessary - sorting functionality will still work without it.

The sortConfig prop has the following type definition:

type SortConfigType = {
/* The column by which the data should be sorted */
key: string,
/* Sort direction */
direction: string,
}

Therefore, if you want your data to initially be sorted ascending by name, the hook would be configured as follows:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
sortConfig: { key: "name", direction: "ascending" },
})

Implementing sorting with NewTable

Regardless of whether or not the ledger's initial state is sorted, you can utilize getSortingProps returned by the hook. Unpack the getter function into a NewTable.Pin as follows:

<Ink.NewTable.Head>
<Ink.NewTable.Row>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin {...getSortingProps().sortByKey("legal_name")}>Stakeholder</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin {...getSortingProps().sortByKey("id")}>ID</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
</Ink.NewTable.Row>
</Ink.NewTable.Head>

Sorting programatically

sortByKey can be used to sort the contents of the ledger. Its default behavior alternates the sorting direction between ascending and descending programmatically, similar to how the sorting direction alternates when clicking the same column header multiple times.

// Given a ledger currently sorted by 'name' in ascending order
getSortingProps().sortByKey("name").onClick();
// As the ledger was already sorted by 'name' in ascending order, this sorts the ledger by 'name' in descending order.

However, if your project requires a specific sorting direction to be applied, for example, if you have the need to store and restore the state of the ledger, you can pass that order directly into sortByKey as an additional argument. The possible values are 'ascending' or 'descending'.

// Given a ledger currently sorted by 'name' in ascending order
getSortingProps().sortByKey("id", "descending").onClick();
// The ledger will be sorted by 'id' in descending order.

Selecting/Bulk Actions

useLedger uses the useSelectable hook under the hood.

Using the selectableConfig prop

In order to implement selecting, you must pass a selectableConfig through to the hook configuration object. Additionally, selecting relies on the dataKey prop - if you want selecting abilities on a returned value from your data set other than results, you must specify it with the selectKey prop.

The selectableConfig prop is the same config object that is passed into the useSelectable hook. It has the following type definition:

type UseSelectableConfigType = {
/* A unique attribute of the elements in the data array. Default: 'id' */
key?: string,
/* An array of unique identifiers that will be initially selected */
initialState?: string[],
}

In the following example, the user would want to initially select rows with the IDs '9', '12', and '13', from the API response key "rows":

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
dataKey: "rows",
selectableConfig: { key: "id", initalState: ["9", "12", "13"] },
})

Implementing selecting with NewTable and NewCheckbox

Once selecting has been instantiated with the selectableConfig, you can use the selected and selectableProps state variables with NewTable.Row and NewCheckbox component as follows:

<Ink.NewTable data-testid="newtable-selectable">
<Ink.NewTable.Head>
<Ink.NewTable.Row>
<Ink.NewTable.HeadCell>
<Ink.NewCheckbox id="select-all" checked={selected.allRows} onChange={selectableProps.toggleAllRows} />
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin>Stakeholder</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
<Ink.NewTable.HeadCell>
<Ink.NewTable.Pin>ID</Ink.NewTable.Pin>
</Ink.NewTable.HeadCell>
</Ink.NewTable.Row>
</Ink.NewTable.Head>
<Ink.NewTable.Body>
{data.map((d) => (
<Ink.NewTable.Row key={d.id} selected={selected.rows[d.id]}>
<Ink.NewTable.Cell preset="checkbox">
<Ink.NewCheckbox id={d.id} onChange={selectableProps.toggleRow} checked={selected.rows[d.id]} />
</Ink.NewTable.Cell>
<Ink.NewTable.Cell>{loading ? <Ink.Shimmer /> : d.legal_name}</Ink.NewTable.Cell>
<Ink.NewTable.Cell>{loading ? <Ink.Shimmer /> : d.id}</Ink.NewTable.Cell>
</Ink.NewTable.Row>
))}
</Ink.NewTable.Body>
</Ink.NewTable>

Searching

Implementing searching with LedgerWrapper.Search

getSearchProps, returned by the useLedger hook, can be unpacked into the Search component for an easy search implementation.

<Ink.LedgerWrapper.Search {...getSearchProps()} />

getSearchProps also provides a clearSearch function that can be used to provide an easy way to clear the search parameter:

<Ink.Button onClick={getSearchProps().clearSearch}>Clear search</Ink.Button>

Custom search parameter

Because useLedger defaults to DRF style endpoints, it assumes that the query string argument that correlates to a search string is search. However, if your API uses another querystring argument for searching, you can denote that with the stateToSearchParam prop in the configuration object.

For example, if the necessary query argument was called q, the configuration object would look as follows:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
stateToSearchParam: (searchQuery) => ({ q: searchQuery }),
})

Pagination

The useLedger hook supports DRF-style page-based pagination. It does not currently support cursor-based pagination. If you have a need for cursor-based pagination, please reach out in the #ink channel.

Implementing pagination with the LedgerWrapper.Pagination component

getPaginationProps, returned by the useLedger hook, can be unpacked into the LedgerWrapper Pagination component for an easy pagination implementation.

<Ink.LedgerWrapper.Pagination {...getPaginationProps()} />

Custom pagination parameters

The Pagination component expects two props: currentPage and totalPages. The hook's default implementation knows how to extrapolate those values from a standard DRF-style endpoint response. However, if you are using a custom endpoint, you will need to customize how to extract those values from the API response in the hook's configuaration object.

On a DRF-style endpoint, currentPage maps out to the page key on the API response, totalPages maps out to the num_pages key on the API response, and totalItems maps out to the count key on the API response. If, for example, your API uses currentPage, totalPageCount, and totalItems, instead of page, num_pages, and count your configuration object would look as follows:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
getNumPages: (results) => results.totalPageCount,
getCurrentPage: (results) => results.currentPage,
getTotalItems: (results) => results.totalItems,
})

Likewise, useLedger defaults pagination to 50 items per page, but you can pass a pageSize prop into the config object to denote otherwise.

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
pageSize: 10,
})

Filtering

The useLedger hook provides a getFilterProps getter that can be used to set up Ledger-style filter functionality. It can be easily implemented with the LedgerWrapper Filter component.

Implementing filtering with LedgerWrapper.Filter

Unpack getFilterProps into the LedgerWrapper.Filter component, along with a filter definition:

const filters = {
groups: [
{
label: "Type",
key: "type",
items: [
{
id: "corporation",
value: "Corporation",
label: "Corporation",
},
{
id: "personal",
value: "Personal",
label: "Personal",
},
],
},
],
}
<Ink.LedgerWrapper.Filter id="filter" items={filters} {...getFilterProps()} />

Custom filter parameters

The useLedger hook operates under the assumption that the user is calling a DRF-style endpoint, in which case filters are appended to an API call as query arguments. If the user is filtering by the type of Corporation, their API call would go from looking like:

http://www.mycoolapi.com/?ordering=&page=1&page_size=50&search=

To:

http://www.mycoolapi.com/?ordering=&page=1&page_size=50&search=&type=Corporation

When assembling the API call, the hook unpacks the filter arguments into the query string before making the request.

However, if you are using a custom endpoint, you can specify how to map the state to the filter parameters in the hook's configuration object. For example, if your API expects filters to be attributed to a query argument called filterBy, the configuration object could be defined as follows:

const {
data,
loading,
getSearchProps,
getPaginationProps,
getSortingProps,
getFilterProps,
selected,
selectableProps,
} = useLedger({
url: "http:///www.mycoolapi.com",
stateToFilterParams: (filters) => ({ filterBy: filters }),
})

In which case the request query string would look like:

http://www.mycoolapi.com/?ordering=&page=1&page_size=50&search=&filterBy={type:Corporation}

Replacing the filtering state

getFilterProps returns two functions to set filter values programatically: setFilterValue and replaceFilters.

setFilterValue sets one filter value at a time:

getFilterProps().setFilterValue("state", "CA")

If you intend to set multiple filter values at once, successive calls setFilterValue will not reflect in the final state of the ledger, leading to an incomplete filtering state:

getFilterProps().setFilterValue("city", "San Francisco");
getFilterProps().setFilterValue("state", "CA");
getFilterProps().setFilterValue("country", "US");
// The final filtering state will be: { country: 'US' }

To resolve this, use the replaceFilters function to replace the current filtering state with the one provided as a parameter. This will trigger a re-render that will include all the filter values as expected.

getFilterProps().replaceFilters({ city: "San Francisco", state: "CA", country: "US" });
// The final filtering state will be: { city: 'San Francisco', state: 'CA', country: 'US' }

Analytics behavior

The useLedger hook has built-in analytics integration with the carta-frontend-platform useAnalytics hook. The analytics provider will be called when the Ledger makes a request.

If your application already utilizes the useAnalytics hook, you can activate the analytics integration in the useLedger by passing useAnalyticsConfig as true:

const { data } = useLedger({
url: "http://www.mycoolapi.com/",
useAnalyticsConfig: true,
})

By default, we send an Object with the following format to the analytics provider:

{
apiResponse: [YOUR_API_RESPONSE],
queryString: [THE_CURRENT_STATE_OF_THE_QUERY_STRING]
}

For more granular implementations, the useAnalyticsConfig configuration object also receives the same parameters as the useAnalytics hook, so you can append specific data to your analytics calls and send custom dispatch functions of Middlewares:

const { data } = useLedger({
url: "http://www.mycoolapi.com/",
useAnalyticsConfig: {
config: { dispatch: () => console.log("My custom analytics call!") },
eventData: { appendedData: "My cool appended data!" },
},
})

Is this page helpful?