diff --git a/package-lock.json b/package-lock.json
index ea919b97f96aa3a2acdd3f36ab9c00ebf3f5c0f5..4a1bd3308f2a7c038cc84ecd9287171fba36c54c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,6 @@
       "name": "ce-deploy-ui",
       "version": "0.1.0",
       "dependencies": {
-        "@ahooksjs/use-url-state": "^3.5.0",
         "@emotion/react": "^11.13.3",
         "@emotion/styled": "^11.13.0",
         "@ess-ics/ce-ui-common": "7.5.0",
@@ -73,20 +72,6 @@
       "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==",
       "dev": true
     },
-    "node_modules/@ahooksjs/use-url-state": {
-      "version": "3.5.1",
-      "resolved": "https://registry.npmjs.org/@ahooksjs/use-url-state/-/use-url-state-3.5.1.tgz",
-      "integrity": "sha512-XTrOLZKOAXahDD1Evg+aSN6qNzoh/FuvRKbUtB/0RhYvz57tyXRPbED0KXK4h2C3ZyHUKBJcVCSDcd6EsTyMyQ==",
-      "dependencies": {
-        "ahooks": "^3.4.1",
-        "query-string": "^6.9.0",
-        "tslib": "^2.4.1"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
-        "react-router": "^5.0.0 || ^6.0.0"
-      }
-    },
     "node_modules/@ai-sdk/anthropic": {
       "version": "0.0.27",
       "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-0.0.27.tgz",
@@ -4690,28 +4675,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/ahooks": {
-      "version": "3.8.1",
-      "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.8.1.tgz",
-      "integrity": "sha512-JoP9+/RWO7MnI/uSKdvQ8WB10Y3oo1PjLv+4Sv4Vpm19Z86VUMdXh+RhWvMGxZZs06sq2p0xVtFk8Oh5ZObsoA==",
-      "dependencies": {
-        "@babel/runtime": "^7.21.0",
-        "dayjs": "^1.9.1",
-        "intersection-observer": "^0.12.0",
-        "js-cookie": "^3.0.5",
-        "lodash": "^4.17.21",
-        "react-fast-compare": "^3.2.2",
-        "resize-observer-polyfill": "^1.5.1",
-        "screenfull": "^5.0.0",
-        "tslib": "^2.4.1"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
     "node_modules/ai": {
       "version": "3.2.16",
       "resolved": "https://registry.npmjs.org/ai/-/ai-3.2.16.tgz",
@@ -6250,7 +6213,8 @@
     "node_modules/dayjs": {
       "version": "1.11.13",
       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
-      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+      "dev": true
     },
     "node_modules/debug": {
       "version": "4.3.7",
@@ -6268,14 +6232,6 @@
         }
       }
     },
-    "node_modules/decode-uri-component": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
-      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
-      "engines": {
-        "node": ">=0.10"
-      }
-    },
     "node_modules/decompress-response": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -7757,14 +7713,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/filter-obj": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
-      "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/find-root": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -8470,11 +8418,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/intersection-observer": {
-      "version": "0.12.2",
-      "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz",
-      "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg=="
-    },
     "node_modules/is-arguments": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -9010,14 +8953,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/js-cookie": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
-      "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
-      "engines": {
-        "node": ">=14"
-      }
-    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -10874,23 +10809,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/query-string": {
-      "version": "6.14.1",
-      "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz",
-      "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
-      "dependencies": {
-        "decode-uri-component": "^0.2.0",
-        "filter-obj": "^1.1.0",
-        "split-on-first": "^1.0.0",
-        "strict-uri-encode": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/querystring-es3": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
@@ -11111,11 +11029,6 @@
         "react": ">=16.13.1"
       }
     },
-    "node_modules/react-fast-compare": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
-      "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
-    },
     "node_modules/react-is": {
       "version": "18.3.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -11355,11 +11268,6 @@
       "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
       "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
     },
-    "node_modules/resize-observer-polyfill": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
-      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
-    },
     "node_modules/resolve": {
       "version": "1.22.8",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -11586,17 +11494,6 @@
         "loose-envify": "^1.1.0"
       }
     },
-    "node_modules/screenfull": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz",
-      "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==",
-      "engines": {
-        "node": ">=0.10.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/secure-json-parse": {
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
@@ -11860,14 +11757,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/split-on-first": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
-      "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -11974,14 +11863,6 @@
       "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==",
       "dev": true
     },
-    "node_modules/strict-uri-encode": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
-      "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/string_decoder": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -12600,7 +12481,8 @@
     "node_modules/tslib": {
       "version": "2.8.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
-      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "dev": true
     },
     "node_modules/tty-browserify": {
       "version": "0.0.1",
diff --git a/package.json b/package.json
index dce84fd3c6c5fd2162baad4d9128367ec80f246d..b55e51837c1d9f3a7e745a01e095a500ed405909 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,6 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
-    "@ahooksjs/use-url-state": "^3.5.0",
     "@emotion/react": "^11.13.3",
     "@emotion/styled": "^11.13.0",
     "@ess-ics/ce-ui-common": "7.5.0",
diff --git a/src/components/IOC/IOCLiveStatus/IOCLiveStatus.jsx b/src/components/IOC/IOCLiveStatus/IOCLiveStatus.jsx
index 6bd7994eb79a521bbb5d6e5462d49d9ce7d30fec..b8a0c051c8ff69b9a613e58f9ab66bf457116fbc 100644
--- a/src/components/IOC/IOCLiveStatus/IOCLiveStatus.jsx
+++ b/src/components/IOC/IOCLiveStatus/IOCLiveStatus.jsx
@@ -1,4 +1,4 @@
-import { useCallback } from "react";
+import { useCallback, useState } from "react";
 import { Typography } from "@mui/material";
 import {
   SimpleAccordion,
@@ -10,18 +10,14 @@ import { LokiPanel } from "../../common/Loki/LokiPanel";
 import { RecordSearch } from "../../records/RecordSearch";
 import { GitRefLink } from "../../common/Git/GitRefLink";
 import AccessControl from "../../auth/AccessControl";
-import useUrlState from "@ahooksjs/use-url-state";
-import { serialize, deserialize } from "../../common/URLState/URLState";
+import { useSearchParams } from "react-router-dom";
 
 export function IOCLiveStatus({ ioc }) {
-  const [state, setState] = useUrlState(
-    {
-      procserv_log_open: "true",
-      records_open: "false",
-      log_stream_open: "false"
-    },
-    { navigateMode: "replace" }
-  );
+  const [searchParams] = useSearchParams();
+  const [accordionState, setAccordionState] = useState({
+    logStreamOpen: false,
+    recordsOpen: !!searchParams.get("query")
+  });
 
   const hostName = ioc.activeDeployment?.host?.hostName;
   const externalIdValid = ioc.activeDeployment?.host?.externalIdValid;
@@ -81,9 +77,9 @@ export function IOCLiveStatus({ ioc }) {
                 IOC log stream
               </Typography>
             }
-            expanded={deserialize(state.log_stream_open)}
+            expanded={accordionState.logStreamOpen}
             onChange={(_, expanded) =>
-              setState({ log_stream_open: serialize(expanded) })
+              setAccordionState({ ...accordionState, logStreamOpen: expanded })
             }
           >
             {hostName && (
@@ -103,9 +99,9 @@ export function IOCLiveStatus({ ioc }) {
               Records
             </Typography>
           }
-          expanded={deserialize(state.records_open)}
+          expanded={accordionState.recordsOpen}
           onChange={(_, expanded) =>
-            setState({ records_open: serialize(expanded) })
+            setAccordionState({ ...accordionState, recordsOpen: expanded })
           }
         >
           <RecordSearch
diff --git a/src/components/IOC/IOCTable/IOCTable.jsx b/src/components/IOC/IOCTable/IOCTable.jsx
index d2ab4837843a2d8458191bd1857dce24227ec062..5084d98b81da3c6a8fbb52bfbec92c9f2bcefa4f 100644
--- a/src/components/IOC/IOCTable/IOCTable.jsx
+++ b/src/components/IOC/IOCTable/IOCTable.jsx
@@ -135,7 +135,7 @@ function createTableRowForExploreIocs(ioc) {
 }
 
 export function IOCTable({
-  iocs = [],
+  iocs,
   rowType = "explore",
   loading,
   pagination,
diff --git a/src/components/common/URLState/URLState.js b/src/components/common/URLState/URLState.js
deleted file mode 100644
index e291eedac26f907d5605095075c195761311cc75..0000000000000000000000000000000000000000
--- a/src/components/common/URLState/URLState.js
+++ /dev/null
@@ -1,25 +0,0 @@
-export function serialize(value) {
-  if (typeof value === "boolean") {
-    return value ? "true" : "false";
-  }
-  if (typeof value === "number") {
-    return value.toString();
-  }
-  if (typeof value === "object") {
-    return JSON.stringify(value);
-  }
-  return value;
-}
-
-export function deserialize(value) {
-  if (value === "true") {
-    return true;
-  }
-  if (value === "false") {
-    return false;
-  }
-  if (Number(value).toString() === value && !isNaN(Number(value))) {
-    return Number(value);
-  }
-  return value;
-}
diff --git a/src/components/common/URLState/index.js b/src/components/common/URLState/index.js
deleted file mode 100644
index 778ede8aba4f359f6cdf41a85cff241958d0ff9d..0000000000000000000000000000000000000000
--- a/src/components/common/URLState/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { serialize, deserialize } from "./URLState";
-
-export { serialize, deserialize };
diff --git a/src/components/common/User/UserOperationList.jsx b/src/components/common/User/UserOperationList.jsx
index 52345558afae0eb9a9167cd1bb27e5d862839520..ad5eaf38c0f118dae97d93a305ae418fa4c16a16 100644
--- a/src/components/common/User/UserOperationList.jsx
+++ b/src/components/common/User/UserOperationList.jsx
@@ -4,6 +4,7 @@ import { initRequestParams } from "../Helper";
 import { JobTable } from "../../Job";
 import { apiContext } from "../../../api/DeployApi";
 import { useAPIMethod, usePagination, usePolling } from "@ess-ics/ce-ui-common";
+import { ROWS_PER_PAGE } from "../../../constants";
 
 export function UserOperationList({ userName }) {
   const client = useContext(apiContext);
@@ -19,10 +20,9 @@ export function UserOperationList({ userName }) {
     call: false
   });
 
-  const rowsPerPage = [20, 50];
   const { pagination, setPagination } = usePagination({
-    rowsPerPageOptions: rowsPerPage,
-    initLimit: rowsPerPage[0],
+    rowsPerPageOptions: ROWS_PER_PAGE,
+    initLimit: ROWS_PER_PAGE[0],
     initPage: 0
   });
 
diff --git a/src/components/records/RecordSearch.jsx b/src/components/records/RecordSearch.jsx
index ff07e9aad34178fc08e9f7ed0966107a8fe55e85..0a38e118115a410304aae2e2bb9b417082574f4a 100644
--- a/src/components/records/RecordSearch.jsx
+++ b/src/components/records/RecordSearch.jsx
@@ -1,11 +1,11 @@
-import { useEffect, useCallback, useState, useContext, useMemo } from "react";
+import { useEffect, useCallback, useState, useContext } from "react";
 import { initRequestParams } from "../common/Helper";
 import { RecordTable } from "./RecordTable";
-import useUrlState from "@ahooksjs/use-url-state";
-import { serialize, deserialize } from "../common/URLState/URLState";
+import { useSearchParams } from "react-router-dom";
 import { Grid, Tabs, Tab } from "@mui/material";
 import { useAPIMethod, usePagination, SearchBar } from "@ess-ics/ce-ui-common";
 import { apiContext } from "../../api/DeployApi";
+import { ROWS_PER_PAGE } from "../../constants";
 
 export function RecordSearch({ iocName, rowType }) {
   const client = useContext(apiContext);
@@ -21,37 +21,11 @@ export function RecordSearch({ iocName, rowType }) {
     call: false
   });
 
-  const [urlState, setUrlState] = useUrlState(
-    {
-      record_tab: "0",
-      rows: "20",
-      page: "0",
-      query: ""
-    },
-    { navigateMode: "replace" }
-  );
+  const [searchParams, setSearchParams] = useSearchParams({ query: "" });
   const [recordFilter, setRecordFilter] = useState();
+  const [tabIndex, setTabIndex] = useState(0);
 
-  const handleTabChange = useCallback(
-    (event, tab) => {
-      setUrlState((s) =>
-        serialize(s.record_tab) === serialize(tab)
-          ? { record_tab: serialize(tab) }
-          : { record_tab: serialize(tab), page: "0" }
-      );
-
-      changeTab(tab);
-    },
-    [setUrlState]
-  );
-
-  useEffect(() => {
-    if (urlState.record_tab) {
-      changeTab(deserialize(urlState.record_tab));
-    }
-  }, [urlState]);
-
-  const changeTab = (tab) => {
+  const handleTabChange = (tab) => {
     if (tab === 0) {
       setRecordFilter(null);
     } else if (tab === 1) {
@@ -59,28 +33,13 @@ export function RecordSearch({ iocName, rowType }) {
     } else if (tab === 2) {
       setRecordFilter("INACTIVE");
     }
+    setTabIndex(tab);
   };
 
-  const urlPagination = useMemo(() => {
-    return {
-      rows: deserialize(urlState.rows),
-      page: deserialize(urlState.page)
-    };
-  }, [urlState.rows, urlState.page]);
-
-  const setUrlPagination = useCallback(
-    ({ rows, page }) => {
-      setUrlState({ rows: serialize(rows), page: serialize(page) });
-    },
-    [setUrlState]
-  );
-
-  const rowsPerPage = [20, 50];
-
   const { pagination, setPagination, setTotalCount } = usePagination({
-    rowsPerPageOptions: rowsPerPage,
-    initLimit: urlPagination.rows ?? rowsPerPage[0],
-    initPage: urlPagination.page ?? 0
+    rowsPerPageOptions: ROWS_PER_PAGE,
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
 
   // update pagination whenever search result total pages change
@@ -88,36 +47,25 @@ export function RecordSearch({ iocName, rowType }) {
     setTotalCount(records?.totalCount ?? 0);
   }, [records?.totalCount, setTotalCount]);
 
-  // whenever url state changes, update pagination
-  useEffect(() => {
-    setPagination({ ...urlPagination });
-  }, [setPagination, urlPagination]);
-
-  // whenever table pagination internally changes (user clicks next page etc)
-  // update the row params
-  useEffect(() => {
-    setUrlPagination(pagination);
-  }, [pagination, setUrlPagination]);
-
   // Request new search results whenever search or pagination changes
   useEffect(() => {
     let requestParams = initRequestParams(pagination);
     requestParams.pv_status = recordFilter;
-    requestParams.text = deserialize(urlState.query);
+    requestParams.text = searchParams.get("query");
     requestParams.ioc_name = iocName;
     getRecords(requestParams);
 
     return () => {
       abort();
     };
-  }, [getRecords, recordFilter, urlState.query, pagination, abort, iocName]);
+  }, [getRecords, recordFilter, searchParams, pagination, abort, iocName]);
 
   // Callback for searchbar, called whenever user updates search
   const setSearch = useCallback(
     (query) => {
-      setUrlState({ query: serialize(query) });
+      setSearchParams({ query });
     },
-    [setUrlState]
+    [setSearchParams]
   );
 
   // Invoked by Table on change to pagination
@@ -134,8 +82,8 @@ export function RecordSearch({ iocName, rowType }) {
     >
       <Grid item>
         <Tabs
-          value={deserialize(urlState.record_tab)}
-          onChange={handleTabChange}
+          value={tabIndex}
+          onChange={(_, tab) => handleTabChange(tab)}
         >
           <Tab label="All" />
           <Tab label="Only active" />
@@ -145,7 +93,7 @@ export function RecordSearch({ iocName, rowType }) {
       <Grid item>
         <SearchBar
           search={setSearch}
-          query={deserialize(urlState.query)}
+          query={searchParams.get("query")}
           loading={loading || !dataReady}
         >
           <RecordTable
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8de6913d72fe11b847a3b8302e83aed3017b2a46
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1 @@
+export const ROWS_PER_PAGE = [20, 50];
diff --git a/src/views/IOC/IOCDetailsContainer.jsx b/src/views/IOC/IOCDetailsContainer.jsx
index f89a4ac4514b5e89e14a7f9d429380df205a5df2..b8e38f54673d1278b2aebf980e89419252fc6f76 100644
--- a/src/views/IOC/IOCDetailsContainer.jsx
+++ b/src/views/IOC/IOCDetailsContainer.jsx
@@ -3,13 +3,10 @@ import { IOCDetailsView } from "./IOCDetailsView";
 import { LinearProgress } from "@mui/material";
 import NotFoundView from "../../components/navigation/NotFoundView/NotFoundView";
 import { onFetchEntityError } from "../../components/common/Helper";
-import { userContext, useAPIMethod } from "@ess-ics/ce-ui-common";
-import useUrlState from "@ahooksjs/use-url-state";
+import { useAPIMethod } from "@ess-ics/ce-ui-common";
 import { apiContext } from "../../api/DeployApi";
 
 export function IOCDetailsContainer({ id }) {
-  const { user } = useContext(userContext);
-  const [urlState] = useUrlState();
   const [error, setError] = useState(null);
 
   const client = useContext(apiContext);
@@ -38,16 +35,6 @@ export function IOCDetailsContainer({ id }) {
     }
   }, [fetchError]);
 
-  useEffect(() => {
-    // user logs in => clear error message, and try to re-request userInfo
-    if (user) {
-      setError(null);
-    } else if (!user && urlState?.tab) {
-      // user is not logged in => show a message
-      setError({ message: "Unauthorized", status: "401" });
-    }
-  }, [urlState, user]);
-
   if (error) {
     return (
       <NotFoundView
diff --git a/src/views/IOC/IOCDetailsView.jsx b/src/views/IOC/IOCDetailsView.jsx
index 699b3268b85f71c5cf89f56560e698329946db18..8de62919c324f45c3506088cc63e570f720a77b9 100644
--- a/src/views/IOC/IOCDetailsView.jsx
+++ b/src/views/IOC/IOCDetailsView.jsx
@@ -17,12 +17,8 @@ import {
   usePagination,
   usePolling
 } from "@ess-ics/ce-ui-common";
-import useUrlState from "@ahooksjs/use-url-state";
-import {
-  serialize,
-  deserialize
-} from "../../components/common/URLState/URLState";
 import { apiContext } from "../../api/DeployApi";
+import { ROWS_PER_PAGE } from "../../constants";
 
 const IOC_POLL_INTERVAL = 10000;
 export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
@@ -33,15 +29,6 @@ export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
     }
   }, [ioc, setTitle]);
 
-  const [urlState, setUrlState] = useUrlState(
-    {
-      tab: "Status",
-      jobs_rows: "20",
-      jobs_page: "0"
-    },
-    { navigateMode: "replace" }
-  );
-
   const [buttonDisabled, setButtonDisabled] = useState(false);
   const navigate = useNavigate();
 
@@ -78,48 +65,19 @@ export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
     call: false
   });
 
-  const jobUrlPagination = useMemo(() => {
-    return {
-      rows: deserialize(urlState.jobs_rows),
-      page: deserialize(urlState.jobs_page)
-    };
-  }, [urlState.jobs_rows, urlState.jobs_page]);
-
-  const setJobUrlPagination = useCallback(
-    (params) => {
-      setUrlState({
-        jobs_rows: serialize(params.rows),
-        jobs_page: serialize(params.page)
-      });
-    },
-    [setUrlState]
-  );
-
-  const rowsPerPage = [20, 50];
-
   const { pagination: jobPagination, setPagination: setJobPagination } =
     usePagination({
-      rowsPerPageOptions: rowsPerPage,
-      initLimit: jobUrlPagination.rows ?? rowsPerPage[0],
-      initPage: jobUrlPagination.page ?? 0
+      rowsPerPageOptions: ROWS_PER_PAGE,
+      initLimit: ROWS_PER_PAGE[0],
+      initPage: 0
     });
+  const [tabIndex, setTabIndex] = useState(0);
 
   // update pagination whenever search result total pages change
   useEffect(() => {
     setJobPagination({ totalCount: jobs?.totalCount ?? 0 });
   }, [setJobPagination, jobs?.totalCount]);
 
-  // whenever url state changes, update pagination
-  useEffect(() => {
-    setJobPagination({ ...jobUrlPagination });
-  }, [setJobPagination, jobUrlPagination]);
-
-  // whenever table pagination internally changes (user clicks next page etc)
-  // update the row params
-  useEffect(() => {
-    setJobUrlPagination(jobPagination);
-  }, [jobPagination, setJobUrlPagination]);
-
   // Invoked by Table on change to pagination
   const onPage = useCallback(
     (params) => {
@@ -141,10 +99,6 @@ export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
     setButtonDisabled(Boolean(ioc?.operationInProgress));
   }, [ioc?.operationInProgress]);
 
-  const onTabChange = (index, label) => {
-    setUrlState({ tab: serialize(label) });
-  };
-
   const callGetJobs = useCallback(() => {
     let requestParams = initRequestParams(jobPagination);
     requestParams.ioc_id = ioc.id;
@@ -166,14 +120,6 @@ export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
     navigate(-1);
   };
 
-  const resetTab = useCallback(() => {
-    if (ioc?.activeDeployment) {
-      setUrlState({ tab: "Status" });
-    } else {
-      setUrlState({ tab: "Management" });
-    }
-  }, [ioc?.activeDeployment, setUrlState]);
-
   const setButtonDisabledAndUpdate = useCallback(
     (isDisabled) => {
       setButtonDisabled(isDisabled);
@@ -225,14 +171,13 @@ export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
         <IOCAdmin
           ioc={ioc}
           getIOC={getIOC}
-          resetTab={resetTab}
+          resetTab={() => setTabIndex(0)}
           buttonDisabled={buttonDisabled}
           setButtonDisabled={setButtonDisabled}
         />
       )
     });
   }
-  const initialIndex = tabs.map((it) => it.label).indexOf(urlState?.tab);
 
   return (
     <Grid
@@ -246,8 +191,8 @@ export function IOCDetailsView({ ioc, getIOC, abortGetIOC, loading }) {
       >
         <TabPanel
           tabs={tabs}
-          initialIndex={initialIndex}
-          onTabChange={onTabChange}
+          initialIndex={tabIndex}
+          onTabChange={(_, tab) => setTabIndex(tab)}
           TabsProps={{ centered: true, sx: { flex: 1 } }}
           renderTabs={(tabs) => (
             <Stack
diff --git a/src/views/IOC/IOCListView.tsx b/src/views/IOC/IOCListView.tsx
index 8c750824e54f823f39c902daf62c4414fc942869..3a5e700c398bd9c16e8b8dd72349cad41c333173 100644
--- a/src/views/IOC/IOCListView.tsx
+++ b/src/views/IOC/IOCListView.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback, useMemo } from "react";
+import { useState, useEffect, useCallback } from "react";
 import { useLazyListIocsQuery, ListIocsApiArg } from "../../store/deployApi";
 import {
   useGlobalAppBarContext,
@@ -10,46 +10,22 @@ import {
   applicationTitle,
   initRequestParams
 } from "../../components/common/Helper";
-import useUrlState from "@ahooksjs/use-url-state";
-import {
-  serialize,
-  deserialize
-} from "../../components/common/URLState/URLState";
+import { useSearchParams } from "react-router-dom";
 import { GlobalAppBarContext, OnPageParams } from "../../types/common";
 import { ApiAlertError } from "../../components/common/Alerts/ApiAlertError";
 import { Container, Grid, Tabs, Tab } from "@mui/material";
 import IOCTable from "../../components/IOC/IOCTable";
-
-const ROWS_PER_PAGE = [20, 50];
+import { ROWS_PER_PAGE } from "../../constants";
 
 export const IOCListView = () => {
   const [deploymentStatus, setDeploymentStatus] =
     useState<ListIocsApiArg["deploymentStatus"]>("ALL");
+  const [tabIndex, setTabIndex] = useState(0);
   const [listIocs, { data: iocs, isFetching, error }] = useLazyListIocsQuery();
-  const [urlState, setUrlState] = useUrlState(
-    {
-      tab: "0",
-      rows: "20",
-      page: "0",
-      query: ""
-    },
-    { navigateMode: "replace" }
-  );
+  const [searchParams, setSearchParams] = useSearchParams({ query: "" });
   const { setTitle }: GlobalAppBarContext = useGlobalAppBarContext();
 
-  const handleTabChange = useCallback(
-    (tab: number) => {
-      setUrlState((s) =>
-        serialize(s.tab) === serialize(tab)
-          ? { tab: serialize(tab) }
-          : { tab: serialize(tab), page: "0" }
-      );
-      changeTab(tab);
-    },
-    [setUrlState]
-  );
-
-  const changeTab = (tab: number) => {
+  const handleTabChange = (tab: number) => {
     if (tab === 0) {
       setDeploymentStatus("ALL");
     } else if (tab === 1) {
@@ -57,29 +33,13 @@ export const IOCListView = () => {
     } else if (tab === 2) {
       setDeploymentStatus("NOT_DEPLOYED");
     }
+    setTabIndex(tab);
   };
 
-  const setUrlPagination = useCallback(
-    ({ rows, page }: OnPageParams) => {
-      setUrlState({
-        rows: serialize(rows),
-        page: serialize(page)
-      });
-    },
-    [setUrlState]
-  );
-
-  const urlPagination = useMemo(() => {
-    return {
-      rows: deserialize(urlState.rows),
-      page: deserialize(urlState.page)
-    };
-  }, [urlState.rows, urlState.page]);
-
   const { pagination, setPagination } = usePagination({
     rowsPerPageOptions: ROWS_PER_PAGE,
-    initLimit: urlPagination.rows ?? ROWS_PER_PAGE[0],
-    initPage: urlPagination.page ?? 0
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
 
   // Invoked by Table on change to pagination
@@ -90,44 +50,27 @@ export const IOCListView = () => {
   // Callback for searchbar, called whenever user updates search
   const setSearch = useCallback(
     (query: string) => {
-      setUrlState({ query: serialize(query) });
+      setSearchParams({ query });
     },
-    [setUrlState]
+    [setSearchParams]
   );
 
   useEffect(() => setTitle(applicationTitle("IOCs")), [setTitle]);
 
-  useEffect(() => {
-    if (urlState.tab) {
-      changeTab(deserialize(urlState.tab));
-    }
-  }, [urlState.tab]);
-
   // update pagination whenever search result total pages change
   useEffect(() => {
     setPagination({ totalCount: iocs?.totalCount ?? 0 });
   }, [setPagination, iocs?.totalCount]);
 
-  // whenever url state changes, update pagination
-  useEffect(() => {
-    setPagination({ ...urlPagination });
-  }, [setPagination, urlPagination]);
-
-  // whenever table pagination internally changes (user clicks next page etc)
-  // update the row params
-  useEffect(() => {
-    setUrlPagination(pagination);
-  }, [pagination, setUrlPagination]);
-
   useEffect(() => {
     const requestParams = Object.assign(
       {},
-      initRequestParams(pagination, deserialize(urlState.query)),
+      initRequestParams(pagination, searchParams.get("query")),
       { deploymentStatus: deploymentStatus }
     );
 
     listIocs(requestParams);
-  }, [listIocs, urlPagination, deploymentStatus, urlState.query, pagination]);
+  }, [listIocs, deploymentStatus, searchParams, pagination]);
 
   return (
     <RootPaper>
@@ -145,7 +88,7 @@ export const IOCListView = () => {
         >
           <Grid item>
             <Tabs
-              value={deserialize(urlState.tab)}
+              value={tabIndex}
               onChange={(_, tab) => handleTabChange(tab)}
             >
               <Tab label="All" />
@@ -161,14 +104,14 @@ export const IOCListView = () => {
         >
           <SearchBar
             search={setSearch}
-            query={deserialize(urlState.query)}
+            query={searchParams.get("query")}
             loading={isFetching || !iocs}
           >
             {error ? (
               <ApiAlertError error={error} />
             ) : (
               <IOCTable
-                iocs={iocs?.iocList}
+                iocs={iocs?.iocList || []}
                 loading={isFetching || !iocs}
                 rowType="explore"
                 pagination={pagination}
diff --git a/src/views/host/HostListView.jsx b/src/views/host/HostListView.jsx
index e2beda242b48fd1ce02f2b48574faf0dba06ddf6..1ec9c3a3d8c80afb20c9dca12851b242b5bb7a3f 100644
--- a/src/views/host/HostListView.jsx
+++ b/src/views/host/HostListView.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback, useContext, useMemo } from "react";
+import { useState, useEffect, useCallback, useContext } from "react";
 import { Container, Grid, Tabs, Tab } from "@mui/material";
 import { HostTable } from "../../components/host/HostTable";
 import {
@@ -12,12 +12,9 @@ import {
   applicationTitle,
   initRequestParams
 } from "../../components/common/Helper";
-import useUrlState from "@ahooksjs/use-url-state";
-import {
-  serialize,
-  deserialize
-} from "../../components/common/URLState/URLState";
+import { useSearchParams } from "react-router-dom";
 import { apiContext } from "../../api/DeployApi";
+import { ROWS_PER_PAGE } from "../../constants";
 
 export function HostListView() {
   const { setTitle } = useContext(GlobalAppBarContext);
@@ -35,49 +32,11 @@ export function HostListView() {
     call: false
   });
 
-  const [urlState, setUrlState] = useUrlState(
-    {
-      tab: "0",
-      rows: "20",
-      page: "0",
-      query: ""
-    },
-    { navigateMode: "replace" }
-  );
+  const [searchParams, setSearchParams] = useSearchParams({ query: "" });
+  const [tabIndex, setTabIndex] = useState(0);
   const [hostFilter, setHostFilter] = useState("ALL");
 
-  const urlPagination = useMemo(
-    () => ({
-      rows: deserialize(urlState.rows),
-      page: deserialize(urlState.page)
-    }),
-    [urlState.rows, urlState.page]
-  );
-
-  const setUrlPagination = useCallback(
-    ({ rows, page }) => {
-      setUrlState({
-        rows: serialize(rows),
-        page: serialize(page)
-      });
-    },
-    [setUrlState]
-  );
-
-  const handleTabChange = useCallback(
-    (event, tab) => {
-      setUrlState((s) =>
-        serialize(s.tab) === serialize(tab)
-          ? { tab: serialize(tab) }
-          : { tab: serialize(tab), page: "0" }
-      );
-
-      changeTab(tab);
-    },
-    [setUrlState]
-  );
-
-  const changeTab = (tab) => {
+  const handleTabChange = (tab) => {
     if (tab === 0) {
       setHostFilter("ALL");
     } else if (tab === 1) {
@@ -85,20 +44,13 @@ export function HostListView() {
     } else if (tab === 2) {
       setHostFilter("NO_IOCS");
     }
+    setTabIndex(tab);
   };
 
-  useEffect(() => {
-    if (urlState.tab) {
-      changeTab(deserialize(urlState.tab));
-    }
-  }, [urlState]);
-
-  const rowsPerPage = [20, 50];
-
   const { pagination, setPagination } = usePagination({
-    rowsPerPageOptions: rowsPerPage,
-    initLimit: urlPagination.rows ?? rowsPerPage[0],
-    initPage: urlPagination.page ?? 0
+    rowsPerPageOptions: ROWS_PER_PAGE,
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
 
   // update pagination whenever search result total pages change
@@ -106,35 +58,24 @@ export function HostListView() {
     setPagination({ totalCount: hosts?.totalCount ?? 0 });
   }, [setPagination, hosts?.totalCount]);
 
-  // whenever url state changes, update pagination
-  useEffect(() => {
-    setPagination({ ...urlPagination });
-  }, [setPagination, urlPagination]);
-
-  // whenever table pagination internally changes (user clicks next page etc)
-  // update the row params
-  useEffect(() => {
-    setUrlPagination(pagination);
-  }, [pagination, setUrlPagination]);
-
   // Request new search results whenever search or pagination changes
   useEffect(() => {
     let requestParams = initRequestParams(pagination);
     requestParams.filter = hostFilter;
-    requestParams.text = deserialize(urlState.query);
+    requestParams.text = searchParams.get("query");
     getHosts(requestParams);
 
     return () => {
       abort();
     };
-  }, [getHosts, hostFilter, urlState.query, pagination, abort]);
+  }, [getHosts, hostFilter, searchParams, pagination, abort]);
 
   // Callback for searchbar, called whenever user updates search
   const setSearch = useCallback(
     (query) => {
-      setUrlState({ query: serialize(query) });
+      setSearchParams({ query });
     },
-    [setUrlState]
+    [setSearchParams]
   );
 
   // Invoked by Table on change to pagination
@@ -146,7 +87,7 @@ export function HostListView() {
   const content = (
     <SearchBar
       search={setSearch}
-      query={deserialize(urlState.query)}
+      query={searchParams.get("query")}
       loading={loading}
     >
       <HostTable
@@ -174,8 +115,8 @@ export function HostListView() {
         >
           <Grid item>
             <Tabs
-              value={deserialize(urlState.tab)}
-              onChange={handleTabChange}
+              value={tabIndex}
+              onChange={(_, tab) => handleTabChange(tab)}
             >
               <Tab label="All" />
               <Tab label="Only hosts with IOCs" />
diff --git a/src/views/host/details/HostDetailsView.jsx b/src/views/host/details/HostDetailsView.jsx
index 40f707ca2b89d4ad56062dcbaf5b2dfd31fe8e4f..e10f69904f2f56544078077f7262aa8c18c5d1e9 100644
--- a/src/views/host/details/HostDetailsView.jsx
+++ b/src/views/host/details/HostDetailsView.jsx
@@ -1,5 +1,4 @@
-import { useEffect, useContext } from "react";
-import useUrlState from "@ahooksjs/use-url-state";
+import { useEffect, useContext, useState } from "react";
 import { Box, IconButton, Typography, Stack } from "@mui/material";
 import ArrowBackIcon from "@mui/icons-material/ArrowBack";
 import {
@@ -13,10 +12,6 @@ import { LokiPanel } from "../../../components/common/Loki/LokiPanel";
 import { useNavigate } from "react-router-dom";
 import { applicationTitle } from "../../../components/common/Helper";
 import AccessControl from "../../../components/auth/AccessControl";
-import {
-  serialize,
-  deserialize
-} from "../../../components/common/URLState/URLState";
 import { HostDetailsTable } from "./HostDetailsTable";
 import { HostStatus } from "../../../components/host/HostStatus";
 import { HostJobsSection } from "./HostJobsSection";
@@ -25,6 +20,10 @@ import env from "../../../config/env";
 
 export function HostDetailsView({ hostId, host, alert }) {
   const { setTitle } = useContext(GlobalAppBarContext);
+  const [accordionState, setAccordionState] = useState({
+    detailsOpen: false,
+    logStreamOpen: false
+  });
 
   useEffect(() => {
     if (host && host.name) {
@@ -32,18 +31,6 @@ export function HostDetailsView({ hostId, host, alert }) {
     }
   }, [host, setTitle]);
 
-  const [urlState, setUrlState] = useUrlState(
-    {
-      iocs_rows: 20,
-      iocs_page: 0,
-      details_open: false,
-      log_stream_open: false,
-      job_log_open: false,
-      job_log_rows: 20,
-      job_log_page: 0
-    },
-    { navigateMode: "replace" }
-  );
   const navigate = useNavigate();
 
   return (
@@ -95,20 +82,9 @@ export function HostDetailsView({ hostId, host, alert }) {
             </Box>
           </Stack>
           <Stack gap={2}>
-            <HostIocSection
-              hostId={hostId}
-              rows={deserialize(urlState.iocs_rows)}
-              page={deserialize(urlState.iocs_page)}
-              onUrlStateChange={(params) => setUrlState(params)}
-            />
+            <HostIocSection hostId={hostId} />
           </Stack>
-          <HostJobsSection
-            hostId={hostId}
-            rows={deserialize(urlState.job_log_rows)}
-            page={deserialize(urlState.job_log_page)}
-            expanded={deserialize(urlState.job_log_open)}
-            onUrlStateChange={(params) => setUrlState(params)}
-          />
+          <HostJobsSection hostId={hostId} />
 
           <AccessControl
             allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
@@ -123,9 +99,9 @@ export function HostDetailsView({ hostId, host, alert }) {
                   Host details
                 </Typography>
               }
-              expanded={deserialize(urlState.details_open)}
+              expanded={accordionState.detailsOpen}
               onChange={(_, expanded) =>
-                setUrlState({ details_open: serialize(expanded) })
+                setAccordionState({ detailsOpen: expanded })
               }
             >
               <HostDetailsTable host={host} />
@@ -140,9 +116,9 @@ export function HostDetailsView({ hostId, host, alert }) {
                   Host log stream
                 </Typography>
               }
-              expanded={deserialize(urlState.log_stream_open)}
+              expanded={accordionState.logStreamOpen}
               onChange={(_, expanded) =>
-                setUrlState({ log_stream_open: serialize(expanded) })
+                setAccordionState({ logStreamOpen: expanded })
               }
             >
               <LokiPanel
diff --git a/src/views/host/details/HostIocSection.jsx b/src/views/host/details/HostIocSection.jsx
index fa81ed1dec3f1b01a3d2e3d28bc6956e1c5da311..c3ed876004ff863608d57e8a885b083360d4c672 100644
--- a/src/views/host/details/HostIocSection.jsx
+++ b/src/views/host/details/HostIocSection.jsx
@@ -1,24 +1,22 @@
 import { useEffect, useContext, useCallback } from "react";
 import IOCTable from "../../../components/IOC/IOCTable";
-import { string, number, func } from "prop-types";
+import { string } from "prop-types";
 import { apiContext } from "../../../api/DeployApi";
 import { Typography } from "@mui/material";
 import { useAPIMethod, usePagination } from "@ess-ics/ce-ui-common";
 import { initRequestParams } from "../../../components/common/Helper";
-import { serialize } from "../../../components/common/URLState/URLState";
+import { ROWS_PER_PAGE } from "../../../constants";
 
 const propTypes = {
-  hostId: string,
-  row: number,
-  page: number,
-  onUrlStateChange: func
+  hostId: string
 };
 
-export const HostIocSection = ({ hostId, rows, page, onUrlStateChange }) => {
+export const HostIocSection = ({ hostId }) => {
   const client = useContext(apiContext);
+
   const { pagination, setPagination, setTotalCount } = usePagination({
-    initLimit: rows,
-    initPage: page
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
 
   const {
@@ -36,19 +34,15 @@ export const HostIocSection = ({ hostId, rows, page, onUrlStateChange }) => {
     (params) => {
       setPagination(params);
       abortGetIocs();
-      onUrlStateChange({
-        iocs_rows: serialize(params.limit),
-        iocs_page: serialize(params.page)
-      });
     },
-    [setPagination, abortGetIocs, onUrlStateChange]
+    [setPagination, abortGetIocs]
   );
 
   const getIocs = useCallback(() => {
-    let requestParams = initRequestParams({ page, rows }, null);
+    let requestParams = initRequestParams(pagination, null);
     requestParams.host_id = hostId;
     callgetIocs(requestParams);
-  }, [callgetIocs, hostId, page, rows]);
+  }, [callgetIocs, hostId, pagination]);
 
   // update pagination whenever search result total pages change
   useEffect(() => {
@@ -61,7 +55,7 @@ export const HostIocSection = ({ hostId, rows, page, onUrlStateChange }) => {
     return () => {
       abortGetIocs();
     };
-  }, [rows, page, abortGetIocs, getIocs]);
+  }, [abortGetIocs, getIocs]);
 
   return (
     <>
diff --git a/src/views/host/details/HostJobsSection.jsx b/src/views/host/details/HostJobsSection.jsx
index 15edecf3819dc7a068ce2fd9f8696d10ac3be795..8430a3eac11dc210b1dddf20b86eb29bdcd561dd 100644
--- a/src/views/host/details/HostJobsSection.jsx
+++ b/src/views/host/details/HostJobsSection.jsx
@@ -1,6 +1,5 @@
-import { useContext, useEffect, useMemo, useCallback } from "react";
-import { string, number, boolean, func } from "prop-types";
-import { serialize } from "../../../components/common/URLState/URLState";
+import { useContext, useEffect, useMemo, useCallback, useState } from "react";
+import { string } from "prop-types";
 import { getErrorMessage } from "../../../components/common/Helper";
 import { apiContext } from "../../../api/DeployApi";
 import {
@@ -10,31 +9,21 @@ import {
 } from "@ess-ics/ce-ui-common";
 import { JobTable } from "../../../components/Job";
 import { Alert, Typography } from "@mui/material";
+import { ROWS_PER_PAGE } from "../../../constants";
 
 const propTypes = {
-  hostId: string.isRequired,
-  rows: number,
-  page: number,
-  expanded: boolean,
-  onUrlStateChange: func
+  hostId: string.isRequired
 };
 
-const rowsPerPage = [20, 50];
-
-export const HostJobsSection = ({
-  hostId,
-  rows,
-  page,
-  expanded,
-  onUrlStateChange
-}) => {
+export const HostJobsSection = ({ hostId }) => {
   const client = useContext(apiContext);
 
   const { pagination, setPagination } = usePagination({
-    rowsPerPageOptions: rowsPerPage,
-    initLimit: rows,
-    initPage: page
+    rowsPerPageOptions: ROWS_PER_PAGE,
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
+  const [expanded, setExpanded] = useState(false);
 
   const {
     value: hostLog,
@@ -56,12 +45,8 @@ export const HostJobsSection = ({
     (params) => {
       setPagination(params);
       abortGetHostLog();
-      onUrlStateChange({
-        job_log_rows: serialize(params.limit),
-        job_log_page: serialize(params.page)
-      });
     },
-    [setPagination, abortGetHostLog, onUrlStateChange]
+    [setPagination, abortGetHostLog]
   );
 
   useEffect(() => {
@@ -82,6 +67,7 @@ export const HostJobsSection = ({
 
   return (
     <SimpleAccordion
+      expanded={expanded}
       summary={
         <Typography
           variant="h3"
@@ -90,9 +76,7 @@ export const HostJobsSection = ({
           Job log
         </Typography>
       }
-      onChange={(_, isExpanded) =>
-        onUrlStateChange({ job_log_open: serialize(isExpanded) })
-      }
+      onChange={(_, expanded) => setExpanded(expanded)}
     >
       {error ? <Alert severity="error">{getErrorMessage(error)}</Alert> : null}
       {hostLog ? (
diff --git a/src/views/jobs/JobListView.jsx b/src/views/jobs/JobListView.jsx
index 9449ba16236be7433047ed0e8f03391e4461c5f0..3f5e5fef22ceaf6bd8f7a72f1722eef4a955b7f0 100644
--- a/src/views/jobs/JobListView.jsx
+++ b/src/views/jobs/JobListView.jsx
@@ -1,4 +1,4 @@
-import { useContext, useCallback, useMemo, useEffect } from "react";
+import { useContext, useCallback, useEffect } from "react";
 import { Box } from "@mui/material";
 import {
   RootPaper,
@@ -7,13 +7,9 @@ import {
   usePolling
 } from "@ess-ics/ce-ui-common";
 import { initRequestParams } from "../../components/common/Helper";
-import useUrlState from "@ahooksjs/use-url-state";
-import {
-  serialize,
-  deserialize
-} from "../../components/common/URLState/URLState";
 import { JobTable } from "../../components/Job/JobTable";
 import { apiContext } from "../../api/DeployApi";
+import { ROWS_PER_PAGE } from "../../constants";
 
 export function JobListView() {
   const client = useContext(apiContext);
@@ -29,38 +25,10 @@ export function JobListView() {
     call: false
   });
 
-  const [urlState, setUrlState] = useUrlState(
-    {
-      rows: "20",
-      page: "0",
-      query: ""
-    },
-    { navigateMode: "replace" }
-  );
-
-  const urlPagination = useMemo(() => {
-    return {
-      rows: deserialize(urlState.rows),
-      page: deserialize(urlState.page)
-    };
-  }, [urlState.rows, urlState.page]);
-
-  const setUrlPagination = useCallback(
-    ({ rows, page }) => {
-      setUrlState({
-        rows: serialize(rows),
-        page: serialize(page)
-      });
-    },
-    [setUrlState]
-  );
-
-  const rowsPerPage = [20, 50];
-
   const { pagination, setPagination } = usePagination({
-    rowsPerPageOptions: rowsPerPage,
-    initLimit: urlPagination.rows ?? rowsPerPage[0],
-    initPage: urlPagination.page ?? 0
+    rowsPerPageOptions: ROWS_PER_PAGE,
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
 
   // update pagination whenever search result total pages change
@@ -68,25 +36,10 @@ export function JobListView() {
     setPagination({ totalCount: jobs?.totalCount ?? 0 });
   }, [setPagination, jobs?.totalCount]);
 
-  // whenever url state changes, update pagination
-  useEffect(() => {
-    setPagination({ ...urlPagination });
-  }, [setPagination, urlPagination]);
-
-  // whenever table pagination internally changes (user clicks next page etc)
-  // update the row params
-  useEffect(() => {
-    setUrlPagination(pagination);
-  }, [pagination, setUrlPagination]);
-
   const callGetOperations = useCallback(() => {
-    let requestParams = initRequestParams(
-      urlPagination,
-      deserialize(urlState.query)
-    );
-
+    let requestParams = initRequestParams(pagination);
     getJobs(requestParams);
-  }, [getJobs, urlPagination, urlState.query]);
+  }, [getJobs, pagination]);
 
   // Request new search results whenever search or pagination changes
   useEffect(() => {
@@ -95,7 +48,7 @@ export function JobListView() {
     return () => {
       abort();
     };
-  }, [callGetOperations, abort]);
+  }, [abort, callGetOperations, pagination]);
 
   usePolling(callGetOperations, loading, 30000, abort, false);
 
diff --git a/src/views/records/RecordListView.jsx b/src/views/records/RecordListView.jsx
index b4c6bc2dccdffa572313fae15c6972152edb6fb6..05ed3a623fd66a95d66a8adca0600e8ee1435cf1 100644
--- a/src/views/records/RecordListView.jsx
+++ b/src/views/records/RecordListView.jsx
@@ -1,4 +1,4 @@
-import { useState, useMemo, useCallback, useContext, useEffect } from "react";
+import { useState, useCallback, useContext, useEffect } from "react";
 import { Container, Grid, Tabs, Tab } from "@mui/material";
 import {
   GlobalAppBarContext,
@@ -11,13 +11,10 @@ import {
   applicationTitle,
   initRequestParams
 } from "../../components/common/Helper";
-import useUrlState from "@ahooksjs/use-url-state";
-import {
-  serialize,
-  deserialize
-} from "../../components/common/URLState/URLState";
+import { useSearchParams } from "react-router-dom";
 import { RecordTable } from "../../components/records/RecordTable";
 import { apiContext } from "../../api/DeployApi";
+import { ROWS_PER_PAGE } from "../../constants";
 
 export function RecordListView() {
   const { setTitle } = useContext(GlobalAppBarContext);
@@ -36,32 +33,12 @@ export function RecordListView() {
     call: false
   });
 
-  const [urlState, setUrlState] = useUrlState(
-    {
-      tab: "0",
-      rows: "20",
-      page: "0",
-      query: ""
-    },
-    { navigateMode: "replace" }
-  );
+  const [searchParams, setSearchParams] = useSearchParams({ query: "" });
+  const [tabIndex, setTabIndex] = useState(0);
   const [recordFilter, setRecordFilter] = useState(null);
   // used to request record list again when tab is switched, but request it only once! (totalRecord is a random number that is generated by ChannelFinder)
 
-  const handleTabChange = useCallback(
-    (event, tab) => {
-      setUrlState((s) =>
-        serialize(s.tab) === serialize(tab)
-          ? { tab: serialize(tab) }
-          : { tab: serialize(tab), page: "0" }
-      );
-
-      changeTab(tab);
-    },
-    [setUrlState]
-  );
-
-  const changeTab = (tab) => {
+  const handleTabChange = (tab) => {
     if (tab === 0) {
       setRecordFilter(null);
     } else if (tab === 1) {
@@ -69,37 +46,13 @@ export function RecordListView() {
     } else if (tab === 2) {
       setRecordFilter("INACTIVE");
     }
+    setTabIndex(tab);
   };
 
-  useEffect(() => {
-    if (urlState.tab) {
-      changeTab(deserialize(urlState.tab));
-    }
-  }, [urlState]);
-
-  const urlPagination = useMemo(() => {
-    return {
-      rows: deserialize(urlState.rows),
-      page: deserialize(urlState.page)
-    };
-  }, [urlState.rows, urlState.page]);
-
-  const setUrlPagination = useCallback(
-    ({ rows, page }) => {
-      setUrlState({
-        rows: serialize(rows),
-        page: serialize(page)
-      });
-    },
-    [setUrlState]
-  );
-
-  const rowsPerPage = [20, 50];
-
   const { pagination, setPagination, setTotalCount } = usePagination({
-    rowsPerPageOptions: rowsPerPage,
-    initLimit: urlPagination.rows ?? rowsPerPage[0],
-    initPage: urlPagination.page ?? 0
+    rowsPerPageOptions: ROWS_PER_PAGE,
+    initLimit: ROWS_PER_PAGE[0],
+    initPage: 0
   });
 
   // update pagination whenever search result total pages change
@@ -107,35 +60,24 @@ export function RecordListView() {
     setTotalCount(records?.totalCount ?? 0);
   }, [records?.totalCount, setTotalCount]);
 
-  // whenever url state changes, update pagination
-  useEffect(() => {
-    setPagination({ ...urlPagination });
-  }, [setPagination, urlPagination]);
-
-  // whenever table pagination internally changes (user clicks next page etc)
-  // update the row params
-  useEffect(() => {
-    setUrlPagination(pagination);
-  }, [pagination, setUrlPagination]);
-
   // Request new search results whenever search or pagination changes
   useEffect(() => {
     let requestParams = initRequestParams(pagination);
     requestParams.pv_status = recordFilter;
-    requestParams.text = deserialize(urlState.query);
+    requestParams.text = searchParams.get("query");
     getRecords(requestParams);
 
     return () => {
       abort();
     };
-  }, [getRecords, recordFilter, urlState.query, pagination, abort]);
+  }, [getRecords, recordFilter, pagination, abort, searchParams]);
 
   // Callback for searchbar, called whenever user updates search
   const setSearch = useCallback(
     (query) => {
-      setUrlState({ query: serialize(query) });
+      setSearchParams({ query });
     },
-    [setUrlState]
+    [setSearchParams]
   );
 
   // Invoked by Table on change to pagination
@@ -147,7 +89,7 @@ export function RecordListView() {
   let content = (
     <SearchBar
       search={setSearch}
-      query={deserialize(urlState.query)}
+      query={searchParams.get("query")}
       loading={loading || !dataReady}
     >
       <RecordTable
@@ -175,8 +117,8 @@ export function RecordListView() {
         >
           <Grid item>
             <Tabs
-              value={deserialize(urlState.tab)}
-              onChange={handleTabChange}
+              value={tabIndex}
+              onChange={(_, tab) => handleTabChange(tab)}
             >
               <Tab label="All" />
               <Tab label="Only active" />