From 2eba4eaf59e80b32ca38ab5cfbb2c79e98f3fe7c Mon Sep 17 00:00:00 2001
From: Lim Chee Aun <cheeaun@gmail.com>
Date: Wed, 5 Jul 2023 16:59:28 +0800
Subject: [PATCH] Prevent re-render timeline in multi-column mode

---
 src/components/columns.jsx |  6 +++++-
 src/pages/hashtag.jsx      | 19 +++++++++++--------
 src/pages/list.jsx         |  5 +++--
 src/pages/mentions.jsx     |  5 +++--
 src/pages/public.jsx       | 11 +++++++----
 src/pages/trending.jsx     |  9 +++++----
 src/utils/useTitle.js      |  6 +++---
 7 files changed, 37 insertions(+), 24 deletions(-)

diff --git a/src/components/columns.jsx b/src/components/columns.jsx
index d79ab5a4..0f1f0237 100644
--- a/src/components/columns.jsx
+++ b/src/components/columns.jsx
@@ -18,6 +18,8 @@ function Columns() {
   const snapStates = useSnapshot(states);
   const { shortcuts } = snapStates;
 
+  console.debug('RENDER Columns', shortcuts);
+
   const components = shortcuts.map((shortcut) => {
     if (!shortcut) return null;
     const { type, ...params } = shortcut;
@@ -33,7 +35,9 @@ function Columns() {
       trending: Trending,
     }[type];
     if (!Component) return null;
-    return <Component {...params} />;
+    return (
+      <Component key={type + JSON.stringify(params)} {...params} columnMode />
+    );
   });
 
   useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {
diff --git a/src/pages/hashtag.jsx b/src/pages/hashtag.jsx
index 76337280..b81a6744 100644
--- a/src/pages/hashtag.jsx
+++ b/src/pages/hashtag.jsx
@@ -25,9 +25,9 @@ const LIMIT = 20;
 const TAGS_LIMIT_PER_MODE = 4;
 const TOTAL_TAGS_LIMIT = TAGS_LIMIT_PER_MODE + 1;
 
-function Hashtags(props) {
-  const navigate = useNavigate();
-  let { hashtag, ...params } = useParams();
+function Hashtags({ columnMode, ...props }) {
+  // const navigate = useNavigate();
+  let { hashtag, ...params } = columnMode ? {} : useParams();
   if (props.hashtag) hashtag = props.hashtag;
   let hashtags = hashtag.trim().split(/[\s+]+/);
   hashtags.sort();
@@ -217,11 +217,14 @@ function Hashtags(props) {
                   ) {
                     hashtags.push(newHashtag);
                     hashtags.sort();
-                    navigate(
-                      instance
-                        ? `/${instance}/t/${hashtags.join('+')}`
-                        : `/t/${hashtags.join('+')}`,
-                    );
+                    // navigate(
+                    //   instance
+                    //     ? `/${instance}/t/${hashtags.join('+')}`
+                    //     : `/t/${hashtags.join('+')}`,
+                    // );
+                    location.hash = instance
+                      ? `/${instance}/t/${hashtags.join('+')}`
+                      : `/t/${hashtags.join('+')}`;
                   }
                 }}
               >
diff --git a/src/pages/list.jsx b/src/pages/list.jsx
index d42d5109..3c360288 100644
--- a/src/pages/list.jsx
+++ b/src/pages/list.jsx
@@ -24,7 +24,7 @@ function List(props) {
   const snapStates = useSnapshot(states);
   const { masto, instance } = api();
   const id = props?.id || useParams()?.id;
-  const navigate = useNavigate();
+  // const navigate = useNavigate();
   const latestItem = useRef();
   // const [reloadCount, reload] = useReducer((c) => c + 1, 0);
 
@@ -154,7 +154,8 @@ function List(props) {
                 setList(result.list);
                 // reload();
               } else if (result.state === 'deleted') {
-                navigate('/l');
+                // navigate('/l');
+                location.hash = '/l';
               }
               setShowListAddEditModal(false);
             }}
diff --git a/src/pages/mentions.jsx b/src/pages/mentions.jsx
index 3e77f0f6..a57d9e2e 100644
--- a/src/pages/mentions.jsx
+++ b/src/pages/mentions.jsx
@@ -8,11 +8,12 @@ import { saveStatus } from '../utils/states';
 import useTitle from '../utils/useTitle';
 
 const LIMIT = 20;
+const emptySearchParams = new URLSearchParams();
 
-function Mentions(props) {
+function Mentions({ columnMode, ...props }) {
   useTitle('Mentions', '/mentions');
   const { masto, instance } = api();
-  const [searchParams] = useSearchParams();
+  const [searchParams] = columnMode ? [emptySearchParams] : useSearchParams();
   const type = props?.type || searchParams.get('type');
 
   const mentionsIterator = useRef();
diff --git a/src/pages/public.jsx b/src/pages/public.jsx
index 8f4a1145..b634f236 100644
--- a/src/pages/public.jsx
+++ b/src/pages/public.jsx
@@ -14,16 +14,16 @@ import useTitle from '../utils/useTitle';
 
 const LIMIT = 20;
 
-function Public({ local, ...props }) {
+function Public({ local, columnMode, ...props }) {
   const snapStates = useSnapshot(states);
   const isLocal = !!local;
-  const params = useParams();
+  const params = columnMode ? {} : useParams();
   const { masto, instance } = api({
     instance: props?.instance || params.instance,
   });
   const title = `${isLocal ? 'Local' : 'Federated'} timeline (${instance})`;
   useTitle(title, isLocal ? `/:instance?/p/l` : `/:instance?/p`);
-  const navigate = useNavigate();
+  // const navigate = useNavigate();
   const latestItem = useRef();
 
   const publicIterator = useRef();
@@ -128,7 +128,10 @@ function Public({ local, ...props }) {
               }
               if (newInstance) {
                 newInstance = newInstance.toLowerCase().trim();
-                navigate(isLocal ? `/${newInstance}/p/l` : `/${newInstance}/p`);
+                // navigate(isLocal ? `/${newInstance}/p/l` : `/${newInstance}/p`);
+                location.hash = isLocal
+                  ? `/${newInstance}/p/l`
+                  : `/${newInstance}/p`;
               }
             }}
           >
diff --git a/src/pages/trending.jsx b/src/pages/trending.jsx
index 92c03b21..743fcaad 100644
--- a/src/pages/trending.jsx
+++ b/src/pages/trending.jsx
@@ -15,15 +15,15 @@ import useTitle from '../utils/useTitle';
 
 const LIMIT = 20;
 
-function Trending(props) {
+function Trending({ columnMode, ...props }) {
   const snapStates = useSnapshot(states);
-  const params = useParams();
+  const params = columnMode ? {} : useParams();
   const { masto, instance } = api({
     instance: props?.instance || params.instance,
   });
   const title = `Trending (${instance})`;
   useTitle(title, `/:instance?/trending`);
-  const navigate = useNavigate();
+  // const navigate = useNavigate();
   const latestItem = useRef();
 
   const [hashtags, setHashtags] = useState([]);
@@ -151,7 +151,8 @@ function Trending(props) {
               }
               if (newInstance) {
                 newInstance = newInstance.toLowerCase().trim();
-                navigate(`/${newInstance}/trending`);
+                // navigate(`/${newInstance}/trending`);
+                location.hash = `/${newInstance}/trending`;
               }
             }}
           >
diff --git a/src/utils/useTitle.js b/src/utils/useTitle.js
index 17f32fb4..af9b8b44 100644
--- a/src/utils/useTitle.js
+++ b/src/utils/useTitle.js
@@ -1,4 +1,4 @@
-import { useEffect } from 'preact/hooks';
+import { useLayoutEffect } from 'preact/hooks';
 import { matchPath } from 'react-router-dom';
 import { subscribeKey } from 'valtio/utils';
 
@@ -23,13 +23,13 @@ export default function useTitle(title, path) {
     } else if (path) {
       matched = matchPath(path, currentLocation);
     }
-    console.log('setTitle', { title, path, currentLocation, paths, matched });
+    console.debug('setTitle', { title, path, currentLocation, paths, matched });
     if (matched) {
       document.title = title ? `${title} / ${CLIENT_NAME}` : CLIENT_NAME;
     }
   }
 
-  useEffect(() => {
+  useLayoutEffect(() => {
     setTitle();
     return subscribeKey(states, 'currentLocation', setTitle);
   }, [title, path]);