               (persistence/try-restore
                {:key (:state-key props)
                 :validator (fn [stored-value] (= (set (keys stored-value)) persistence-keys))
-                :initial
-                (fn [] (let [processed-columns (if-let [defaults (:column-defaults props)]
-                                                 (let [by-header (utils/index-by :header (:columns props))
-                                                       default-showing (->> defaults
-                                                                            (replace by-header)
-                                                                            (map #(assoc % :show-initial? true)))
-                                                       default-hiding (as-> by-header $
-                                                                            (apply dissoc $ defaults)
-                                                                            (vals $)
-                                                                            (map #(assoc % :show-initial? false) $))]
-                                                   (concat default-showing default-hiding))
-                                                 (:columns props))
-                             column-meta (vec (map (fn [{:keys [header header-key starting-width] :as col}]
-                                                     {:header (or header-key header)
-                                                      :width (or starting-width 100)
-                                                      :visible? (get col :show-initial? true)})
-                                                   processed-columns))
-                             initial-sort-column (or (some->> (:columns props)
-                                                              (filter #(contains? % :sort-initial))
-                                                              first)
-                                                     (when (:always-sort? props)
-                                                       (first (:columns props))))]
-                         {:column-meta column-meta
-                          :filter-group-index (get props :initial-filter-group-index 0)
-                          :query-params (merge
-                                         {:current-page 1 :rows-per-page initial-rows-per-page
-                                          :filter-text ""}
-                                         (when initial-sort-column
-                                           {:sort-column (:header initial-sort-column)
-                                            ; default needed when forcing sort
-                                            :sort-order (or (:sort-initial initial-sort-column) :asc)}))}))}))))
-   :render
-   (fn [{:keys [this state props refs after-update]}]
-     (assert (vector? (:column-meta @state)) "column-meta got un-vec'd")
-     (let [{:keys [filterable? reorderable-columns? toolbar retain-header-on-empty?]} props
-           {:keys [no-data? error]} @state
-           any-width=remaining? (->> (:column-meta @state)
-                                     (map :width)
-                                     (some (partial = :remaining)))]
-       [:div {}
-        (when (or filterable? reorderable-columns? toolbar)
-          (let [built-in
-                [:div {:style {:paddingBottom "1em"}}
-                 (when reorderable-columns?
-                   [:div {:style {:float "left"}}
-                    [comps/Button {:icon :settings :title-text "Select Columns..."
-                                   :ref "col-edit-button"
-                                   :onClick #(swap! state assoc :reordering-columns? true)}]
-                    (when (:reordering-columns? @state)
-                      [overlay/Overlay
-                       {:get-anchor-dom-node #(react/find-dom-node (@refs "col-edit-button"))
-                        :dismiss-self #(swap! state assoc :reordering-columns? false)
-                        :content
-                        (react/create-element
-                         table-utils/ColumnEditor
-                         {:columns (react/call :get-ordered-columns this)
-                          :on-reorder
-                          (fn [source-index target-index]
-                            (swap! state update :column-meta utils/move source-index target-index))
-                          :on-visibility-change
-                          (fn [column-index visible?]
-                            (if (= :all column-index)
-                              (swap! state update :column-meta #(vec (map merge % (repeat {:visible? visible?}))))
-                              (swap! state assoc-in [:column-meta column-index :visible?] visible?)))})}])])
-                 (when filterable?
-                   [:div {:style {:float "left" :marginLeft "1em"}}
-                    [table-utils/TextFilter {:initial-text (get-in @state [:query-params :filter-text])
-                                             :on-filter #(swap! state update-in [:query-params]
-                                                                assoc :filter-text % :current-page 1)}]])
-                 (when (:filter-groups props)
-                   [:div {:style {:float "left" :marginLeft "1em" :marginTop -3}}
-                    [table-utils/FilterGroupBar
-                     (merge (select-keys props [:filter-groups :data])
-                            {:selected-index (:filter-group-index @state)
-                             :on-change (fn [new-index]
-                                          (swap! state assoc :filter-group-index new-index)
-                                          (after-update #(react/call :refresh-rows this))
-                                          (when-let [f (:on-filter-change props)]
-                                            (f new-index)))})]])
-                 (common/clear-both)]]
-            ((or toolbar identity) built-in)))
-        [:div {:style {:position "relative"}}
-         [comps/DelayedBlocker {:ref "blocker" :banner "Loading..."}]
-         ;; When using an auto-width column the table ends up ~1px wider than its parent
-         [:div {:style {:overflowX (if any-width=remaining? "hidden" "auto")}}
-          [:div {:style {:position "relative"
-                         :paddingBottom 10
-                         :minWidth (when-not (or no-data? any-width=remaining?)
-                                     (->> (react/call :get-ordered-columns this)
-                                          (filter :visible?)
-                                          (map :width)
-                                          (apply +)))
-                         :cursor (when (:dragging? @state) "col-resize")}}
-           (when (or (not no-data?) retain-header-on-empty?)
-             (table-utils/render-header state props this))
-           (when error
-             [:div {:style {:padding "0.5em"}}
-              (style/create-server-error-message error)])
-           (when (and (not error) no-data?)
-             (style/create-message-well (or (:empty-message props) "There are no rows to display.")))
-           [table-utils/Body
-            (assoc props
-              :columns (filter :visible? (react/call :get-ordered-columns this))
-              :rows (:display-rows @state))]]]]
-        (when-not (= (:pagination props) :none)
-          [:div {:style {:paddingTop (:paginator-space props)}}
-           [table-utils/Paginator
-            {:width (:width props)
-             :pagination-params (select-keys (:query-params @state) [:current-page :rows-per-page])
-             :num-visible-rows (:filtered-count @state)
-             :num-total-rows (or (:num-total-rows props) (:grouped-count @state))
-             :on-change #(swap! state update-in [:query-params] merge %)
-             :initial-rows-per-page (get-in @state [:query-params :rows-per-page])}]])]))
+                :initial #(initial-table-state props)}))))
+   :render render-table
    :get-ordered-columns
    (fn [{:keys [state]}]
      (->> (:column-meta @state)
           (map (fn [{:keys [header] :as column-meta}]
                  (merge column-meta (get (:given-columns-by-header @state) header))))
           vec))
-   :refresh-rows
-   (fn [{:keys [props state refs]}]
-     (react/call :show (@refs "blocker"))
-     (let [{:keys [pagination data ->row]} props]
-       (if (fn? pagination)
-         (pagination (merge (select-keys @state [:filter-group-index]) (:query-params @state))
-                     (fn [{:keys [group-count filtered-count rows error]}]
-                       (react/call :hide (@refs "blocker"))
-                       (swap! state assoc
-                              :grouped-count group-count
-                              :filtered-count filtered-count
-                              :display-rows (map ->row rows)
-                              :no-data? (empty? rows)
-                              :error error)))
-         (let [{:keys [current-page rows-per-page sort-column sort-order filter-text]} (:query-params @state)
-               grouped-data (if-not (:filter-groups props)
-                              data
-                              (filter (:pred (get-in props [:filter-groups (:filter-group-index @state)]))
-                                      data))
-               filtered-data (if-let [txt (not-empty filter-text)]
-                               (table-utils/filter-data grouped-data ->row (:columns props) txt)
-                               grouped-data)
-               rows (map ->row filtered-data)
-               sorted-rows (if sort-column
-                             (let [column (get (:given-columns-by-header @state) sort-column)
-                                   column-index (utils/first-matching-index (fn [{:keys [header]}] (= header sort-column)) (:column-meta @state))
-                                   key-fn (or (:sort-by column) identity)
-                                   key-fn (if (= key-fn :text) (:as-text column) key-fn)]
-                               (sort-by (fn [row] (key-fn (nth row column-index))) rows))
-                             rows)
-               ordered-rows (if (= :desc sort-order) (reverse sorted-rows) sorted-rows)
-               ;; realize this sequence so errors can be caught early:
-               clipped-rows (if (= pagination :none)
-                              ordered-rows
-                              (doall (take rows-per-page (drop (* (dec current-page) rows-per-page) ordered-rows))))]
-           (react/call :hide (@refs "blocker"))
-           (swap! state assoc
-                  :grouped-count (count grouped-data)
-                  :filtered-count (count filtered-data)
-                  :display-rows clipped-rows
-                  :no-data? (empty? clipped-rows))))))
+   :refresh-rows refresh-table-rows
    :component-did-mount
    (fn [{:keys [this state locals]}]
      (swap! locals assoc :initial-state (select-keys @state persistence-keys))

   (fn [{:keys [this]}]
     (.removeEventListener js/window "mousemove" (.-onMouseMoveHandler this))
     (.removeEventListener js/window "mouseup" (.-onMouseUpHandler this)))})

   :starting-width (or (:starting-width props) 200)
   :as-text #(common/format-date % (:format props))})

(declare initial-table-state render-table refresh-table-rows)

;; Table component with specifiable style and column behaviors.
;;
