diff --git a/enterprise/frontend/src/metabase-enterprise/application_permissions/selectors.ts b/enterprise/frontend/src/metabase-enterprise/application_permissions/selectors.ts
index c6b9d8b8fa2584a326f7ad07bdd92a339be9e1bb..a73d70533f0fbcf6b3b7ceaa4c60fd6b95911a75 100644
--- a/enterprise/frontend/src/metabase-enterprise/application_permissions/selectors.ts
+++ b/enterprise/frontend/src/metabase-enterprise/application_permissions/selectors.ts
@@ -1,6 +1,6 @@
 import _ from "underscore";
 import { t } from "ttag";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { getIn } from "icepick";
 import { Group } from "metabase-types/api";
 import { isAdminGroup } from "metabase/lib/groups";
@@ -42,7 +42,7 @@ const getApplicationPermission = (
 export const getIsDirty = createSelector(
   (state: ApplicationPermissionsState) =>
     state.plugins.applicationPermissionsPlugin?.applicationPermissions,
-  state =>
+  (state: ApplicationPermissionsState) =>
     state.plugins.applicationPermissionsPlugin?.originalApplicationPermissions,
   (permissions, originalPermissions) =>
     !_.isEqual(permissions, originalPermissions),
diff --git a/enterprise/frontend/src/metabase-enterprise/sandboxes/selectors.ts b/enterprise/frontend/src/metabase-enterprise/sandboxes/selectors.ts
index 69f434e6893afc29268adff426a26a9dbb6fbaf4..bddd6a20b53f7aded192454ee06b1730b268ac1b 100644
--- a/enterprise/frontend/src/metabase-enterprise/sandboxes/selectors.ts
+++ b/enterprise/frontend/src/metabase-enterprise/sandboxes/selectors.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { GroupTableAccessPolicyParams, SandboxesState } from "./types";
 import { getPolicyKeyFromParams } from "./utils";
 
diff --git a/frontend/src/metabase/account/notifications/selectors.js b/frontend/src/metabase/account/notifications/selectors.js
index 5558b68e79e6a947f636c7b3df8cd4e9f94bb0bd..8fb751164958d97923f6957d96db59f251f7eafb 100644
--- a/frontend/src/metabase/account/notifications/selectors.js
+++ b/frontend/src/metabase/account/notifications/selectors.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { parseTimestamp } from "metabase/lib/time";
 
 export const getAlertId = ({ params: { alertId } }) => {
diff --git a/frontend/src/metabase/account/profile/selectors.ts b/frontend/src/metabase/account/profile/selectors.ts
index bff59f25a30d2629f6f3dfc543dd213f9d3e34c9..724efb4341c54fc3082b6ff5f2aa5476f0b80544 100644
--- a/frontend/src/metabase/account/profile/selectors.ts
+++ b/frontend/src/metabase/account/profile/selectors.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 import { getUser } from "metabase/selectors/user";
 import { getSettings } from "metabase/selectors/settings";
diff --git a/frontend/src/metabase/admin/people/selectors.js b/frontend/src/metabase/admin/people/selectors.js
index 09ab1ac33bc0b72f7288874235176d53ec12350c..3c5da4591729c2107f2d6fd5b32d86a2ef8b14a7 100644
--- a/frontend/src/metabase/admin/people/selectors.js
+++ b/frontend/src/metabase/admin/people/selectors.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 export const getMemberships = state => state.admin.people.memberships;
 
diff --git a/frontend/src/metabase/admin/permissions/selectors/collection-permissions.js b/frontend/src/metabase/admin/permissions/selectors/collection-permissions.js
index 2c19391602ed9101bb77047d2be562f1c98cfde0..ffb36916b903ab27eee1ab645949cdb822d4d74a 100644
--- a/frontend/src/metabase/admin/permissions/selectors/collection-permissions.js
+++ b/frontend/src/metabase/admin/permissions/selectors/collection-permissions.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { t } from "ttag";
 import { getIn } from "icepick";
 import _ from "underscore";
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/breadcrumbs.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/breadcrumbs.ts
index 08d2872b820554f78dda34f90341935db8a72109..f30e87fb0fad51f0904998ea835acb81bc2e1da9 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/breadcrumbs.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/breadcrumbs.ts
@@ -1,5 +1,5 @@
 import { Group } from "metabase-types/api";
-import { isNotNull } from "metabase/core/utils/types";
+import { isNotFalsy } from "metabase/core/utils/types";
 import type Metadata from "metabase-lib/metadata/Metadata";
 import type Schema from "metabase-lib/metadata/Schema";
 import type Table from "metabase-lib/metadata/Table";
@@ -15,11 +15,17 @@ import {
 } from "../../utils/urls";
 import { DataRouteParams, GroupRouteParams } from "../../types";
 
+export type EditorBreadcrumb = {
+  id?: number | string;
+  text: string;
+  url?: string;
+};
+
 export const getDatabasesEditorBreadcrumbs = (
   params: GroupRouteParams,
   metadata: Metadata,
   group: Group,
-) => {
+): EditorBreadcrumb[] | null => {
   const { groupId, databaseId, schemaName } = params;
 
   if (groupId == null) {
@@ -59,7 +65,7 @@ export const getDatabasesEditorBreadcrumbs = (
 export const getGroupsDataEditorBreadcrumbs = (
   params: DataRouteParams,
   metadata: Metadata,
-) => {
+): EditorBreadcrumb[] | null => {
   const { databaseId, schemaName, tableId } = params;
 
   if (databaseId == null) {
@@ -91,7 +97,7 @@ export const getGroupsDataEditorBreadcrumbs = (
   const hasMultipleSchemas = database.schemasCount() > 1;
 
   if (tableId == null) {
-    return [databaseItem, hasMultipleSchemas && schemaItem].filter(isNotNull);
+    return [databaseItem, hasMultipleSchemas && schemaItem].filter(isNotFalsy);
   }
 
   const table = metadata.table(tableId) as Table;
@@ -102,6 +108,6 @@ export const getGroupsDataEditorBreadcrumbs = (
   };
 
   return [databaseItem, hasMultipleSchemas && schemaItem, tableItem].filter(
-    Boolean,
+    isNotFalsy,
   );
 };
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.ts
index 41e5a21d63169991afff235ce50ee72ce5ab85f8..b4b6fe1c00c0c488b151a6d2d753489295036032 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.ts
@@ -1,11 +1,13 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
+import type { Selector } from "@reduxjs/toolkit";
 import { t } from "ttag";
 
 import { getMetadataWithHiddenTables } from "metabase/selectors/metadata";
 
-import { State } from "metabase-types/store";
 import { ITreeNodeItem } from "metabase/components/tree/types";
 import { isNotNull } from "metabase/core/utils/types";
+
+import { State } from "metabase-types/store";
 import Database from "metabase-lib/metadata/Database";
 import Metadata from "metabase-lib/metadata/Metadata";
 import { EntityId, RawDataRouteParams } from "../../types";
@@ -17,6 +19,20 @@ import {
 import { getDatabase } from "../../utils/metadata";
 import { getIsLoadingDatabaseTables } from "./permission-editor";
 
+type DataTreeNodeItem = {
+  entityId: EntityId;
+  children?: DataTreeNodeItem[];
+} & ITreeNodeItem;
+
+type DataSidebarProps = {
+  title?: string;
+  description?: string;
+  entityGroups: DataTreeNodeItem[][];
+  entityViewFocus?: "database";
+  selectedId?: string | null;
+  filterPlaceholder?: string;
+};
+
 const getRouteParams = (
   _state: State,
   props: { params: RawDataRouteParams },
@@ -32,7 +48,7 @@ const getRouteParams = (
 const getSchemaId = (name: string) => `schema:${name}`;
 const getTableId = (id: string | number) => `table:${id}`;
 
-const getDatabasesSidebar = (metadata: Metadata) => {
+const getDatabasesSidebar = (metadata: Metadata): DataSidebarProps => {
   const entities = metadata
     .databasesList({ savedQuestions: false })
     .map(database => ({
@@ -49,16 +65,11 @@ const getDatabasesSidebar = (metadata: Metadata) => {
   };
 };
 
-type DataTreeNodeItem = {
-  entityId: EntityId;
-  children?: DataTreeNodeItem[];
-} & ITreeNodeItem;
-
 const getTablesSidebar = (
   database: Database,
   schemaName?: string,
   tableId?: string,
-) => {
+): DataSidebarProps => {
   let selectedId = null;
 
   if (tableId != null) {
@@ -102,23 +113,24 @@ const getTablesSidebar = (
   };
 };
 
-export const getDataFocusSidebar = createSelector(
-  getMetadataWithHiddenTables,
-  getRouteParams,
-  getIsLoadingDatabaseTables,
-  (metadata, params, isLoading) => {
-    if (isLoading) {
-      return null;
-    }
+export const getDataFocusSidebar: Selector<State, DataSidebarProps | null> =
+  createSelector(
+    getMetadataWithHiddenTables,
+    getRouteParams,
+    getIsLoadingDatabaseTables,
+    (metadata, params, isLoading) => {
+      if (isLoading) {
+        return null;
+      }
 
-    const { databaseId, schemaName, tableId } = params;
+      const { databaseId, schemaName, tableId } = params;
 
-    if (databaseId == null) {
-      return getDatabasesSidebar(metadata);
-    }
+      if (databaseId == null) {
+        return getDatabasesSidebar(metadata);
+      }
 
-    const database = getDatabase(metadata, databaseId);
+      const database = getDatabase(metadata, databaseId);
 
-    return getTablesSidebar(database, schemaName, tableId);
-  },
-);
+      return getTablesSidebar(database, schemaName, tableId);
+    },
+  );
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.unit.spec.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.unit.spec.ts
index 62456455d1e4059bb3bed1f8f234dc649dcb2361..7428854a152456ad6cc429872e9a2307737d9489 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.unit.spec.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/data-sidebar.unit.spec.ts
@@ -1,7 +1,10 @@
+import type { State } from "metabase-types/store";
 import { RawDataRouteParams } from "../../types";
-import { state } from "./data-permissions.unit.spec.fixtures";
+import { state as mockState } from "./data-permissions.unit.spec.fixtures";
 import { getDataFocusSidebar } from ".";
 
+const state = mockState as unknown as State;
+
 const getRouteProps = ({
   databaseId,
   schemaName,
@@ -18,7 +21,7 @@ describe("getDataFocusSidebar", () => {
   describe("when database is not selected", () => {
     it("returns a correct placeholder for databases list search", () => {
       const sidebarData = getDataFocusSidebar(
-        state as any,
+        state,
         getRouteProps({ databaseId: undefined }),
       );
 
@@ -26,7 +29,7 @@ describe("getDataFocusSidebar", () => {
     });
 
     it("returns list of databases", () => {
-      const sidebarData = getDataFocusSidebar(state as any, getRouteProps({}));
+      const sidebarData = getDataFocusSidebar(state, getRouteProps({}));
 
       expect(sidebarData?.entityGroups).toEqual([
         [
@@ -54,7 +57,7 @@ describe("getDataFocusSidebar", () => {
   describe("when a database is selected", () => {
     it("returns a correct placeholder for databases list search", () => {
       const sidebarData = getDataFocusSidebar(
-        state as any,
+        state,
         getRouteProps({ databaseId: "2" }),
       );
 
@@ -63,7 +66,7 @@ describe("getDataFocusSidebar", () => {
 
     it("returns tree of schemas and tables for a database with schemas", () => {
       const sidebarData = getDataFocusSidebar(
-        state as any,
+        state,
         getRouteProps({ databaseId: "2" }),
       );
 
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/diff.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/diff.ts
index d2f682ef57027384ce7631529ec470ad700f8263..35c4f47bf483ec68547bafc313309a334dda6821 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/diff.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/diff.ts
@@ -1,13 +1,16 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
+import type { Selector } from "@reduxjs/toolkit";
 import _ from "underscore";
-import { State } from "metabase-types/store";
-import Groups from "metabase/entities/groups";
+
 import { diffDataPermissions } from "metabase/admin/permissions/utils/graph";
-import { Group } from "metabase-types/api";
+import Groups from "metabase/entities/groups";
 import { PLUGIN_DATA_PERMISSIONS } from "metabase/plugins";
+
+import { Database } from "metabase-types/api";
+import { State } from "metabase-types/store";
 import { isVirtualCardId } from "metabase-lib/metadata/utils/saved-questions";
 
-const getDatabasesWithTables = createSelector(
+const getDatabasesWithTables: Selector<State, Database[]> = createSelector(
   (state: State) => state.entities.databases,
   (state: State) => state.entities.tables,
   (databases, tables) => {
@@ -23,8 +26,7 @@ const getDatabasesWithTables = createSelector(
       );
 
       return {
-        id: database.id,
-        name: database.name,
+        ...database,
         tables: databaseTables,
       };
     });
@@ -42,13 +44,8 @@ export const getIsDirty = createSelector(
 export const getDiff = createSelector(
   getDatabasesWithTables,
   Groups.selectors.getList,
-  state => state.admin.permissions.dataPermissions,
-  state => state.admin.permissions.originalDataPermissions,
+  (state: State) => state.admin.permissions.dataPermissions,
+  (state: State) => state.admin.permissions.originalDataPermissions,
   (databases, groups, permissions, originalPermissions) =>
-    diffDataPermissions(
-      permissions,
-      originalPermissions,
-      groups as Group[],
-      databases as any,
-    ),
+    diffDataPermissions(permissions, originalPermissions, groups, databases),
 );
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/fields.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/fields.ts
index 6b63c8357fd7494b564f9142e54e6d23410e02ea..340a049fe28591e118fbd0e7823d59099b9bd92e 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/fields.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/fields.ts
@@ -22,7 +22,7 @@ import {
   UNABLE_TO_CHANGE_ADMIN_PERMISSIONS,
 } from "../../constants/messages";
 import { DATA_PERMISSION_OPTIONS } from "../../constants/data-permissions";
-import { TableEntityId } from "../../types";
+import { TableEntityId, PermissionSectionConfig } from "../../types";
 
 const buildAccessPermission = (
   entityId: TableEntityId,
@@ -114,7 +114,7 @@ export const buildFieldsPermissions = (
   permissions: GroupsPermissions,
   defaultGroup: Group,
   database: Database,
-) => {
+): PermissionSectionConfig[] => {
   const accessPermission = buildAccessPermission(
     entityId,
     groupId,
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.ts
index 15fc5f53000241f6999d07dcd5035389aeecac46..06aac8b0f3dddfcb64ab5112714b808958374c60 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { t } from "ttag";
 
 import { State } from "metabase-types/store";
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.unit.spec.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.unit.spec.ts
index 2540c7241b70f66a10dc1a2a5ad1968bd8455f47..836d615b6bbbdc0c41f8192ca0b2fff71d93cd7a 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.unit.spec.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/group-sidebar.unit.spec.ts
@@ -1,9 +1,12 @@
-import { state } from "./data-permissions.unit.spec.fixtures";
+import type { State } from "metabase-types/store";
+import { state as mockState } from "./data-permissions.unit.spec.fixtures";
 import { getGroupsDataPermissionEditor } from ".";
 
+const state = mockState as unknown as State;
+
 describe("getGroupsDataPermissionEditor", () => {
   it("returns data for permission editor header", () => {
-    const permissionEditorData = getGroupsDataPermissionEditor(state as any, {
+    const permissionEditorData = getGroupsDataPermissionEditor(state, {
       params: {
         databaseId: 3,
       },
@@ -23,7 +26,7 @@ describe("getGroupsDataPermissionEditor", () => {
   });
 
   it("returns entities list for permissions editor", () => {
-    const entities = getGroupsDataPermissionEditor(state as any, {
+    const entities = getGroupsDataPermissionEditor(state, {
       params: {
         databaseId: 3,
       },
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/groups.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/groups.ts
index 4156bea63b478dcb801228df5632e87788e42ec2..423da677e2b8bb0b503b773ed445dcefd9f0fb85 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/groups.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/groups.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import _ from "underscore";
 
 import Groups from "metabase/entities/groups";
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/permission-editor.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/permission-editor.ts
index ce8b09f6828696bc654fe81ed8c5e79e989b89e7..041a94fa69600644f37a83f9bdb9db10db218006 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/permission-editor.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/permission-editor.ts
@@ -1,4 +1,5 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
+import type { Selector } from "@reduxjs/toolkit";
 import { msgid, ngettext, t } from "ttag";
 import _ from "underscore";
 
@@ -8,7 +9,7 @@ import Groups from "metabase/entities/groups";
 import Tables from "metabase/entities/tables";
 
 import { isAdminGroup, isDefaultGroup } from "metabase/lib/groups";
-import { Group, GroupsPermissions } from "metabase-types/api";
+import { Database, Group, GroupsPermissions, Table } from "metabase-types/api";
 import { State } from "metabase-types/store";
 import { PLUGIN_FEATURE_LEVEL_PERMISSIONS } from "metabase/plugins";
 import Schema from "metabase-lib/metadata/Schema";
@@ -19,13 +20,18 @@ import {
   getPermissionSubject,
 } from "../../utils/data-entity-id";
 
-import { DataRouteParams, RawGroupRouteParams } from "../../types";
+import {
+  DataRouteParams,
+  RawGroupRouteParams,
+  PermissionSectionConfig,
+} from "../../types";
 import { buildFieldsPermissions } from "./fields";
 import { buildTablesPermissions } from "./tables";
 import { buildSchemasPermissions } from "./schemas";
 import {
   getDatabasesEditorBreadcrumbs,
   getGroupsDataEditorBreadcrumbs,
+  EditorBreadcrumb,
 } from "./breadcrumbs";
 import { getOrderedGroups } from "./groups";
 
@@ -52,7 +58,14 @@ export const getLoadingDatabaseTablesError = (
   });
 };
 
-const getRouteParams = (_state: State, props: { params: DataRouteParams }) => {
+type RouteParamsSelectorParameters = {
+  params: DataRouteParams;
+};
+
+const getRouteParams = (
+  _state: State,
+  props: RouteParamsSelectorParameters,
+) => {
   const { databaseId, schemaName, tableId } = props.params;
   return {
     databaseId,
@@ -248,92 +261,124 @@ export const getDatabasesPermissionEditor = createSelector(
   },
 );
 
-export const getGroupsDataPermissionEditor = createSelector(
-  getMetadataWithHiddenTables,
-  getRouteParams,
-  getDataPermissions,
-  getOrderedGroups,
-  (metadata, params, permissions, groups: Group[][]) => {
-    const { databaseId, schemaName, tableId } = params;
-    const database = metadata?.database(databaseId);
+type DataPermissionEditorEntity = {
+  id: Group["id"];
+  name: Group["name"];
+  hint: string | null;
+  entityId: {
+    databaseId?: Database["id"];
+    schemaName?: Schema["name"];
+    tableId?: Table["id"];
+  };
+  permissions?: PermissionSectionConfig[];
+};
 
-    if (!permissions || databaseId == null || !database) {
-      return null;
-    }
+type DataPermissionEditorProps = {
+  title: string;
+  filterPlaceholder: string;
+  breadcrumbs: EditorBreadcrumb[] | null;
+  columns: { name: string }[];
+  entities: DataPermissionEditorEntity[];
+};
 
-    const sortedGroups = groups.flat();
+type GetGroupsDataPermissionEditorSelectorParameters =
+  RouteParamsSelectorParameters & {
+    includeHiddenTables?: boolean;
+  };
 
-    const defaultGroup = _.find(sortedGroups, isDefaultGroup);
+type GetGroupsDataPermissionEditorSelector = Selector<
+  State,
+  DataPermissionEditorProps | null,
+  GetGroupsDataPermissionEditorSelectorParameters[]
+>;
+
+export const getGroupsDataPermissionEditor: GetGroupsDataPermissionEditorSelector =
+  createSelector(
+    getMetadataWithHiddenTables,
+    getRouteParams,
+    getDataPermissions,
+    getOrderedGroups,
+    (metadata, params, permissions, groups) => {
+      const { databaseId, schemaName, tableId } = params;
+      const database = metadata?.database(databaseId);
+
+      if (!permissions || databaseId == null || !database) {
+        return null;
+      }
 
-    if (!defaultGroup) {
-      throw new Error("No default group found");
-    }
+      const sortedGroups = groups.flat();
 
-    const permissionSubject = getPermissionSubject(params);
-    const columns = [
-      { name: t`Group name` },
-      { name: t`Data access` },
-      { name: t`Native query editing` },
-      ...PLUGIN_FEATURE_LEVEL_PERMISSIONS.getDataColumns(permissionSubject),
-    ];
+      const defaultGroup = _.find(sortedGroups, isDefaultGroup);
 
-    const entities = sortedGroups.map(group => {
-      const isAdmin = isAdminGroup(group);
-      let groupPermissions;
-
-      if (tableId != null) {
-        groupPermissions = buildFieldsPermissions(
-          {
-            databaseId,
-            schemaName,
-            tableId,
-          },
-          group.id,
-          isAdmin,
-          permissions,
-          defaultGroup,
-          database,
-        );
-      } else if (schemaName != null) {
-        groupPermissions = buildTablesPermissions(
-          {
-            databaseId,
-            schemaName,
-          },
-          group.id,
-          isAdmin,
-          permissions,
-          defaultGroup,
-        );
-      } else if (databaseId != null) {
-        groupPermissions = buildSchemasPermissions(
-          {
-            databaseId,
-          },
-          group.id,
-          isAdmin,
-          permissions,
-          defaultGroup,
-        );
+      if (!defaultGroup) {
+        throw new Error("No default group found");
       }
 
+      const permissionSubject = getPermissionSubject(params);
+      const columns = [
+        { name: t`Group name` },
+        { name: t`Data access` },
+        { name: t`Native query editing` },
+        ...PLUGIN_FEATURE_LEVEL_PERMISSIONS.getDataColumns(permissionSubject),
+      ];
+
+      const entities = sortedGroups.map(group => {
+        const isAdmin = isAdminGroup(group);
+        let groupPermissions;
+
+        if (tableId != null) {
+          groupPermissions = buildFieldsPermissions(
+            {
+              databaseId,
+              schemaName,
+              tableId,
+            },
+            group.id,
+            isAdmin,
+            permissions,
+            defaultGroup,
+            database,
+          );
+        } else if (schemaName != null) {
+          groupPermissions = buildTablesPermissions(
+            {
+              databaseId,
+              schemaName,
+            },
+            group.id,
+            isAdmin,
+            permissions,
+            defaultGroup,
+          );
+        } else if (databaseId != null) {
+          groupPermissions = buildSchemasPermissions(
+            {
+              databaseId,
+            },
+            group.id,
+            isAdmin,
+            permissions,
+            defaultGroup,
+          );
+        }
+
+        return {
+          id: group.id,
+          name: group.name,
+          hint: isAdmin
+            ? t`The Administrators group is special, and always has Unrestricted access.`
+            : null,
+          entityId: params,
+          permissions: groupPermissions,
+        };
+      });
+
       return {
-        id: group.id,
-        name: group.name,
-        hint: isAdmin
-          ? t`The Administrators group is special, and always has Unrestricted access.`
-          : null,
-        entityId: params,
-        permissions: groupPermissions,
+        title: t`Permissions for`,
+        filterPlaceholder: t`Search for a group`,
+        breadcrumbs: getGroupsDataEditorBreadcrumbs(params, metadata),
+        columns,
+        entities,
       };
-    });
-
-    return {
-      title: t`Permissions for`,
-      filterPlaceholder: t`Search for a group`,
-      breadcrumbs: getGroupsDataEditorBreadcrumbs(params, metadata),
-      columns,
-      entities,
-    };
-  },
-);
+    },
+  );
diff --git a/frontend/src/metabase/admin/permissions/selectors/data-permissions/tables.ts b/frontend/src/metabase/admin/permissions/selectors/data-permissions/tables.ts
index e031d46ccacc6b66089fda773d0a0bd270e03a72..905e9245777d74807d0be6f95c7705666ac09a47 100644
--- a/frontend/src/metabase/admin/permissions/selectors/data-permissions/tables.ts
+++ b/frontend/src/metabase/admin/permissions/selectors/data-permissions/tables.ts
@@ -19,7 +19,7 @@ import {
   getPermissionWarningModal,
   getControlledDatabaseWarningModal,
 } from "../confirmations";
-import { SchemaEntityId } from "../../types";
+import { SchemaEntityId, PermissionSectionConfig } from "../../types";
 import { getGroupFocusPermissionsUrl } from "../../utils/urls";
 
 const buildAccessPermission = (
@@ -104,7 +104,7 @@ export const buildTablesPermissions = (
   isAdmin: boolean,
   permissions: GroupsPermissions,
   defaultGroup: Group,
-) => {
+): PermissionSectionConfig[] => {
   const accessPermission = buildAccessPermission(
     entityId,
     groupId,
diff --git a/frontend/src/metabase/admin/permissions/types.ts b/frontend/src/metabase/admin/permissions/types.ts
index 405ac8d7f822ad3364b76270c5b1a6a941bcfbfc..8d283414a3a3ebddc1b6b9218db26313a85a1916 100644
--- a/frontend/src/metabase/admin/permissions/types.ts
+++ b/frontend/src/metabase/admin/permissions/types.ts
@@ -40,3 +40,31 @@ export type EntityId = DatabaseEntityId &
 export type DataPermission = "data" | "download" | "data-model" | "details";
 
 export type PermissionSubject = "schemas" | "tables" | "fields";
+
+export type PermissionSectionConfig = {
+  permission: string;
+  type: string;
+  isDisabled: boolean;
+  disabledTooltip: string | null;
+  isHighlighted: boolean;
+  value: string;
+  warning: string | null;
+  options: {
+    label: string;
+    value: string;
+    icon: string;
+    iconColor: string;
+  }[];
+  actions: {
+    controlled: {
+      label: string;
+      icon: string;
+      iconColor: string;
+      actionCreator: (...args: unknown[]) => void;
+    }[];
+  };
+  postActions: {
+    controlled: null | ((...args: unknown[]) => void);
+  };
+  confirmations: unknown[];
+};
diff --git a/frontend/src/metabase/admin/permissions/utils/graph/permissions-diff.ts b/frontend/src/metabase/admin/permissions/utils/graph/permissions-diff.ts
index 163bfad2f21a8037cb8fc75281e5d1b8739618de..d1e943aa4221eb7db35fa394fa0777cb2ac454f0 100644
--- a/frontend/src/metabase/admin/permissions/utils/graph/permissions-diff.ts
+++ b/frontend/src/metabase/admin/permissions/utils/graph/permissions-diff.ts
@@ -1,9 +1,9 @@
 import type {
+  ConcreteTableId,
+  Database,
   Group,
   GroupsPermissions,
-  ConcreteTableId,
 } from "metabase-types/api";
-import type Database from "metabase-lib/metadata/Database";
 import {
   getFieldsPermission,
   getNativePermission,
@@ -41,7 +41,7 @@ function diffDatabasePermissions(
     databaseDiff.native = newNativePerm;
   }
   // check each table in this db
-  for (const table of database.tables) {
+  for (const table of database.tables ?? []) {
     const oldFieldsPerm = getFieldsPermission(
       oldPerms,
       groupId,
diff --git a/frontend/src/metabase/admin/settings/selectors.js b/frontend/src/metabase/admin/settings/selectors.js
index a3dec68ba0f850425531a41d7fb942ae6353182a..4b74d2c14dc85cd53d01034102ffb401d36ff237 100644
--- a/frontend/src/metabase/admin/settings/selectors.js
+++ b/frontend/src/metabase/admin/settings/selectors.js
@@ -1,7 +1,7 @@
 /* eslint-disable react/display-name */
 import React from "react";
 import _ from "underscore";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { t, jt } from "ttag";
 import ExternalLink from "metabase/core/components/ExternalLink";
 import MetabaseSettings from "metabase/lib/settings";
diff --git a/frontend/src/metabase/auth/selectors.ts b/frontend/src/metabase/auth/selectors.ts
index 2bff3bbdee297a5e0311f84465606de98509ce95..3e591d6347a3096c6cbcdcf2a0180e585adb6fbe 100644
--- a/frontend/src/metabase/auth/selectors.ts
+++ b/frontend/src/metabase/auth/selectors.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 import { getSettings } from "metabase/selectors/settings";
 import { PLUGIN_AUTH_PROVIDERS } from "metabase/plugins";
diff --git a/frontend/src/metabase/core/components/Select/Select.tsx b/frontend/src/metabase/core/components/Select/Select.tsx
index 845f8ebd6ea2a87a132c98c1d00a276191073e44..5ff24052e08b1a819ae4b6a0627a4d0a6228061f 100644
--- a/frontend/src/metabase/core/components/Select/Select.tsx
+++ b/frontend/src/metabase/core/components/Select/Select.tsx
@@ -9,7 +9,7 @@ import React, {
 
 import _ from "underscore";
 import cx from "classnames";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import Icon from "metabase/components/Icon";
 import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
 import SelectButton, {
diff --git a/frontend/src/metabase/core/utils/types/index.ts b/frontend/src/metabase/core/utils/types/index.ts
index 10b604b77046b0b47ba6e2ba11bc468402e643c1..eea524d655708d7561b53642ce62e241627eece6 100644
--- a/frontend/src/metabase/core/utils/types/index.ts
+++ b/frontend/src/metabase/core/utils/types/index.ts
@@ -1 +1 @@
-export { isNotNull, checkNotNull } from "./types";
+export * from "./types";
diff --git a/frontend/src/metabase/core/utils/types/types.ts b/frontend/src/metabase/core/utils/types/types.ts
index db53810a336cb210d4152fc76d5def58e9191509..d5329923776027abcccaafbacc85b80cf476a759 100644
--- a/frontend/src/metabase/core/utils/types/types.ts
+++ b/frontend/src/metabase/core/utils/types/types.ts
@@ -2,6 +2,12 @@ export const isNotNull = <T>(value: T | null | undefined): value is T => {
   return value != null;
 };
 
+export const isNotFalsy = <T>(
+  value: T | null | undefined | false,
+): value is T => {
+  return value != null;
+};
+
 export const checkNotNull = <T>(value: T | null | undefined): T => {
   if (value != null) {
     return value;
diff --git a/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx b/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx
index ecc34ea3acc42008eaa6df1170591fa80602b27d..619bf81dcc18a8ac61eb783d4aab9f572d73b55f 100644
--- a/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx
+++ b/frontend/src/metabase/dashboard/components/AddSeriesModal/AddSeriesModal.jsx
@@ -4,7 +4,7 @@ import PropTypes from "prop-types";
 import { t } from "ttag";
 import { getIn } from "icepick";
 import { connect } from "react-redux";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import _ from "underscore";
 
 import Visualization from "metabase/visualizations/components/Visualization";
diff --git a/frontend/src/metabase/dashboard/selectors.js b/frontend/src/metabase/dashboard/selectors.js
index 4654af7f3b71b23be31477ba85265f1b9f3a0782..4e6262e81135f7ec4ef9dee944f20624e0d418ae 100644
--- a/frontend/src/metabase/dashboard/selectors.js
+++ b/frontend/src/metabase/dashboard/selectors.js
@@ -1,6 +1,6 @@
 import _ from "underscore";
 
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { getMetadata } from "metabase/selectors/metadata";
 import { LOAD_COMPLETE_FAVICON } from "metabase/hoc/Favicon";
 
diff --git a/frontend/src/metabase/databases/containers/DatabaseSyncModal/DatabaseSyncModal.tsx b/frontend/src/metabase/databases/containers/DatabaseSyncModal/DatabaseSyncModal.tsx
index b45b7d04e982548310340c3567dfa4842af7ca3a..820038f74c6048ba30cf778d6e03b9d0288c276e 100644
--- a/frontend/src/metabase/databases/containers/DatabaseSyncModal/DatabaseSyncModal.tsx
+++ b/frontend/src/metabase/databases/containers/DatabaseSyncModal/DatabaseSyncModal.tsx
@@ -1,5 +1,5 @@
 import { connect } from "react-redux";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import _ from "underscore";
 import Databases from "metabase/entities/databases";
 import DatabaseCandidates from "metabase/entities/database-candidates";
diff --git a/frontend/src/metabase/entities/bookmarks.js b/frontend/src/metabase/entities/bookmarks.js
index d9e8c9ea021cbac1ec4d7e46627236e5b49dc25c..006d2099c0263fea683465fd7255ed8257815c88 100644
--- a/frontend/src/metabase/entities/bookmarks.js
+++ b/frontend/src/metabase/entities/bookmarks.js
@@ -1,6 +1,6 @@
 import { assoc, updateIn, dissoc } from "icepick";
 import _ from "underscore";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { createEntity } from "metabase/lib/entities";
 import Collections from "metabase/entities/collections";
 import Dashboards from "metabase/entities/dashboards";
diff --git a/frontend/src/metabase/entities/collections/collections.ts b/frontend/src/metabase/entities/collections/collections.ts
index 3925687002e4906cefe8e024af15644e237ff3a6..a32837b4c824538a061182258847d8b841d1f82b 100644
--- a/frontend/src/metabase/entities/collections/collections.ts
+++ b/frontend/src/metabase/entities/collections/collections.ts
@@ -1,5 +1,5 @@
 import { t } from "ttag";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 import { GET } from "metabase/lib/api";
 import { createEntity, undo } from "metabase/lib/entities";
diff --git a/frontend/src/metabase/entities/collections/getInitialCollectionId.ts b/frontend/src/metabase/entities/collections/getInitialCollectionId.ts
index 642903704d753917a5f0c9695bea427e397e0b18..4762ce1eadf10ef21be468c0715a98bb10adf85a 100644
--- a/frontend/src/metabase/entities/collections/getInitialCollectionId.ts
+++ b/frontend/src/metabase/entities/collections/getInitialCollectionId.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import type { Location } from "history";
 
 import * as Urls from "metabase/lib/urls";
diff --git a/frontend/src/metabase/entities/containers/EntityListLoader.jsx b/frontend/src/metabase/entities/containers/EntityListLoader.jsx
index 4f528c029c7a07daf59d6922c0da93fe67e72783..9a935794d26e14283c40d961a0be0358844351e0 100644
--- a/frontend/src/metabase/entities/containers/EntityListLoader.jsx
+++ b/frontend/src/metabase/entities/containers/EntityListLoader.jsx
@@ -2,9 +2,8 @@
 import React from "react";
 import PropTypes from "prop-types";
 import { connect } from "react-redux";
+import { createSelector } from "@reduxjs/toolkit";
 import _ from "underscore";
-import { createSelector } from "reselect";
-import { createMemoizedSelector } from "metabase/lib/redux";
 
 import paginationState from "metabase/hoc/PaginationState";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
@@ -72,13 +71,12 @@ const getEntityQuery = (state, props) =>
     ? props.entityQuery(state, props)
     : props.entityQuery;
 
-// NOTE: Memoize entityQuery so we don't re-render even if a new but identical
-// object is created. This works because entityQuery must be JSON serializable
-// NOTE: Technically leaks a small amount of memory because it uses an unbounded
-// memoization cache, but that's probably ok.
-const getMemoizedEntityQuery = createMemoizedSelector(
-  [getEntityQuery],
+const getMemoizedEntityQuery = createSelector(
+  getEntityQuery,
   entityQuery => entityQuery,
+  {
+    equalityFn: _.isEqual,
+  },
 );
 
 class EntityListLoaderInner extends React.Component {
diff --git a/frontend/src/metabase/entities/containers/EntityObjectLoader.jsx b/frontend/src/metabase/entities/containers/EntityObjectLoader.jsx
index 70ea3456de16f3ffd6e850ddb48ac814e0a75f55..86103ac87e3a81a06fce9743f49f4d524b7ffc56 100644
--- a/frontend/src/metabase/entities/containers/EntityObjectLoader.jsx
+++ b/frontend/src/metabase/entities/containers/EntityObjectLoader.jsx
@@ -1,10 +1,9 @@
 /* eslint-disable react/prop-types */
 import React from "react";
 import { connect } from "react-redux";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import _ from "underscore";
 
-import { createMemoizedSelector } from "metabase/lib/redux";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
 import entityType from "./EntityType";
 
@@ -23,11 +22,12 @@ const CONSUMED_PROPS = [
   "requestType",
 ];
 
-// NOTE: Memoize entityQuery so we don't re-render even if a new but identical
-// object is created. This works because entityQuery must be JSON serializable
-const getMemoizedEntityQuery = createMemoizedSelector(
+const getMemoizedEntityQuery = createSelector(
   (state, entityQuery) => entityQuery,
   entityQuery => entityQuery,
+  {
+    equalityFn: _.isEqual,
+  },
 );
 
 class EntityObjectLoaderInner extends React.Component {
diff --git a/frontend/src/metabase/entities/databases.js b/frontend/src/metabase/entities/databases.js
index 6925a42ef7161146a009812058ca0e73b0d84735..ba94a37f000e1a9575262574ff5bbfcb6596633c 100644
--- a/frontend/src/metabase/entities/databases.js
+++ b/frontend/src/metabase/entities/databases.js
@@ -1,7 +1,7 @@
 import { normalize } from "normalizr";
 import _ from "underscore";
 
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { createEntity } from "metabase/lib/entities";
 import * as Urls from "metabase/lib/urls";
 import { color } from "metabase/lib/colors";
diff --git a/frontend/src/metabase/entities/persisted-models.js b/frontend/src/metabase/entities/persisted-models.js
index f2267f339c9786ac63b1a9acea3352a9bbcb938a..934e0e2bd5f737ee8d9296154f7bcf0680d9ebeb 100644
--- a/frontend/src/metabase/entities/persisted-models.js
+++ b/frontend/src/metabase/entities/persisted-models.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { PersistedModelSchema } from "metabase/schema";
 import { createEntity } from "metabase/lib/entities";
 import { CardApi, PersistedModelsApi } from "metabase/services";
diff --git a/frontend/src/metabase/entities/snippet-collections.js b/frontend/src/metabase/entities/snippet-collections.js
index b6acb049577044aedceab982a59702406a553ad9..20c38df717a07e998a25524186382bf339526ba7 100644
--- a/frontend/src/metabase/entities/snippet-collections.js
+++ b/frontend/src/metabase/entities/snippet-collections.js
@@ -1,6 +1,6 @@
 import _ from "underscore";
 import { t } from "ttag";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 import { createEntity, undo } from "metabase/lib/entities";
 import { SnippetCollectionSchema } from "metabase/schema";
diff --git a/frontend/src/metabase/entities/tables.js b/frontend/src/metabase/entities/tables.js
index a198fd8f8805d94bda32df0f53e04979d0d02835..2d3cb73f33b8e693ebe7e310a1181be7cf926535 100644
--- a/frontend/src/metabase/entities/tables.js
+++ b/frontend/src/metabase/entities/tables.js
@@ -1,6 +1,6 @@
 import { t } from "ttag";
 import _ from "underscore";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { updateIn } from "icepick";
 import { createEntity, notify } from "metabase/lib/entities";
 import {
diff --git a/frontend/src/metabase/lib/entities.js b/frontend/src/metabase/lib/entities.js
index c4338c3ad92fc4df42b7521fbca10d3aeda3a9fa..2610d56e66878b0af936913f7a2231ed276d9eaa 100644
--- a/frontend/src/metabase/lib/entities.js
+++ b/frontend/src/metabase/lib/entities.js
@@ -73,7 +73,7 @@ import createCachedSelector from "re-reselect";
 // NOTE: need to use inflection directly here due to circular dependency
 import inflection from "inflection";
 
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { normalize, denormalize, schema } from "normalizr";
 import { getIn, merge } from "icepick";
 import _ from "underscore";
diff --git a/frontend/src/metabase/lib/redux/utils.js b/frontend/src/metabase/lib/redux/utils.js
index 164934ff5e055c85c1e489ed60783a47c6b2eb3c..26efd9649c7d7234519a19b15e33cfb2d09f7e2f 100644
--- a/frontend/src/metabase/lib/redux/utils.js
+++ b/frontend/src/metabase/lib/redux/utils.js
@@ -3,7 +3,6 @@ import _ from "underscore";
 import { getIn } from "icepick";
 import { normalize } from "normalizr";
 import { compose } from "@reduxjs/toolkit";
-import { createSelectorCreator } from "reselect";
 
 import * as MetabaseAnalytics from "metabase/lib/analytics";
 import {
@@ -183,11 +182,6 @@ export const formDomOnlyProps = ({
   ...domProps
 }) => domProps;
 
-export const createMemoizedSelector = createSelectorCreator(
-  _.memoize,
-  (...args) => JSON.stringify(args),
-);
-
 // THUNK DECORATORS
 
 /**
diff --git a/frontend/src/metabase/metabot/selectors.ts b/frontend/src/metabase/metabot/selectors.ts
index 663f78e52c5cb32724dc35b2a9b5455f25d968c1..f7b3ac01b8754aa618f572fbd338237f6080b41a 100644
--- a/frontend/src/metabase/metabot/selectors.ts
+++ b/frontend/src/metabase/metabot/selectors.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { getMetadata } from "metabase/selectors/metadata";
 import { State } from "metabase-types/store";
 import Question from "metabase-lib/Question";
diff --git a/frontend/src/metabase/pulse/selectors.js b/frontend/src/metabase/pulse/selectors.js
index ee8b4ef454434df889d14e62e7dc04e84c1fcfac..cd8e82e56596d2253cc8bc2f7d6df8f9bdba532e 100644
--- a/frontend/src/metabase/pulse/selectors.js
+++ b/frontend/src/metabase/pulse/selectors.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import _ from "underscore";
 
 export const getEditingPulse = state => state.pulse.editingPulse;
diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js
index dcd28e0426a56220936c9b9e8701a183ee212257..14606d86870d7ae4a6559be394f6ec04080e3414 100644
--- a/frontend/src/metabase/query_builder/selectors.js
+++ b/frontend/src/metabase/query_builder/selectors.js
@@ -1,7 +1,7 @@
 /*eslint no-use-before-define: "error"*/
 
 import d3 from "d3";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import createCachedSelector from "re-reselect";
 import _ from "underscore";
 import { getIn, merge, updateIn } from "icepick";
diff --git a/frontend/src/metabase/reference/selectors.js b/frontend/src/metabase/reference/selectors.js
index 5251045e56734bc18ef0cbbba3dd46a72e9136ce..4cea36d1454e5c1c778a334035fc293ba6b5ec3a 100644
--- a/frontend/src/metabase/reference/selectors.js
+++ b/frontend/src/metabase/reference/selectors.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { assoc, getIn } from "icepick";
 
 import Dashboards from "metabase/entities/dashboards";
diff --git a/frontend/src/metabase/selectors/app.ts b/frontend/src/metabase/selectors/app.ts
index e2809f5f9f06b073d48f5eba924cc5112123fee3..0339272faed6d6d1af18d1dc8dcd63dc57f8f48e 100644
--- a/frontend/src/metabase/selectors/app.ts
+++ b/frontend/src/metabase/selectors/app.ts
@@ -1,5 +1,5 @@
 import { Location } from "history";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { getUser } from "metabase/selectors/user";
 import {
   getIsEditing as getIsEditingDashboard,
diff --git a/frontend/src/metabase/selectors/embed.ts b/frontend/src/metabase/selectors/embed.ts
index 5410680ce428ec131e3761026db81dcd72c44442..b01d1135071b52daaf3d3bb7ca38446e99ba8907 100644
--- a/frontend/src/metabase/selectors/embed.ts
+++ b/frontend/src/metabase/selectors/embed.ts
@@ -1,7 +1,7 @@
 import { isWithinIframe } from "metabase/lib/dom";
 import { State } from "metabase-types/store";
 
-export const getIsEmbedded = () => {
+export const getIsEmbedded = (state: State) => {
   return isWithinIframe();
 };
 
diff --git a/frontend/src/metabase/selectors/metadata.js b/frontend/src/metabase/selectors/metadata.js
index 85f58fd6f875432578b8a0b949223fbced199a36..4ad05ca3b49eebdb6a4da28e7bcfd2100587d78d 100644
--- a/frontend/src/metabase/selectors/metadata.js
+++ b/frontend/src/metabase/selectors/metadata.js
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 import _ from "underscore";
 import { isVirtualCardId } from "metabase-lib/metadata/utils/saved-questions";
diff --git a/frontend/src/metabase/selectors/settings.ts b/frontend/src/metabase/selectors/settings.ts
index a1231370fac88edc3abf46df37260a124124de99..f85de9746506e6cfbff671f37d93493030737648 100644
--- a/frontend/src/metabase/selectors/settings.ts
+++ b/frontend/src/metabase/selectors/settings.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 
 import type { Settings, SettingKey, TokenFeatures } from "metabase-types/api";
 import type { State } from "metabase-types/store";
diff --git a/frontend/src/metabase/selectors/user.ts b/frontend/src/metabase/selectors/user.ts
index 0f46fdf65521df2e550ba5a6aaa077ed681a0df7..0f82edad0ed20baf6f4d2cfb299ae849dc3acf34 100644
--- a/frontend/src/metabase/selectors/user.ts
+++ b/frontend/src/metabase/selectors/user.ts
@@ -1,4 +1,4 @@
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { PLUGIN_APPLICATION_PERMISSIONS } from "metabase/plugins";
 
 import type { State } from "metabase-types/store";
diff --git a/frontend/src/metabase/styled-components/selectors.ts b/frontend/src/metabase/styled-components/selectors.ts
index 944b2540038b53fb0b16f5eb3f8ca0b4558bcff2..fdfb3e09b6ff85da1d8b2ab42478259ea1ddc964 100644
--- a/frontend/src/metabase/styled-components/selectors.ts
+++ b/frontend/src/metabase/styled-components/selectors.ts
@@ -1,5 +1,5 @@
 import _ from "underscore";
-import { createSelector } from "reselect";
+import { createSelector } from "@reduxjs/toolkit";
 import { getSettings } from "metabase/selectors/settings";
 import { getEmbedOptions } from "metabase/selectors/embed";
 
diff --git a/package.json b/package.json
index de020bd58c7d685dfcdb2107452c4570f7f91af6..371be6246902375b8ecfdeed8f9619b9eaa200e0 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
     "number-to-locale-string": "^1.0.1",
     "password-generator": "^2.0.1",
     "prop-types": "^15.5.7",
-    "re-reselect": "^3.4.0",
+    "re-reselect": "^4.0.1",
     "react": "~16.14.0",
     "react-ace": "^9.5.0",
     "react-ansi-style": "^1.0.0",
@@ -97,7 +97,6 @@
     "regenerator": "^0.14.1",
     "rehype-external-links": "^2.0.1",
     "remark-gfm": "1.0.0",
-    "reselect": "^3.0.0",
     "screenfull": "^4.2.1",
     "server-text-width": "^1.0.2",
     "simple-statistics": "^3.0.0",
diff --git a/yarn.lock b/yarn.lock
index 1e79338f76a46c2cee900dfec565e774d93d2a7a..022818f1d9b839db44c96a02bc2603cb45b4ba55 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -18382,10 +18382,10 @@ rc@^1.2.8:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-re-reselect@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd"
-  integrity sha512-JsecfN+JlckncVXTWFWjn0Vk6uInl8GSf4eEd9tTk5qXHlgqkPdILpnYpgZcISXNYAzvfvsCZviaDk8AxyS5sg==
+re-reselect@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-4.0.1.tgz#21a2306d11bbf377ac78687aa46e1a8848974194"
+  integrity sha512-xVTNGQy/dAxOolunBLmVMGZ49VUUR1s8jZUiJQb+g1sI63GAv9+a5Jas9yHvdxeUgiZkU9r3gDExDorxHzOgRA==
 
 react-ace@^9.5.0:
   version "9.5.0"
@@ -19358,11 +19358,6 @@ requires-port@^1.0.0:
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
 
-reselect@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
-  integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
-
 reselect@^4.1.7:
   version "4.1.7"
   resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42"