diff --git a/src/components/IOC/IOCManage/IOCManage.js b/src/components/IOC/IOCManage/IOCManage.js
index 5001a0e26123888fc075015efcf90261258307b4..e2f767cff5624f0154e9e58a2253c495112cc27c 100644
--- a/src/components/IOC/IOCManage/IOCManage.js
+++ b/src/components/IOC/IOCManage/IOCManage.js
@@ -28,8 +28,8 @@ export function IOCManage({
   operationsLoading,
   getOperations,
   setButtonDisabled,
-  jobLazyParams,
-  setJobLazyParams
+  pagination,
+  onPage
 }) {
   const { user } = useContext(userContext);
   const [state, setState] = useUrlState(
@@ -44,8 +44,6 @@ export function IOCManage({
   const [deploymentJob, getDeploymentJobById] = useJobById();
   const [operationList, setOperationList] = useState(operations.operationsList);
 
-  const rowsPerPage = [5, 20];
-
   useEffect(() => {
     if (ioc.activeDeployment?.awxJobId) {
       getDeploymentJobById(ioc.activeDeployment?.awxJobId);
@@ -114,7 +112,7 @@ export function IOCManage({
               getOperations,
               buttonDisabled,
               setButtonDisabled,
-              jobLazyParams
+              jobLazyParams: pagination
             }}
           />
         ) : (
@@ -126,7 +124,7 @@ export function IOCManage({
     },
     [
       buttonDisabled,
-      jobLazyParams,
+      pagination,
       currentCommand,
       deploymentJob,
       getOperations,
@@ -225,12 +223,9 @@ export function IOCManage({
             <JobAsyncList
               jobs={operationList}
               setJobs={setOperationList}
+              pagination={pagination}
+              onPage={onPage}
               loading={operationsLoading}
-              totalCount={operations.totalCount}
-              lazyParams={jobLazyParams}
-              setLazyParams={setJobLazyParams}
-              rowsPerPage={rowsPerPage}
-              deploymentId={ioc?.activeDeployment?.id}
             />
           </SimpleAccordion>
           <SimpleModal
diff --git a/src/components/Job/JobAsyncList.js b/src/components/Job/JobAsyncList.js
index ea48e862223140adc47718e22a5ef2eab8d213b6..33a4743ba7840e43260b845ba22d04111537067a 100644
--- a/src/components/Job/JobAsyncList.js
+++ b/src/components/Job/JobAsyncList.js
@@ -6,13 +6,9 @@ export function JobAsyncList({
   jobs,
   setJobs,
   rowType,
-  totalCount,
-  lazyParams,
-  setLazyParams,
-  rowsPerPage,
-  loading,
-  paginator,
-  deploymentId
+  pagination,
+  onPage,
+  loading
 }) {
   const jobDetailFetchers = jobs?.map((job, index) => (
     <JobDetailFetcher
@@ -27,12 +23,9 @@ export function JobAsyncList({
       <JobTable
         jobs={jobs}
         rowType={rowType}
+        pagination={pagination}
+        onPage={onPage}
         loading={loading}
-        totalCount={totalCount}
-        lazyParams={lazyParams}
-        setLazyParams={setLazyParams}
-        rowsPerPage={rowsPerPage}
-        deploymentId={deploymentId}
       />
       {jobDetailFetchers}
     </>
diff --git a/src/components/Job/JobTable.js b/src/components/Job/JobTable.js
index 02dddc2c3576a0651df6a5220b86dac8cc02bb0e..70193c8001109e224a7c6a669ceeaa96248bfa5e 100644
--- a/src/components/Job/JobTable.js
+++ b/src/components/Job/JobTable.js
@@ -1,5 +1,5 @@
-import React, { useCallback } from "react";
-import { CustomTable } from "../common/table/CustomTable";
+import React from "react";
+import { Table } from "@ess-ics/ce-ui-common";
 import { Grid, Tooltip } from "@mui/material";
 import { noWrapText } from "../common/Helper";
 import { useRedirect } from "../../hooks/Redirect";
@@ -7,16 +7,23 @@ import { JobStatusIcon, JobTypeIcon } from "./JobIcons";
 import { formatDate } from "../common/Helper";
 
 const jobLogColumns = [
-  { id: "type", label: "Type", width: "8ch", textAlign: "center" },
-  { id: "ioc", label: "IOC name", width: "15ch", sortable: false },
-  { id: "version", label: "Revision", width: "10ch", sortable: false },
-  { id: "host", label: "Host", width: "15ch", sortable: false },
-  { id: "user", label: "User", width: "10ch", sortable: false },
   {
-    id: "start",
-    label: "Time",
+    field: "type",
+    headerName: "Type",
+    flex: 0,
+    headerAlign: "center",
+    align: "center"
+  },
+  { field: "ioc", headerName: "IOC name", width: "15ch", sortable: false },
+  { field: "version", headerName: "Revision", width: "10ch", sortable: false },
+  { field: "host", headerName: "Host", width: "15ch", sortable: false },
+  { field: "user", headerName: "User", width: "10ch", sortable: false },
+  {
+    field: "start",
+    headerName: "Time",
     width: "15ch",
     sortable: false,
+    headerAlign: "right",
     textAlign: "right"
   }
 ];
@@ -33,7 +40,7 @@ function createGitVersionField(version, shortVersion) {
   );
 }
 
-function createTableRow(job, deploymentId, colorStyle) {
+function createTableRow(job, colorStyle) {
   return {
     id: job.id,
     type: (
@@ -72,19 +79,16 @@ function createTableRow(job, deploymentId, colorStyle) {
   };
 }
 
-function createTableRowJobLog(job, deploymentId) {
-  return createTableRow(job, deploymentId, "black");
+function createTableRowJobLog(job) {
+  return createTableRow(job, "black");
 }
 
 export function JobTable({
   jobs,
   rowType = "jobLog",
-  totalCount,
-  lazyParams,
-  setLazyParams,
-  rowsPerPage,
-  loading,
-  deploymentId
+  pagination,
+  onPage,
+  loading
 }) {
   const tableTypeSpecifics = {
     jobLog: [jobLogColumns, createTableRowJobLog]
@@ -93,29 +97,22 @@ export function JobTable({
   const redirect = useRedirect();
   const [columns, createRow] = tableTypeSpecifics[rowType];
 
-  const itemLink = useCallback((item) => {
-    if (["DEPLOY", "UNDEPLOY", "START", "STOP"].includes(item.jobType)) {
-      return `/jobs/${item.id}`;
+  const onRowClick = (params) => {
+    const job = params.row;
+    if (["DEPLOY", "UNDEPLOY", "START", "STOP"].includes(job?.jobType)) {
+      redirect(`/jobs/${job?.id}`);
     }
-  }, []);
-  const onRowClicked = useCallback(
-    (id, item) => {
-      redirect(itemLink(item));
-    },
-    [itemLink, redirect]
-  );
+  };
 
   return (
-    <CustomTable
+    <Table
       columns={columns}
-      rows={jobs?.map((job) => createRow(job, deploymentId))}
-      handleRowClick={onRowClicked}
-      itemLink={itemLink}
-      totalCount={totalCount}
-      lazyParams={lazyParams}
-      setLazyParams={setLazyParams}
-      rowsPerPage={rowsPerPage}
+      rows={jobs?.map((job) => createRow(job))}
+      onRowClick={onRowClick}
+      pagination={pagination}
+      onPage={onPage}
       loading={loading}
+      autoHeight
     />
   );
 }
diff --git a/src/components/common/User/UserOperationList.js b/src/components/common/User/UserOperationList.js
index d98be2cb5df1b0fc946b28a32e5f0d5113499da1..371401793ed9ae95e5b406b35e909fa81ad0ec8a 100644
--- a/src/components/common/User/UserOperationList.js
+++ b/src/components/common/User/UserOperationList.js
@@ -4,31 +4,39 @@ import { initRequestParams } from "../Helper";
 import { useEffect, useState } from "react";
 import { useOperationsSearch } from "../../../api/SwaggerApi";
 import { JobAsyncList } from "../../Job/JobAsyncList";
+import { usePagination } from "../../../hooks/pagination";
 
 export function UserOperationList({ userName }) {
   const [operations, getOperations, , loading] = useOperationsSearch();
-
-  const [lazyParams, setLazyParams] = useState({
-    first: 0,
-    rows: 20,
-    page: 0
-  });
+  const [jobList, setJobList] = useState([]);
+  useEffect(() => {
+    setJobList(operations.operationsList);
+  }, [operations, setJobList]);
 
   const rowsPerPage = [20, 50];
+  const { pagination, setPagination } = usePagination({
+    rowsPerPageOptions: rowsPerPage,
+    initLimit: rowsPerPage[0],
+    initPage: 0
+  });
 
-  const [jobList, setJobList] = useState([]);
+  // update pagination whenever search result total pages change
+  useEffect(() => {
+    setPagination({ totalCount: operations.totalCount ?? 0 });
+  }, [setPagination, operations.totalCount]);
 
   useEffect(() => {
-    let requestParams = initRequestParams(lazyParams);
+    let requestParams = initRequestParams(pagination);
 
     requestParams.user = userName;
 
     getOperations(requestParams);
-  }, [getOperations, lazyParams, userName]);
+  }, [getOperations, pagination, userName]);
 
-  useEffect(() => {
-    setJobList(operations.operationsList);
-  }, [operations, setJobList]);
+  // Invoked by Table on change to pagination
+  const onPage = (params) => {
+    setPagination(params);
+  };
 
   return (
     <Card>
@@ -43,10 +51,8 @@ export function UserOperationList({ userName }) {
         jobs={jobList}
         setJobs={setJobList}
         loading={loading}
-        totalCount={operations.totalCount}
-        lazyParams={lazyParams}
-        setLazyParams={setLazyParams}
-        rowsPerPage={rowsPerPage}
+        pagination={pagination}
+        onPage={onPage}
       />
     </Card>
   );
diff --git a/src/mocks/mockAPI.js b/src/mocks/mockAPI.js
index 51a700cd6ddb066690faea67efcc0f644600dfbf..d9ed82ec00d754844679f171ae91f0a9c5d11fcb 100644
--- a/src/mocks/mockAPI.js
+++ b/src/mocks/mockAPI.js
@@ -8,6 +8,26 @@ function getParameters(req, pattern) {
   return match?.params;
 }
 
+function withMockPagination({ req, data, key }) {
+  const params = new URL(req.url).searchParams;
+  const page = Number(params.get("page")) || 0;
+  const limit = Number(params.get("limit")) || 20;
+
+  const startIndex = page * limit;
+  const pagedData = data[key].slice(startIndex, startIndex + limit);
+
+  const results = {
+    limit,
+    listSize: pagedData.length,
+    pageNumber: page,
+    totalCount: data[key].length,
+    [key]: pagedData,
+    DEVELOPER_NOTE: "THIS IS MOCKED PAGING!"
+  };
+
+  return results;
+}
+
 function spec(req) {
   const data = require("./fixtures/ccce-api.json");
   return {
@@ -121,6 +141,12 @@ function getDeploymentJobLog(req) {
   return { body, status };
 }
 
+function listOperations(req) {
+  const data = require("./fixtures/OperationList.json");
+  const body = withMockPagination({ req, data, key: "operations" });
+  return { body };
+}
+
 function getCommandList(req) {
   const body = require("./fixtures/PagedCommandResponse.json");
   return { body };
@@ -176,7 +202,8 @@ const mockAPI = {
     getDeployment,
     getDeploymentJob,
     getDeploymentJobLog,
-    getCommandList
+    getCommandList,
+    listOperations
   },
   hosts: {
     getCSEntryHostWithStatus
@@ -243,6 +270,11 @@ export const apiHandlers = [
   ),
 
   // deployments
+  makeHandler(
+    "GET",
+    qRegExp(".*/operations"),
+    mockAPI.deployments.listOperations
+  ),
   makeHandler(
     "GET",
     qRegExp(".*/deployments/[0-9]+"),
diff --git a/src/stories/views/UserPage/UserPageView.stories.js b/src/stories/views/UserPage/UserPageView.stories.js
new file mode 100644
index 0000000000000000000000000000000000000000..3e248aa889bb550b124b9ce2c819a171565ab4c9
--- /dev/null
+++ b/src/stories/views/UserPage/UserPageView.stories.js
@@ -0,0 +1,13 @@
+import React from "react";
+import { UserPageView } from "../../../views/UserPage";
+import { AppHarness } from "../../../mocks/AppHarness";
+
+export default {
+  title: "Views/UserPage/UserPageView"
+};
+
+export const Default = () => (
+  <AppHarness>
+    <UserPageView />
+  </AppHarness>
+);
diff --git a/src/views/IOC/IOCDetailsView.js b/src/views/IOC/IOCDetailsView.js
index ecb584cbd305fd1c0ec6796211dd4f0cb8c4eb8a..018dd0bf8337295292481a435bbdba30279e8c7d 100644
--- a/src/views/IOC/IOCDetailsView.js
+++ b/src/views/IOC/IOCDetailsView.js
@@ -1,6 +1,12 @@
 import { Grid, Paper, Tab, Tabs, Typography, IconButton } from "@mui/material";
 import ArrowBackIcon from "@mui/icons-material/ArrowBack";
-import React, { useCallback, useContext, useEffect, useState } from "react";
+import React, {
+  useCallback,
+  useContext,
+  useEffect,
+  useMemo,
+  useState
+} from "react";
 import { useOngoingCommand, useOperationsSearch } from "../../api/SwaggerApi";
 import { IOCLiveStatus } from "../../components/IOC/IOCLiveStatus";
 import { IOCManage } from "../../components/IOC/IOCManage";
@@ -18,10 +24,11 @@ import {
   serialize,
   deserialize
 } from "../../components/common/URLState/URLState";
+import { usePagination } from "../../hooks/pagination";
 
 const IOC_POLL_INTERVAL = 10000;
 export function IOCDetailsView({ ioc, getIOC, loading }) {
-  const [state, setState] = useUrlState(
+  const [urlState, setUrlState] = useUrlState(
     {
       tab: "Status",
       jobs_rows: "5",
@@ -41,23 +48,53 @@ export function IOCDetailsView({ ioc, getIOC, loading }) {
     /* reset*/ ongoingCommandLoading
   ] = useOngoingCommand(ioc.id);
 
-  const jobLazyParams = useCallback(() => {
+  const jobUrlPagination = useMemo(() => {
     return {
-      rows: deserialize(state.jobs_rows),
-      page: deserialize(state.jobs_page)
+      rows: deserialize(urlState.jobs_rows),
+      page: deserialize(urlState.jobs_page)
     };
-  }, [state]);
+  }, [urlState.jobs_rows, urlState.jobs_page]);
 
-  const setJobLazyParams = useCallback(
+  const setJobUrlPagination = useCallback(
     (params) => {
-      setState({
+      setUrlState({
         jobs_rows: serialize(params.rows),
         jobs_page: serialize(params.page)
       });
     },
-    [setState]
+    [setUrlState]
   );
 
+  const rowsPerPage = [5, 20];
+
+  const { pagination: jobPagination, setPagination: setJobPagination } =
+    usePagination({
+      rowsPerPageOptions: rowsPerPage,
+      initLimit: jobUrlPagination.rows ?? rowsPerPage[0],
+      initPage: jobUrlPagination.page ?? 0
+    });
+
+  // update pagination whenever search result total pages change
+  useEffect(() => {
+    setJobPagination({ totalCount: operations.totalCount ?? 0 });
+  }, [setJobPagination, operations.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 = (params) => {
+    setJobPagination(params);
+  };
+
   useSafePolling(getIOC, loading, IOC_POLL_INTERVAL);
   useSafePolling(getOngoingCommand, ongoingCommandLoading, IOC_POLL_INTERVAL);
 
@@ -66,14 +103,15 @@ export function IOCDetailsView({ ioc, getIOC, loading }) {
   }, [ioc?.operationInProgress]);
 
   const handleTabChange = (event, tab) => {
-    setState({ tab: serialize(tab) });
+    setUrlState({ tab: serialize(tab) });
   };
 
+  // Submit new search whenever pagination or ioc changes
   useEffect(() => {
-    let requestParams = initRequestParams(jobLazyParams());
+    let requestParams = initRequestParams(jobPagination);
     requestParams.ioc_id = ioc.id;
     getOperations(requestParams);
-  }, [getOperations, jobLazyParams, ioc]);
+  }, [getOperations, jobPagination, ioc]);
 
   const handleClick = () => {
     navigate(-1);
@@ -81,11 +119,11 @@ export function IOCDetailsView({ ioc, getIOC, loading }) {
 
   const resetTab = useCallback(() => {
     if (ioc?.activeDeployment) {
-      setState({ tab: "Status" });
+      setUrlState({ tab: "Status" });
     } else {
-      setState({ tab: "Management" });
+      setUrlState({ tab: "Management" });
     }
-  }, [ioc?.activeDeployment, setState]);
+  }, [ioc?.activeDeployment, setUrlState]);
 
   const setButtonDisabledAndUpdate = useCallback(
     (isDisabled) => {
@@ -127,7 +165,7 @@ export function IOCDetailsView({ ioc, getIOC, loading }) {
   const CustomTabs = ({ children }) => {
     return (
       <Tabs
-        value={deserialize(state.tab)}
+        value={deserialize(urlState.tab)}
         onChange={handleTabChange}
       >
         {children}
@@ -188,8 +226,10 @@ export function IOCDetailsView({ ioc, getIOC, loading }) {
           xs={12}
           style={{ paddingBottom: 0 }}
         >
-          {deserialize(state.tab) === "Status" && <IOCLiveStatus ioc={ioc} />}
-          {deserialize(state.tab) === "Management" && (
+          {deserialize(urlState.tab) === "Status" && (
+            <IOCLiveStatus ioc={ioc} />
+          )}
+          {deserialize(urlState.tab) === "Management" && (
             <IOCManage
               ioc={ioc}
               getIOC={getIOC}
@@ -199,11 +239,11 @@ export function IOCDetailsView({ ioc, getIOC, loading }) {
               operationsLoading={operationsLoading}
               getOperations={getOperations}
               setButtonDisabled={setButtonDisabledAndUpdate}
-              jobLazyParams={jobLazyParams()}
-              setJobLazyParams={setJobLazyParams}
+              pagination={jobPagination}
+              onPage={onPage}
             />
           )}
-          {deserialize(state.tab) === "Admin" && (
+          {deserialize(urlState.tab) === "Admin" && (
             <IOCAdmin
               ioc={ioc}
               getIOC={getIOC}
diff --git a/src/views/host/HostListView.js b/src/views/host/HostListView.js
index 9a1cc77c12f09199c9115af1efc4fbb5705fd5c3..27145fa8b938e5a57e2d3665850b31e1e4b8b177 100644
--- a/src/views/host/HostListView.js
+++ b/src/views/host/HostListView.js
@@ -5,21 +5,18 @@ import React, {
   useContext,
   useMemo
 } from "react";
-import { styled } from "@mui/material/styles";
 import {
   Container,
-  Paper,
   Grid,
   Tabs,
   Tab,
   Typography,
   useMediaQuery
 } from "@mui/material";
-import { RootContainer } from "../../components/common/Container/RootContainer";
 import { HostList } from "../../components/host/HostList";
 import { HostTable } from "../../components/host/HostTable";
 import { useCSEntrySearch } from "../../api/SwaggerApi";
-import { GlobalAppBarContext } from "@ess-ics/ce-ui-common";
+import { GlobalAppBarContext, RootPaper } from "@ess-ics/ce-ui-common";
 import {
   applicationTitle,
   initRequestParams,
@@ -33,18 +30,6 @@ import {
 } from "../../components/common/URLState/URLState";
 import { usePagination } from "../../hooks/pagination";
 
-const PREFIX = "HostListView";
-
-const classes = {
-  root: `${PREFIX}-root`
-};
-
-const StyledRootContainer = styled(RootContainer)(({ theme }) => ({
-  [`& .${classes.root}`]: {
-    marginBottom: theme.spacing(2)
-  }
-}));
-
 export function HostListView() {
   const { setTitle } = useContext(GlobalAppBarContext);
   useEffect(() => setTitle(applicationTitle("IOC hosts")), [setTitle]);
@@ -177,50 +162,46 @@ export function HostListView() {
   );
 
   return (
-    <StyledRootContainer>
-      <Paper className={classes.root}>
+    <RootPaper>
+      <Grid
+        container
+        spacing={1}
+      >
         <Grid
           container
           spacing={1}
+          component={Container}
+          justifyContent="space-between"
+          alignItems="center"
+          style={{ display: "flex" }}
         >
-          <Grid
-            container
-            spacing={1}
-            component={Container}
-            justifyContent="space-between"
-            alignItems="center"
-            style={{ display: "flex" }}
-          >
-            <Grid item>
-              <Tabs
-                value={deserialize(urlState.tab)}
-                onChange={handleTabChange}
-              >
-                <Tab label={<Typography variant="h5">All</Typography>} />
-                <Tab
-                  label={
-                    <Typography variant="h5">Only hosts with IOCs</Typography>
-                  }
-                />
-                <Tab
-                  label={
-                    <Typography variant="h5">
-                      Only hosts without IOCs
-                    </Typography>
-                  }
-                />
-              </Tabs>
-            </Grid>
-          </Grid>
-          <Grid
-            item
-            xs={12}
-            md={12}
-          >
-            {content}
+          <Grid item>
+            <Tabs
+              value={deserialize(urlState.tab)}
+              onChange={handleTabChange}
+            >
+              <Tab label={<Typography variant="h5">All</Typography>} />
+              <Tab
+                label={
+                  <Typography variant="h5">Only hosts with IOCs</Typography>
+                }
+              />
+              <Tab
+                label={
+                  <Typography variant="h5">Only hosts without IOCs</Typography>
+                }
+              />
+            </Tabs>
           </Grid>
         </Grid>
-      </Paper>
-    </StyledRootContainer>
+        <Grid
+          item
+          xs={12}
+          md={12}
+        >
+          {content}
+        </Grid>
+      </Grid>
+    </RootPaper>
   );
 }
diff --git a/src/views/jobs/JobListView.js b/src/views/jobs/JobListView.js
index ab49c9a9faa03442c285be0fe671ea6ae5c0580f..79331f48e586dd0f8d31fbed47b28c689a3439b8 100644
--- a/src/views/jobs/JobListView.js
+++ b/src/views/jobs/JobListView.js
@@ -1,7 +1,5 @@
-import React, { useContext, useState, useCallback } from "react";
-import { styled } from "@mui/material/styles";
+import React, { useContext, useState, useCallback, useMemo } from "react";
 import {
-  Paper,
   Grid,
   FormControlLabel,
   Switch,
@@ -13,7 +11,7 @@ import {
 import Tabs from "@mui/material/Tabs";
 import Tab from "@mui/material/Tab";
 import { useOperationsSearch } from "../../api/SwaggerApi";
-import { userContext } from "@ess-ics/ce-ui-common";
+import { userContext, RootPaper } from "@ess-ics/ce-ui-common";
 import { initRequestParams } from "../../components/common/Helper";
 import { useEffect } from "react";
 import { SearchBar } from "../../components/common/SearchBar/SearchBar";
@@ -24,34 +22,18 @@ import {
   serialize,
   deserialize
 } from "../../components/common/URLState/URLState";
-
-const PREFIX = "JobListView";
-
-const classes = {
-  root: `${PREFIX}-root`,
-  paper: `${PREFIX}-paper`,
-  formControl: `${PREFIX}-formControl`
-};
-
-const StyledPaper = styled(Paper)(({ theme }) => ({
-  [`& .${classes.root}`]: {
-    width: "100%"
-  },
-
-  [`&.${classes.paper}`]: {
-    marginBottom: theme.spacing(2)
-  },
-
-  [`& .${classes.formControl}`]: {
-    marginLeft: "5px",
-    marginRight: "5px"
-  }
-}));
+import { usePagination } from "../../hooks/pagination";
 
 export function JobListView() {
   const [operations, getOperations /* reset*/, , loading] =
     useOperationsSearch();
-  const [state, setState] = useUrlState(
+  const [jobList, setJobList] = useState([]);
+  // Sync results with mutatable list
+  useEffect(() => {
+    setJobList(operations.operationsList);
+  }, [operations, setJobList]);
+
+  const [urlState, setUrlState] = useUrlState(
     {
       tab: "0",
       own: "false",
@@ -65,10 +47,9 @@ export function JobListView() {
 
   const { user } = useContext(userContext);
   const [deploymentStatus, setDeploymentStatus] = useState(null);
-  const [jobList, setJobList] = useState([]);
 
   const handleTabChange = (event, tab) => {
-    setState((s) =>
+    setUrlState((s) =>
       serialize(s.tab) === serialize(tab)
         ? { tab: serialize(tab) }
         : { tab: serialize(tab), page: "0" }
@@ -88,37 +69,63 @@ export function JobListView() {
   };
 
   useEffect(() => {
-    state.tab && changeTab(deserialize(state.tab));
-  }, [state]);
+    urlState.tab && changeTab(deserialize(urlState.tab));
+  }, [urlState]);
 
   const handleChangeOwn = (event) => {
     const own = event.target.checked;
-    setState({ own: serialize(own), page: "0" });
+    setUrlState({ own: serialize(own), page: "0" });
   };
 
-  const lazyParams = useCallback(() => {
+  const urlPagination = useMemo(() => {
     return {
-      rows: deserialize(state.rows),
-      page: deserialize(state.page)
+      rows: deserialize(urlState.rows),
+      page: deserialize(urlState.page)
     };
-  }, [state]);
+  }, [urlState.rows, urlState.page]);
 
-  const setLazyParams = useCallback(
-    (params) => {
-      setState({ rows: serialize(params.rows), page: serialize(params.page) });
+  const setUrlPagination = useCallback(
+    ({ rows, page }) => {
+      setUrlState({
+        rows: serialize(rows),
+        page: serialize(page)
+      });
     },
-    [setState]
+    [setUrlState]
   );
 
   const rowsPerPage = [20, 50];
 
+  const { pagination, setPagination } = usePagination({
+    rowsPerPageOptions: rowsPerPage,
+    initLimit: urlPagination.rows ?? rowsPerPage[0],
+    initPage: urlPagination.page ?? 0
+  });
+
+  // update pagination whenever search result total pages change
+  useEffect(() => {
+    setPagination({ totalCount: operations.totalCount ?? 0 });
+  }, [setPagination, operations.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(
-      lazyParams(),
-      deserialize(state.query)
+      urlPagination,
+      deserialize(urlState.query)
     );
 
-    if (deserialize(state.own)) {
+    if (deserialize(urlState.own)) {
       requestParams.user = user?.loginName;
     }
 
@@ -126,39 +133,46 @@ export function JobListView() {
       requestParams.status = deploymentStatus;
     }
 
-    if (state.job_type) {
-      requestParams.type = deserialize(state.job_type);
+    if (urlState.job_type) {
+      requestParams.type = deserialize(urlState.job_type);
     }
 
     getOperations(requestParams);
-  }, [getOperations, lazyParams, state, user, deploymentStatus]);
-
-  useEffect(() => {
-    setJobList(operations.operationsList);
-  }, [operations, setJobList]);
+  }, [
+    getOperations,
+    urlPagination,
+    urlState.query,
+    urlState.own,
+    urlState.job_type,
+    user,
+    deploymentStatus
+  ]);
 
   const setSearch = useCallback(
     (query) => {
-      setState({ query: serialize(query) });
+      setUrlState({ query: serialize(query) });
     },
-    [setState]
+    [setUrlState]
   );
 
+  // Invoked by Table on change to pagination
+  const onPage = (params) => {
+    setPagination(params);
+  };
+
   const content = (
     <SearchBar
       search={setSearch}
-      query={deserialize(state.query)}
+      query={deserialize(urlState.query)}
       loading={loading}
       placeholder="Search in IOC name, user or git reference"
     >
       <JobAsyncList
         jobs={jobList}
         setJobs={setJobList}
+        pagination={pagination}
+        onPage={onPage}
         loading={loading}
-        totalCount={operations.totalCount}
-        lazyParams={lazyParams()}
-        setLazyParams={setLazyParams}
-        rowsPerPage={rowsPerPage}
       />
     </SearchBar>
   );
@@ -170,7 +184,7 @@ export function JobListView() {
   ];
 
   return (
-    <StyledPaper className={classes.paper}>
+    <RootPaper>
       <Grid
         container
         spacing={1}
@@ -185,7 +199,7 @@ export function JobListView() {
         >
           <Grid item>
             <Tabs
-              value={deserialize(state.tab)}
+              value={deserialize(urlState.tab)}
               onChange={handleTabChange}
             >
               <Tab label={<Typography variant="h5">All</Typography>} />
@@ -203,8 +217,10 @@ export function JobListView() {
               id="combo-box-demo"
               options={jobTypeOptions}
               defaultValue={
-                state.job_type
-                  ? jobTypeOptions.find((o) => o.filter === state.job_type) || {
+                urlState.job_type
+                  ? jobTypeOptions.find(
+                      (o) => o.filter === urlState.job_type
+                    ) || {
                       label: "Show all"
                     }
                   : { label: "Show all" }
@@ -222,7 +238,7 @@ export function JobListView() {
                 />
               )}
               onChange={(event, value, reason) =>
-                setState({ job_type: value?.filter })
+                setUrlState({ job_type: value?.filter })
               }
               autoSelect
             />
@@ -233,10 +249,9 @@ export function JobListView() {
               renderNoAccess={() => <></>}
             >
               <FormControlLabel
-                className={classes.formControl}
                 control={
                   <Switch
-                    checked={deserialize(state.own)}
+                    checked={deserialize(urlState.own)}
                     onChange={handleChangeOwn}
                   />
                 }
@@ -253,6 +268,6 @@ export function JobListView() {
           {content}
         </Grid>
       </Grid>
-    </StyledPaper>
+    </RootPaper>
   );
 }