Notes on enhancements and bugfixes from Bhagavad Gita v1.3 Next.js web app to v1.4.1

Last updated on 5th Sep. 2024
Minor update on 1 Oct. 2024

Quick Info

App (latest version) deployed at: https://gita-rsi.vercel.app/
Github repo of app: https://github.com/ravisiyer/gita

About older v1.3

Details

To Do:
Settings page:
Done: 1. Message when no translator or commentator is selected should be changed appropriately.
Done: 2. Label of select at least one translator or commentator should be added
Verse page:
Dropped as code seems to prevent this condition and worst case, we will just be showing empty translators and commentators section: 3. If no translators or no commentators are selected then a message of no translator or commentator selected should be shown.
Done: 4. Commentators more info note can be shown only when a commentator has been selected.

=====
Modified hero images so that Krishna is close to center and somewhat prominent even though standing Arjuna figure is more prominent.

=========

SC feedback and msg to him (slightly edited) on it (on 21st Aug. 2024):
Sairam SC, this is about your feedback on my Gita web app. and some of the work I have done about it:

1) Text size should be uniform (in chapter & verse pages)
Verse page now uses same size (normal font size) for all contents except for the Sanskrit verse text which is bigger (text-3xl), and section headings which are also bigger and bolder.
One issue with this is that Hindi and Sanskrit translations and commentaries appear a little compressed as compared to English translations and commentaries (abbr. to T&C), even though they use the same font-size. I chose to use default font of the device for Sanskrit and Hindi for some reasons (omitting to keep this short). I don't know enough about this rather involved topic of Devanagari fonts and their sizes in relation to English fonts (I use latin subset of Google Inter font - https://fonts.google.com/specimen/Inter - as the standard font for the app but as that does not have Devanagari support, the device default font for Devanagari characters comes into play, as I understand it.) Initially I went for a temp fix of having slightly larger font-size for Devanagari text in T&C (than the English T&C). Now I am not sure whether that will lead to an odd UI experience. So I am going for the coding & design wise safe approach of just using same font size for all T&C whether English or Hindi or Sanskrit. If people really start using the app and raise an issue about Devanagari text for T&C being too small/compressed, and I am free then, then I will dive into this font issue in greater depth and see whether there is a well accepted way of handling it, which I can adopt safely in the app.

Similar changes as in Verse page were done for Chapter page (verse Sanskrit text for each verse is slightly bigger (text-xl) as otherwise it seems too small). Changed 'More Translations & Commentaries' to just 'More' - let me see how that feels over time. Slightly reduced the font size for Verse and More buttons. Also made the background colour of the buttons lighter. 
Made similar changes in Chapter Summaries page.

2) Collapsible text can be used for less important text like transliteration and/or Order of text parts (transliteration, translation etc.) can be changed .. I think with above 1) changes, the page seems more readable. I am not sure whether I should introduce collapsible text as that will mean user has to do lot of clicking as he goes through the verses, if he wants to see the collapsed text. So, as of now, I am skipping the collapsible text part. But I will keep it in mind and will use it if I feel the need for it in future.

3) Done but with reservations: Save settings - could do it on client if client API provides return value to confirm whether it got saved or not.

Cookie vs. Local Storage for Settings: The key pages of my app. that show Chapter, Verse and Chapter summaries are React/Next.js server side components/pages, and benefit from some advantages that server side components/pages have, in particular by having simpler code to get data from graphQL server. So I don't think Local Storage would be appropriate for my app, as of now at least.

About 'Save settings' instead of 'Save settings as browser cookie" (and message of "Settings sent to browser as cookie."): Client side cookie setting and getting in MDN reference page: https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie . When I browsed through the longish page, I could not find any mention of handling failure of setting a cookie (Document.cookie = "..."). I had also not got any hits when I had previously browsed for this failure handling. Looks like the coding convention is to simply presume the cookie gets saved successfully. I think I will go by that convention. I will stick to setting cookies on server side as that's how my code is now. I have changed the button label (to 'Save settings') and message (to 'Settings saved') to remove the browser cookie part. I am still not convinced about this but it is too small an issue for me to spend more time, and the wise thing to do is to simply follow the convention. If some problem crops up with this approach, then it would be appropriate to invest time in coming up with a good solution. Note that on client side, one could read the cookie after writing to confirm it got written correctly. But then I don't want to do that unless I see some other 'standard' type of code that does it.

4) Mostly done: Use a more obvious loading icon like throbber in main contents of page instead of on top of page ... Next.js has some specific mechanisms for this - https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming covers it. I had not used loading.js/loading.tsx previously thinking that using React Suspense 'element' with a simple text loading message was enough along with the top orange progress line and small circle segments spinner on top right (TopLoader component for Next.js https://www.npmjs.com/package/nextjs-toploader). Now I am using loading.tsx with a spinner component - (react-loader-spinner package https://www.npmjs.com/package/react-loader-spinner and Circles component in it https://mhnpd.github.io/react-loader-spinner/docs/category/components/). It works to some extent but on Vercel, for verse page (and perhaps also chapter page), there sometimes is a gap after the spinner goes and content is shown on the page. In the interim only the yellow background (empty) is shown. Also the Circles (spinner) component is not shown on next prev navigation. I think this is where the Suspense elements using the same Circles component of react-loader-spinner would help. I plan to try it out. In the interim, I am retaining the top loader progress line as that works in most cases, but now it seems to stop as soon as the spinner is shown. Don't know how the implementation of the TopLoader is different from react-loader-spinner but I don't want to invest time in this matter right now.

5) Tabs could be used in Settings page: Plan to explore Headless UI tabs component for this later.

Currently implemented stuff of above points can be seen at: https://gita-rsi.vercel.app/ (There are some other minor UI changes including changes to the hero images).

Thanks for your valuable input. I think the UI of the web app. is already somewhat better due to above changes, at least some of which are prompted by your feedback (some I had planned to do later like improving Settings page UI including tabs possibility). Thanks again. Sairam brother!
--------------

react-loader-spinner, https://www.npmjs.com/package/react-loader-spinner , https://mhnpd.github.io/react-loader-spinner/docs/category/components/ along with using loading.tsx files for app (layout) and main app (layout), has provided the clearly visible spinner effect. Related code is given below (the div is for centering the circles component in the window/containing element):
app\(mainapp)\loading.tsx:
"use client";
import { Circles } from "react-loader-spinner";
export default function Loading() {
  return (
    <div className="flex min-h-[calc(100vh-45px)] min-w-full justify-center items-center">
      <Circles
        height="200"
        width="200"
        color="rgb(251 146 60)" //bg-orange-400
        ariaLabel="circles-loading"
      />
    </div>
  );
}
==========

I used "use client" as I thought that may result in faster display of circles when a link/button is clicked. But it is not as fast as the TopLoader. TopLoader always shows up immediately after link/button is clicked. As of now, I am retaining both TopLoader and Circles component of react-loader-spinner.

https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming covers loading.js. I think that is enough for my needs. I don't need Suspense as I don't really have component loading boundaries within a page.

Update: On Vercel, for verse page (and perhaps also chapter page), there sometimes is a gap after the spinner goes and content is shown on the page. In the interim only the yellow background (empty) is shown. So I think this is where the Suspense elements would help. I should try it out. Also I need to make the circles and containing div a Loading component which is used by the two loading.tsx files and the Suspense elements.
Also spinner is not shown on next prev navigation. Perhaps Suspense elements may help here too.
==========

Search feature challenges

It seems that https://gql.bhagavadgita.io/graphiql does not support pattern search. GraphQL does not seem to provide something like SQL's Like pattern matching, by default. But I could not get a clear and definitive answer on this. So maybe I am wrong.
I put up the following message in associated Discord channel: https://discord.com/channels/798780458405199893/859299845428084757 :
Hi, Thanks for this great graphql endpoint: https://gql.bhagavadgita.io/graphiql . I am using it in my Gita web app: https://gita-rsi.vercel.app/ . I was exploring a search (pattern match) feature for translations' description but it seems that this graphql endpoint does not have this feature. Am I correct? Thanks.
----

https://api.bhagavadgita.io/docs says it is a REST API with search facility. But it needs a key and I can't figure out how to get the key.  gita-frontend-v2 project seems to be using this API.

gita-frontend-v2 project, has the related code (fetch in a useEffect) in src\app\search\[[...locale]]\SearchPage.tsx . The fetch statement passes a header property X-API-KEY with value of process.env.NEXT_PUBLIC_BG_API_KEY.

NEXT_PUBLIC_BG_API_KEY is found only once in the project (above code), as expected. So the project code does not tell us the key data.
...

Where does Next.js store public keys?, https://stackoverflow.com/questions/75789198/where-does-next-js-store-public-keys . Answer by Youssouf Oumar states that if one searches for the value of the public key (but not name of the public key) in the .next folder, one will find it. This is confirmed by the original post author.

So I cannot discover the NEXT_PUBLIC_BG_API_KEY by searching the program bundle, as I have no idea about its value. Looks like BG_API_KEY may have been intentionally kept private.
============

https://rapidapi.com/bhagavad-gita-bhagavad-gita-default/api/bhagavad-gita3 does not seem to need a key. I have subscribed to its free plan. In this case, no credit card info. was asked. But the API has only get chapters and verses but not search. This seems to the associated GitHub repo: https://github.com/gita/bhagavad-gita-api.

https://rapidapi.com/ysanimesh/api/bhagavad-gita-api claims, "Note that this API is free to use but has a limit of 1000 requests per hour." But when I try to subscribe to it (clicking on 'Subscribe to Test' button, and click on 'Start Free Plan' under Basic (plan), I am asked to fill in Billing information which asks for Card details.  https://rapidapi.com/ysanimesh/api/bhagavad-gita-api/pricing has a FAQ question on why a credit card is required for a Freemium API. ... I don't want to get into such (credit) card stuff, as of now. Using the API (HTML + JS code from the API playground) gives a 'not subscribed to this API' error.
===========

https://vedicscriptures.github.io/ , associated GitHub: https://github.com/vedicscriptures/bhagavad-gita-api , does not seem to need a key but it does not have search feature.
===============

w-fit (fit-content) breaks on Samsung M21 Android mobile as the text is wrapped to next line with button height increased even though there is room for the button to expand horizontally and fit all the content in one horizontal segment (much lesser than screen width). The same content on Chrome browser Inspect Mobile on desktop shows the fit-content properly without text wrapping to next line. ... It seems fit-content support is not wide ... CSS width:fit-content not working on iphones, https://stackoverflow.com/questions/60052794/css-widthfit-content-not-working-on-iphones, Feb. 2020. A comment in it says support is not great and refers to https://caniuse.com/mdn-css_properties_width_fit-content#google_vignette which shows pretty good support including Chrome on Android which is what my Samsung M21 uses. Hmm.

Strangely, using flex with w-fit fixes the Chrome on Samsung M21 Android mobile problem. Decided to use it for Chapter summaries page's chapter number and name button as that content size varies quite a bit. For other buttons in Chapter summaries and Chapter pages, I am using fixed width.
=============

1) Done with some issues unresolved: Created LoaderSpinner component which is (re)used by loading pages and also Suspense elements. Suspense elements being added to Chapter, Verse and Chapter Summaries pages with fallback as the LoaderSpinner may not have fully resolved the blank page being shown issue between spinner going away and text content being shown on Verse (and perhaps Chapter pages). But I was able to see the issue only once in regular usage and have not been able to recreate it using a few trials with Chrome throttling. I don't know exactly when the fallback is invoked for a component that has a lot of code before the return statement have the JSX/TSX code creating the HTML elements. I think I should spend more time in the matter only if the blank page being shown for little time issue occurs frequently.

The spinner is not shown on next prev navigation within Chapter or Verse pages. I think that may be because only the dynamic part of the route changes in these cases. The TopLoader gets shown in these cases. I have increased the height of the TopLoader (to 10px) and also started it from 50 percent so it becomes more visible and can even be noticed on mobile. There does not seem to be a prop. for increasing thickness of the circle-spinner of TopLoader and so that continues to be hard to notice. So I have disabled the circle-spinner of TopLoader.

============

Ran npm run build and then npm run start to run the production server. ... It worked OK. But it gave a console message on server about using sharp package (npm i sharp) for image optimization. So I did that. npm run dev and testing app after that worked. npm run build was without error. On running production server app ... no message about image optimization was shown and the app ran OK. So that seems to be fixed.
=======================

Listbox for select chapter and verse

1) Done using listbox: Popover could be used for going to specific chapter & verse. Explored Popover and while doing the exploration, felt that listbox itself would do the job if it supported options being used in a grid. I tried that in listbox and it worked. Related project on PC: test-popover-listbox.

Note that Listbox of HUI works with grid but the docs page has __demoMode in Listbox component element which seems to cause the Listbox to open up on load and also trip up, at times at least, if one uses the opened LB right away. It took me quite some time to figure out that __demoMode was the culprit for this strange behaviour. Removing __demoMode seems to fix the issue.

Created a separate project to try out these changes in Select Chapter  & Verse (SCV). As it is going through a few rounds of code changes thought it best to publish it as a public GitHub repo: https://github.com/ravisiyer/SelectChapterVerseLB . As of now, the UI is quite responsive as clicking on the chapter or verse number typically triggers a load of that chapter or verse (exception is if '-' entry is chosen in verse (and perhaps later in chapter too)). The Go button does not seem to be needed now and has been commented out. The code has become more complex and useEffect hook functions had to be added. Don't really know how this impacts performance though I don't see any performance issue while testing on local PC (localhost).
============================= 

25 Aug. 2024: 
Folders with changed files for converting SCV (SCV Input element) to SCVLB (SCV HUI ListBox):
constants -> constants.ts 
ui -> corvlb.jsx (new), navbar.tsx, selectchapvar.tsx, setupcorvlb.jsx (new)
=====

Done: Go button disabling should happen when c or v pages are navigated to from links (or user typing in a url or just going to a bookmarked url).

==================
In SCV component, I faced an issue of order of execution of useEffect. I wanted a useEffect with pathname dependency to run after a useEffect with chapterNumber dependency. The useEffect with pathname dependency appeared ***after*** the useEffect with chapterNumber dependency. As per my first-read understanding of section 'Order of Execution of Effects in useEffect Hook' in 'The last useEffect Hook guide you’ll ever need', https://www.zipy.ai/blog/useeffect-hook-guide , the useEffect with pathname dependency should have executed after useEffect with chapterNumber but I think the reverse was happening. Don't know if 'Strict Mode' tripped me up due to component rendering twice ... Anyway I think I have found a way of doing what I wanted without depending on order of useEffects execution. Later, I should read up more on this to have a better understanding of this scenario.

==========
I faced an npm run build error about adding a function as a dependency in a useEffect but on doing that, I got another error as given below but some part of the message got snipped (VSCode console has these issues, sometimes):
122:3  Warning: The 'checkAndGoToChapterVerse' function makes the dependencies of useEffect Hook (at line 118) change on every rende[--snip--]e definition of 'checkAndGoToChapterVerse' in its own useCallback() Hook.  react-hooks/exhaustive-deps
----

I created some dummy function and captured a similar error message in full (no part missing), for it below:
124:3  Warning: The 'dummyCheckAndGoToChapterVerse' function makes the dependencies of useEffect Hook (at line 120) change on every 
render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'dummyCheckAndGoToChapterVerse' in its own useCallback() Hook.  react-hooks/exhaustive-deps
----

These errors can be seen by running 'npm run lint' too, which gets over faster than 'npm run build'.

I made some trials, but I have not got a clear understanding of why the error is caused. Even if a calling function in the useEffect which function does not change one (or more) of the dependency variables list, some error is shown. I think I should investigate this issue some other time.

Fixing build useEffect warnings breaks SCV breaks implementation

To overcome these warnings, I had to drastically change useEffect(s) code in SCV. I am capturing the reason for changes in short, below:
1) SCVLB project worked well in dev. env. but I did not do a build. The build warnings I got were about useEffect dependencies in SCV (selectchapver.tsx). Besides the major issue mentioned above, I faced another major problem with these useEffect warnings. I was forced to add verseNumber as a dependency to a useEffect which I had designed to run only when chapter number changes (which would typically be by listbox selection change). So I had one useEffect() for chapterNumber change which essentially was like a ListBox chapter number change handler, and another one for verseNumber change. Forcing verseNumber as dependency to be added in chapterNumber useEffect, broke that design approach. Identifying which dependency change triggered the useEffect when there are multiple dependencies, does not seem to be straightforward though there may be some intricate ways of figuring it out. https://react.dev/reference/react/useEffect is a detailed and quite complex page. Effect Events seem to be something interesting to look at, to reduce dependencies.
2) For the rewrite, as anyway the chapterNumber change useEffect() had to include pathname and verseNumber dependencies, I decided to combing chapterNumber and verseNumber useEffect code.
3) To simplify the code, I dropped the going to chapter & verse on verse listbox change. Instead the user has to choose the verse and then click on Go button to go to chapter & verse.
4) To simplify the code, I dropped disabling of Go button when appropriate.
---------

I think useEffect was not a good choice for writing code on chapter and verse listbox change. Instead I should have used a callback mechanim that is invoked directly and separately when chapter or verse listbox changes. I think sometime later I will try out this callback mechanism approach.

I think in general, useEffect should be used sparingly. The documentation states, "useEffect is a React Hook that lets you synchronize a component with an external system." The chapter and verse listboxes are NOT external and so I think I made a bad design decision to go with useEffect for it.

For the time being though the code seems to be in working state without lint warnings with above simplifications (3 & 4). Commit point is: "Faced warnings on build of earlier code; Wrote one main useEffect instead of two in SCV to resolve these issues; Go button click needed" , 71de19f, https://github.com/ravisiyer/gita/commit/71de19f471f0221db73c0bb2dfca65aa10c593e9 . Also deployed on vercel: https://gita-rsi.vercel.app/

I will continue with the useEffect (as of now). I may also try out removing the 3 and 4 simplification and have the earlier functionality with the rewritten useEffect code. This may increase my knowledge about useEffect and so may be worth it.

Trial for easy restoration of disabling Go button (simplification 4) was successful. But trial for easy restoration of "going to chapter & verse on verse listbox change" (simplification 3) did not work (I have saved the 'freaky' trial code of SCV as ui/20240825-trial-selectchapver.tsx.txt in commit point mentioned below. I later moved the file to testsrc dir. which is 'gitignored' and so later commits will not have it). I think if I go deeper into useEffect and figure out which dependency change triggered the useEffect, I will be able to more easily restore this feature (simpl. 3). But I don't think it is appropriate for me to invest time in that now. It is OK if the user has to press the Go button to go to chapter or chapter-verse. Disabled and enabled Go button is a good visual cue.

Commit point: "About page update", d78b1a2, https://github.com/ravisiyer/gita/commit/d78b1a29f0b1c2fc036cd5d9924d793c2f207081 which has been deployed on https://gita-rsi.vercel.app/ has above mentioned features.

==========================

nextjs-toploader (NextTopLoader) progress bar does not show when I use the Go button which uses replace method of useRouter hook. NextTopLoader itself seems to use nprogress. Some articles on how to fix the issue with nprogress (don't know if that will work with NextTopLoader too):
How to show a route change transition with a progress bar like NProgress in Next JS 13?, https://stackoverflow.com/questions/75124452/how-to-show-a-route-change-transition-with-a-progress-bar-like-nprogress-in-next ... Has a useEffect() solution/workaround for the issue. But in my case, I have NextTopLoader in layout.tsx which is a server component!! Hmm. Is that the key issue here? I mean, the Go button is in a client SCV component in navbar client component. The NextTopLoader is in a server component and so comes into play for all server related stuff like Next/Prev links. ... Should I try out moving NextTopLoader to navbar component?

Moved NextTopLoader to navbar component. Then I needed to only add Nprogress.start() (along with its import statement) in Go button submit handler in SCV component. A TS error cropped up for the 'import NProgress from "nprogress";' in SCV. TS error was fixed by running 'npm i @types/nprogress'.

==========================
27 Aug. 2024

How to make all files in any folder in Windows sorted by date created?, https://superuser.com/questions/1792754/how-to-make-all-files-in-any-folder-in-windows-sorted-by-date-created ... Explains that Folder Options -> Apply to Folders, applies the view settings for that folder to all folders of that type.
---

How to check if github repo has gone ahead of local git? I updated README of few repos directly on GitHub. Then I wanted to update local git repos with the update. Running 'git status'  on local repo said "On branch main
Your branch is up to date with 'origin/main'."

That was inaccurate as origin/main was ahead of my local git branch!
Comment of Piyush Agarwal in this article gave me the solution: Git: How to check if a local repo is up to date?, https://stackoverflow.com/questions/7938723/git-how-to-check-if-a-local-repo-is-up-to-date. The key points for me are:
1) First run, 'git remote update'
2) Run 'git status -uno'
In my case, the 2nd command above gave following output:
On branch main
Your branch is behind 'origin/main' by 2 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit (use -u to show untracked files)
---
That was accurate.
'git pull' pulled in the updates (only README) from the repo.
Running 'git status -uno' again, gave following output:
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit (use -u to show untracked files)
------------
=============================================
In the next repo with similar situation (GH repo README had been updated), I noted that 'git remote update' is the key command. What that was not run, 'git status -uno' did not report local git being behind the remote repo. And after 'git remote update' was run, even simple 'git status' (without -uno options), reported local git being behing GH repo.
=======================

To Do:
1) Initial version done: Tabs could be used in Settings page: Plan to explore Headless UI tabs component for this later.

2) Done: Save Settings button disabled colour should be changed to something less dark. That colour change is in submit-button.tsx.

3) At times, I have noticed that first time Settings button is clicked, the 'Settings saved' message is not shown (but settings get saved). Also confirmed that this problem is there even with the without tabs version on vercel.

4) Perhaps related to 3, first time Settings are saved, the tab gets reset to first (Verse) even if I am on another tab.

5) Done (fixed): Got this console message: "Both english and hindi LTS are false! Making englishLTS true." 

6) Done (fixed): If all languages for Verse page are unchecked and attempt to Save settings is made, I am shown error dialog, but 'Settings saved' message is also shown! 

========
Settings saved message issue-1

About problem 3 above
... In debugging, saw that submitInvokedOnce is set to true on button click but mysteriously gets set to false later. Is this a strict mode issue? But I modifed About page to have a count state variable and increment it through button click. It does not get reset to 0! Note I changed About page to client component for this test. ... Changed the About page code to simply set a state variable to true. It does not reset it to false! Instead of About page what if I have a button component put up on about page? When used as a div containing a button, the button component state variable does not get reset to false. But if I use it in form, it gets reset to false!!! Hmm. The code is given below:
"use client";
import { useState } from "react";

function TestClickedOnce() {
  const [clickedOnce, setClickedOnce] = useState(false);
  function handleButtonClick() {
    setClickedOnce(true);
  }
  return (
    <form>
      <label>{`Current clickedOnce: ${clickedOnce}`}</label>
      <button onClick={handleButtonClick} className="ml-2 border border-black">
        TestClickedOnce component: Set clickedOnce to true (handler calls
        'setClickedOnce(true)' )
      </button>
    </form>
  );
}
export default TestClickedOnce;
============

Addition of e.preventDefault() in About page test fixed the issue. It was simple. The form was being submitted resulting in page being recreated. 

But what about Settings page? My understanding is that if I call e.preventDefault() on every click then the action will not be invoked (a simple test seems to confirm this). So I call it only if validation fails.

Basic React form submit refreshes entire page, https://stackoverflow.com/questions/50193227/basic-react-form-submit-refreshes-entire-page explains the standard technique for submit handlers that don't want a page refresh. Invoke preventDefault and then do the logic for the submission. ... But how do we do it with form action? - Did not get suitable results on search.

Strange behaviour is that form component seems to get re-created only on first action invocation (after reload of settings page). Later action invocations don't seem to recreate form component and so submitInvokedOnce gets set to true and remains as true (not reset to false).

-----------
https://react.dev/reference/react/useState gives a different example of Form with key, where it states, "You can reset a component’s state by passing a different key to a component. In this example, the Reset button changes the version state variable, which we pass as a key to the Form. When the key changes, React re-creates the Form component (and all of its children) from scratch, so its state gets reset."

That suggests that React is recreating the Form component in my case, and that Form component recreation results in creation of Submit button component from scratch, and so the clicked once state variable gets reset to false (and other state variables similarly). Hmm.

The above React page links to: https://react.dev/learn/preserving-and-resetting-state which is quite involved stuff. 

Now I don't know why the Form component is re-created. But now that I know it is re-created, I could remember the clicked Once state by lifting the state variable up from the Submit button to the form. Quite a hack ... Tried lifting state for submitOnceInvoked to settings component (from submit button component). But that did not solve the issue.

Then added a SettingsWrapper client side component wrapping Settings component (which has the form element) and lifted submitOnceInvoked to SettingsWrapper component. Settings page used SettingsWrapper instead of Settings. That too did not resolve the issue.

end section: Settings saved message issue-1
 ========

Looks like Save Settings button only sends current tab's data as formData! When I had a first look at elements of the Settings page with tabs, I could find only active tab's fields/elements. The inactive tabs fields could not be found! If I am correct then, naturally, only active tab's data will be sent as formData. Need to confirm this and if confirmed, see if I can handle the issue.

I searched the net for this issue but could not get suitable results.

Dug into HUI Tabs documentation and found that TabPanel component has a prop called unmount which is true by default. Tried specifying unmount as false for all the TabPanels. Now elements of all tabs are shown in Inspect. And FormData seems to include data from all tab panels. ... This seems to work as expected. Issue 5) above is not seen anymore.

===============

I think using a Server action to save cookies is bad design. I should do everything on client side itself. Then I can use preventDefault to avoid the Form component refresh, which should solve problems 3) and 4) above.
===========

28 Aug. 2024
For Settings: Verse tab languages, I want to explore having nested language tab for < md (768px) and no nested tabs for md and higher.

TabPanel has a prop static which when set to true, shows contents of the panel at all times. If this can be paired with setting the TabList to be invisible, then visually the tabs will not be seen. 

Perhaps a better way would be to simply not use Tab related elements (TabGroup, TabList, TabPanels etc.) if size is >= md. That seems to be non-trivial to do as conditionally rendering only start tag or only end tag seems to not work. There may be some other complex solutions but I want to avoid complexity if I can.

But how does one do this? We cannot do this only in TW CSS. We need to write JS code which will conditionally insert suitable elements, with condition being width of window < md.
Some articles exploring this approach:
Render different components based on screen size in Next.js, https://github.com/vercel/next.js/discussions/14810 : comment of jrgarciadev: https://github.com/vercel/next.js/discussions/14810#discussioncomment-61177 provides a solution and uses same API.

Why not simply use Window.innerWidth? - https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth . Will I face an initial render problem (as mentioned in above github.com link) even for my Settings client component? Let me check it out.
if (window && window.innerWidth > 600) { 
gave me an error: "ReferenceError: window is not defined"!

How to solve "window is not defined" errors in React and Next.js, https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97 give some explanation of why this happens. Key point seems to be that Next.js pre-rendering ability/facility trips up on such stuff. What's the solution? Using a useEffect() as window is accessible there! Hmm. "Dynamic loading" is another solution mentioned in the article but I don't want to go there, as of now.

So I thought that earlier comment of jrgarciadev: https://github.com/vercel/next.js/discussions/14810#discussioncomment-61177 , Sep. 2020, was the way to go.

It worked but I had some TypeScript issues which I was able to resolve eventually. addListener() is deprecated so I used  addEventListener. Tailwind uses min-width for its breakpoints and so used min-width in the useMediaQuery hook. Also, I felt that useCallback is not needed. I also got a useEffect() dependency warning about width which got fixed by adding it as a dependency. Given below is the code of app\hooks\usemediaquery.tsx that I am using now and which is working:

"use client";
import { useEffect, useState } from "react";

// Based on https://github.com/vercel/next.js/discussions/14810#discussioncomment-61177
export const useMediaQuery = (width: number) => {
  const [targetReached, setTargetReached] = useState(false);

  const updateTarget = (e: MediaQueryListEvent) => {
    if (e.matches) {
      setTargetReached(true);
    } else {
      setTargetReached(false);
    }
  };

  useEffect(() => {
    const media = window.matchMedia(`(min-width: ${width}px)`);
    media.addEventListener("change", updateTarget);

    // Check on mount (callback is not called until a change occurs)
    if (media.matches) {
      setTargetReached(true);
    }

    return () => media.removeEventListener("change", updateTarget);
  }, [width]);

  return targetReached;
};

export default useMediaQuery;
=============

This hook enables me to conditionally specify a prop value based on window width Tailwind breakpoint. Related code from app\ui\settings.tsx:
  const tailwindMDBreakpoint = useMediaQuery(TAILWIND_MD_BREAKPOINT);
  // True if min. window width is TAILWIND_MD_BREAKPOINT (768)
...
                            <TabPanel
                              key={index}
                              unmount={false}
                              static={tailwindMDBreakpoint ? true : false}
                            >
...
------------

Changed UI of LanguageSelections (LS) to drop language checkbox. Also to use label only if Tailwind md breakpoint is true (min-width: 768px) - https://tailwindcss.com/docs/screens, as at lesser window width HUI tablist will be shown with language names. This change broke some other code including writing the cookie (as LS data was being written to cookie only if language checkbox was checked). Did one round of fixing those issues but these changes have to be refactored to be cleaner.

=======================

Why insist on selecting at least one translator or commentator? It may not be difficult to remove the code that implements this check. But if it is needed again later, it could be a hassle to locate the old code and merge needed code from it into current version.

Code with above changes put up as commit point: "Updated About page", d793c57. It is also deployed on Vercel (as production version).

==========================
28 Aug. 2024 - To Do (Retaining old numbers)

3) At times, I have noticed that first time Settings button is clicked, the 'Settings saved' message is not shown (but settings get saved). 

4) Perhaps related to 3, first time Settings are saved, the tab gets reset to first (Verse) even if I am on another tab.

==================
30 Aug. 2024

Settings saved message issue-2

Old-3) Done (changing save cookie code to client side fixed the issue): At times, I have noticed that first time Settings button is clicked, the 'Settings saved' message is not shown (but settings get saved). 

Old-4) First time Settings (or after Settings page is refreshed) are saved, the tab gets reset to first (Verse) even if I am on another tab. Initially on changing save cookie code to client side without any form actions, fixed the issue. Later when I added an action to revalidatePath, the problem resurfaced!
On commenting revalidatePath (code as follows) ... :
export async function revalidateAppPaths() {
  // revalidatePath("/");
  return true;
}
----
... this problem (Old-4) does not occur. ***BUT*** saving new settings in Settings page, going to Home page, and then coming back to Settings page shows the ***old data*** before settings were saved. Refreshing the Settings page shows the updated settings! ... So I think it is best to revalidatePath("/") (whole app) even if first time Settings are saved, the Settings page refreshes. That's a minor UI thing. An interesting aspect is why it does not happen second or later times Settings are saved. Perhaps the answer lies in Next.js implementation details of caching and I think it is inappropriate for me, at this stage, to try to get into more details of this issue.

end section: Settings saved message issue-2

1) Done: Change About page
2) Done: @types for nprogress should be dev dependency.
3) Done (using server action to only revalidatePath): Changed settings reflected in 'back' page only after page refresh or next/prev navigation. This seems to be happening after changing save cookie code to client side.
4) Not a big issue but noticeable: In Settings page, on Verse Page tab for md breakpoint, refresh of page first shows first tab's contents (English) and after short delay, shows other tabs' contents. This delay is much more in dev environment than in build environment. But even in build environment one sees a flash/flicker and if one observes closely, one can see that the other language tabs' contents show up only after a short delay.
5) For large window width cases, Entire App tab in Settings Page, shows longer (more height) box borders for its two settings than appropriate.
6) Settings page top-level tabs shows selected tab with an outline but that disappears on changing the tab!
====================


[Need to study the above to get a clear understanding but initial impression is that action will provide form data, whereas onSubmit will only provide event. So if we want to do some validation before form submission, having both onSubmit and action for a form, and onSubmit invoking e.preventDefault() if data is invalid, should work.]

================================

On-demand Revalidation, https://nextjs.org/docs/app/building-your-application/caching#on-demand-revalidation .. It is quite involved. But for my needs, I think I need to use a server action that simply calls revalidatePath with / as the route (and so all pages of the app are revalidated). 
From https://nextjs.org/docs/app/building-your-application/caching#revalidatepath : revalidatepath can be called in "Server Actions - to revalidate data after a user interaction (e.g. form submission, clicking a button)."

From https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations : "Server Actions are not limited to <form> and can be invoked from event handlers, useEffect, third-party libraries, and other form elements like <button>." So the submit handler function should be able to invoke the server action.

How to go to the top of the page after clicking the link?, https://stackoverflow.com/questions/73231576/how-to-go-to-the-top-of-the-page-after-clicking-the-link has solutions for client pages/components.

From https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating : Sections 'Scrolling to an id', https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#scrolling-to-an-id explains how to go to a specific id in a page instead of top of page when using Link component.

Disabling scroll restoration, https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating states, "The default behavior of the Next.js App Router is to scroll to the top of a new route or to maintain the scroll position for backwards and forwards navigation." It provides ways in which to disable this behaviour for Link tag and for router.replace methods which we are interested in.

===============

To move from devDependencies to dependencies (prod):
npm i <module_name> -P

To move from dependencies to devDependencies
npm i <module_name> -D
================

To move from dependencies to devDependencies, I tried:
npm i "@types/nprogress" -D
---
Its main output:
npm WARN idealTree Removing dependencies.@types/nprogress in favor of devDependencies.@types/nprogress
---
That did the job. @types/nprogress in package.json was moved from dependencies to devDependencies.
===================

30 Aug. 2024
Minor issues, currently ignored/tolerated
=============================
1) First time Settings (or after Settings page is refreshed) are saved, the tab gets reset to first (Verse) even if I am on another tab.
2) Not a big issue but noticeable: In Settings page, on Verse Page tab for md breakpoint, refresh of page first shows first tab's contents (English) and after short delay, shows other tabs' contents. This delay is much more in dev environment than in build environment. But even in build environment one sees a flash/flicker and if one observes closely, one can see that the other language tabs' contents show up only after a short delay.
3) For large window width cases, Entire App tab in Settings Page, shows longer (more height) box borders for its two settings than appropriate.
4) Settings page top-level tabs shows selected tab with an outline but that disappears on changing the tab!
===========
1 Sep. 2024
Not really issues
1) The scroll position for some pages during forward and backward navigation is NOT to the top. That seems to be default Next.js behviour: From earlier note: "The default behavior of the Next.js App Router is to scroll to the top of a new route or to maintain the scroll position for backwards and forwards navigation." We can disable/change it if needed. As of now, I think it is best to go with the default behaviour.

===================
3 Sep. 2024


Used below command to get file history of file which has been deleted. It worked well.
git log -p -- app/ui/submit-button.tsx

=======

Settings saved message issue-3

Debugging why 'settings saved' msg is not shown on first use (or first use after refresh):
After refresh, on settings save button click, initially submitInvokedOnce is true, but the related component is rendered more than 2 times (at least 3, perhaps 4) in dev env. So in the 3rd invocation, submitInvokedOnce is set to false (and formDataModified is set to false).
submitInvokedOnce is set to false on definition and only in one place (handleSubmit) it is set to true. No code sets it to false after definition/initialization. So component seems to be getting re-created.

It seems to be similar to an issue noted earlier in this post
Related extract:

Addition of e.preventDefault() in About page test fixed the issue. It was simple. The form was being submitted resulting in page being recreated. 

But what about Settings page? My understanding is that if I call e.preventDefault() on every click then the action will not be invoked (a simple test seems to confirm this). So I call it only if validation fails.

Basic React form submit refreshes entire page, https://stackoverflow.com/questions/50193227/basic-react-form-submit-refreshes-entire-page explains the standard technique for submit handlers that don't want a page refresh. Invoke preventDefault and then do the logic for the submission. ... But how do we do it with form action? - Did not get suitable results on search.

Strange behaviour is that form component seems to get re-created only on first action invocation (after reload of settings page). Later action invocations don't seem to recreate form component and so submitInvokedOnce gets set to true and remains as true (not reset to false).
---- end extract ---

Also see another extract from earlier part of this blog post:

Old-4) First time Settings (or after Settings page is refreshed) are saved, the tab gets reset to first (Verse) even if I am on another tab. Initially on changing save cookie code to client side without any form actions, fixed the issue. Later when I added an action to revalidatePath, the problem resurfaced!
On commenting revalidatePath (code as follows) ... :
export async function revalidateAppPaths() {
  // revalidatePath("/");
  return true;
}
----
... this problem (Old-4) does not occur. ***BUT*** saving new settings in Settings page, going to Home page, and then coming back to Settings page shows the ***old data*** before settings were saved. Refreshing the Settings page shows the updated settings! ... So I think it is best to revalidatePath("/") (whole app) even if first time Settings are saved, the Settings page refreshes. That's a minor UI thing. An interesting aspect is why it does not happen second or later times Settings are saved. Perhaps the answer lies in Next.js implementation details of caching and I think it is inappropriate for me, at this stage, to try to get into more details of this issue.
-------
---- end extract ---
=========================================

I wrote a test app. server-action-recreate to try to recreate the problem [Saved zip filename on PC: server-action-recreate-wo-nmnx.zip ]. But in this case, the server action does NOT result in the problem mentioned earlier! Hmm. I think I need to look at some simple workarounds for this issue in Gita web app. as investigating it as sucking up too much time.
===========

One temp. hack would be to not disable Save Settings button. So if user has some doubt and presses it again, he will be shown the 'Settings saved' message.

router.reload() reloads current page. ... Could be used for Settings to resolve the "saving new settings in Settings page, going to Home page, and then coming back to Settings page shows the ***old data***" problem mentioned above.

Maybe I could use a flag cookie to know that settings are saved instead of relying on useState variable(s)? But resetting the flag cookie may be tricky. It cannot be reset at component load. Perhaps the reset code should be executed after some time interval either after server action of validatePath is invoked or at load time. I think this is too complex an approach and so am dropping it, as of now. If this becomes a major issue then I can consider implementing it. But even in this approach, the first tab of page being shown even when 'Save settings' was clicked from another tab, will persist.

How can I force a certain page to revalidate from a client component in NextJS?, https://stackoverflow.com/questions/77618891/how-can-i-force-a-certain-page-to-revalidate-from-a-client-component-in-nextjs  ... router.refresh() after form submission

============

Created test app settings-save-issues to explore solutions for the settings saved message including form component recreation on action (and warning when user attempts to leave settings page without saving unsaved changes). [Saved zip filename on PC: settings-save-issues-wo-nmnxpb.zip ]
1) Shifting submitInvokedOnce to Settings page and making that page a client page, did not fix the issue. Commit message: "Shifting submitInvokedOnce to Settings page and making that page a client page"
2) Commenting 'revalidatePath("/");' in form action fixed the issue (but creates other issues). Commit message: "Commenting revalidatePath in form action fixed the issue"
3) Using a beforeunload event listener which does event.preventDefault() fixes the issue ... But I could not recreate the solution! ... Also it results in manual refresh appearing with a 'Reload site' standard alert. So we may need a more nuanced solution. Commit message: "Using a beforeunload event listener which does event.preventDefault() fixes the issue." Ref: Handling Browser Refresh in React: Best Practices and Pitfalls, https://medium.com/@stheodorejohn/handling-browser-refresh-in-react-best-practices-and-pitfalls-5d4451d579ff
4) beforeunload event listener works partially to catch user leaving Settings page after making mods. Commit message: "BeforeUnloadEvent that seems to work partially". If so, we can provide a warning. Ref with partial solution:
Detect when a user leaves page in Next JS, https://stackoverflow.com/questions/63664479/detect-when-a-user-leaves-page-in-next-js . It is a partial solution as for browse away using next navigation like <Link> requires router.events which is not supported in Nextjs App router (which I use).
5) Then I reverted to dev app folder manually by replacing app folder in settings-save-issues with dev app folder. Saved the reverted changes as commit: "reverted to dev app folder"
6) Next I implemented just warning when user moves to external site with unsaved settings changes. Commit message: "Implements warning to user if user browses outside app after making unsaved mods. in app but does not work for in app route changes" followed by commit: "code cleanup". The code after "code cleanup" is as follows:
In app\ui\settings.tsx :
  // Based on https://stackoverflow.com/questions/63664479/detect-when-a-user-leaves-page-in-next-js
  const router = useRouter();
  useEffect(() => {
    const handleWindowClose = (e: BeforeUnloadEvent) => {
      if (!formDataModified) return;
      e.preventDefault();
    };
    window.addEventListener("beforeunload", handleWindowClose);
    return () => {
      window.removeEventListener("beforeunload", handleWindowClose);
    };
  }, [formDataModified]);
--- end code extract ---

7) How to handle detecting browse away using next navigation like <Link> and show warning about unsaved settings changes? 
Prevent route change on condition in NextJs 13 with App Router, https://www.reddit.com/r/nextjs/comments/14e8f99/prevent_route_change_on_condition_in_nextjs_13/ : App router does not support router events. The page gives a complex solution. Don't know if it is worth trying it out.

Preventing navigation away with unsaved changes and show a confirmation model in https://github.com/vercel/next.js/discussions/41934 seems to have a somewhat complex but still in range of acceptable solution. May invest time to investigate this solution later on.

Tried out the above. Was able to achieve partial success with a test form but there is an issue with progress indicator not switching off on cancel. The Testform has to be accessed at /testform (http://localhost:3000/testform) url. Commit message: "Trying out vercel GH suggested experimental approach to block Link navigation on unsaved changes". While I had to use the components code from the above article, the final testform code I used is slightly different and so given below:
app\ui\form.tsx :
// components/form.tsx
"use client";
import { useState } from "react";
import { Blocker } from "./navigationblock";

export default function Form() {
  const [formModified, setFormModified] = useState(false);
  return (
    <>
      {formModified ? <Blocker /> : null}
      <input
        type="text"
        name="firstname"
        onChange={(e) => setFormModified(true)}
        className="border-black border"
      />
      <input
        type="text"
        name="lastname"
        onChange={(e) => setFormModified(true)}
        className="border-black border"
      />
    </>
  );
}
--- end code ---

As of now, I think this solution is rather complex and so inappropriate to introduce into my code. The solution seems to be an experimental Nextjs approach and perhaps may get incorporated into a future Nextjs version, at which time I can consider using it.

Also note that Browser back button is not handled in above solution.
=================
Decision about what to do now:
a) I have commented out the disabling of 'Save settings' button. So the first time submit will result in something like page refresh, first tab of Settings page being shown, and 'save settings' message will not be shown. The settings are saved at this stage but the user does not get a clear message and may wonder if the settings have been saved. He may try to simply save settings again by clicking on the 'Save settings' button which now is NOT disabled in such a situation, and in this case, he will be shown a 'Settings saved' message. I think this is a reasonable compromise approach given the issues involved in getting a proper solution which have been listed above.

b) No warning is given if user navigates away from Settings page when there are unsaved changes. I think that's a consistent approach and is preferrable to partly implement warning only for navigation away to external sites. As mentioned earlier, solving navigation within the app. issue in this context is too complex to implement in my app.

end section: Settings saved message issue-3
=======================

Consider using low-res hero image for mobile as it takes lot of time to load. ... It loads quickly most of the time. I have noticed lot of delay in load only once or twice. ... hero-mobile.jpg is only 322 KB in size. I don't think I should go in for compression of this 322 KB image size unless I come to know of  lot of cases of slow load of this image.

===================

Done: For large window width cases, Entire App tab in Settings Page, shows longer (more height) box borders for its two settings than appropriate.

Understood: Settings page top-level tabs sometimes shows selected tab with an outline but that disappears on changing the tab. .... It is a keyboard focus feedback cue kind-of thing. Use cursor right and left moves the outline to next and previous tab. But if one changes tab with mouse-click the outline does not appear. If I recall correctly, this code comes from Headless UI docs.

==========================

============
5 Sep. 2024

Metadata for the app had description of "Simple Bhagavad Gita web app" which has been changed to "Bhagavad Gita web app".

Changing icon of app.
Use icon.ico instead of favicon.ico (which has to be renamed/removed). Ref: Why is my favicon not working in my next js app?, https://stackoverflow.com/questions/61836949/why-is-my-favicon-not-working-in-my-next-js-app

https://www.freepik.com/icons/bhagavad-gita has BG icons and does not need registration for download. However, attribution is requested.

Am using shree_5912377.png (attribution:)
<a href="https://www.freepik.com/icons/bhagavad-gita">Icon by Freepik</a>

https://cloudconvert.com/png-to-ico converted icons are very small though the app may show it as same size as larger icons.

https://convertio.co/png-ico/ converted icons are reasonable size comparable to default Vercel icon size. I think I will go with this option.

Even with changing shree icon to icon.ico and then restarting nextjs server ('npm run dev'), the app was not showing it (and showing older icon which had been deleted from app folder). Deleting .next folder and restarting nextjs server fixed the issue.

Made the version number 1.4.1 in About page and README.

========

7 Sep. 2024
On my Android Samsung M21 phone, when I would click on the gita-rsi.vercel.app link on WhatsApp, it would show a blurred Vercel icon for a few seconds before the home page was shown. Note that I have a loading spinner for the home page. I faced this issue only on my mobile phone but not on desktop. As I could not figure out what the issue was, I created favicon.ico as a copy of icon.ico, wondering whether that would fix it and pushed it to the main repo branch (for deployment on Vercel) [Commit 795dd48]. It did not. But later some message I saw on the phone as I was trying this out, led me to check whether an earlier version of the app had been 'installed' on the phone. It was and it had the Vercel icon as its icon. Uninstalling the app and then reinstalling the newer version app fixed the issue with the new 'Shree' icon being shown in Settings -> Apps, as well as in the first few seconds the app is loaded on mobile when the url is clicked on WhatsApp.


Comments