Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desktop: Fixes #10060: Fix "New note" button rendering when startup with Trash can selected. #10076

Merged
merged 5 commits into from Mar 15, 2024

Conversation

khuongduy354
Copy link
Contributor

Summary

Previously, New note button ref is stored by useRef, which doesn't trigger rerender when ref change. This cause if the Trash can is selected in previous session, when reopen the app, the ref to New note is null by default, preventing the New note button to re-render into correct format.
More details and how to reproduce bugs: #10060.

Solution notes

  • Changed useRef to useState instead for storing New note button element.
  • I think panel width unchanged should not prevent re-render. There might be cases where element change needs re-render but panel width doesn't change (toggle button visibility for example).
    So i remove this line
    image

Testing

  1. Open Joplin, create an empty notebook in root
  2. Click on Trash can (make sure the New note button is hidden, Trash can is collapsed)
  3. Shrink the panel with search bar as small as possible.
  4. Close & reopen Joplin
  5. Click on the notebook in step 1, New note button should appear in correct format for small screen (which is an icon).

Previously, add note button is stored in useRef, which doesn't trigger rerender when ref change, change to useState instead.
Copy link
Contributor

github-actions bot commented Mar 7, 2024

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@khuongduy354
Copy link
Contributor Author

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Mar 7, 2024
@khuongduy354 khuongduy354 marked this pull request as ready for review March 7, 2024 12:56
@khuongduy354
Copy link
Contributor Author

I forgot to mention, I'm participating GSoC 2024. This is my introduction: https://discourse.joplinapp.org/t/introduction-duy-huynh/36448

Comment on lines 211 to 214
<StyledButton
ref={(el: Element) => {
props.setNewNoteButtonRef(el);
}}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't know why it works but this is not the right pattern to set references, so I'd rather not go with this change.

You also changed the logic to update the width or not, so maybe you fixed it this way and this change to the ref doesn't do anything

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for your feedback. I read from here: render when ref change. It's a pattern here callback ref.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref doesnt re render immediately as State, the ref inside useEffect hang on old value (I tested this, and this is how React intended useRef behavior).

To sum up: I want to use State instead of Ref, maybe my implementation is weird, I'd appreciate if you guide me some part of it, thank you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can prove that changing width logic doesn't fix the problem, but it's the state vs ref that fixes.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it works now I think it's only by chance, as a side effect of forcing a rendering when the state is updated.

It would be better to fully understand why it doesn't render. Usually it means that the effect is not taking into account a particular state variable. For example before it wasn't taking into account the width of the note list so that's why I've added this widthHasChanged check.

So I think the ref is fine but we need to find what part of the application state is not being taken into account.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useRef version

This is total old version, I just log newNoteRef. You see, it hang on to old value.

joplinrefpoc1.mp4

And this is from React useRef() docs
image

My understanding: when switch to another tab, showNewNoteButton triggers useEffect, but newNoteRef stills hang on previous value. When newNoteRef change to new value, it doesn't trigger useEffect for re-rendering (because of useRef behavior from React docs above).

useState version

This is my useState version, every click to tab, run the useEffect twice.

2024-03-11.10-44-39.mp4

My understanding: when switch to another tab, showNewNoteButton triggers useEffect once, but newNoteRef stills hang on previous value (1st print is oldState). When newNoteRef change to new value, it triggers useEffect again (2nd print is newState). This keep the newNoteRef in sync with the updating breakpoints.

Also, the whole thing I do just switching tabs, no change width is involved.

const [dynamicBreakpoints, setDynamicBreakpoints] = useState<Breakpoints>({ Sm: BaseBreakpoint.Sm, Md: BaseBreakpoint.Md, Lg: BaseBreakpoint.Lg, Xl: BaseBreakpoint.Xl });
const previousWidth = usePrevious(width);
const widthHasChanged = width !== previousWidth;
const showNewNoteButton = selectedFolderId !== getTrashFolderId();

// Initialize language-specific breakpoints
useEffect(() => {
if (!widthHasChanged) return;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking this check is correct and should not be removed. If the width hasn't changed, there's no reason to recalculate the breakpoints. But as mentioned maybe there's something else missing here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case when width hasn't changed but need breakpoint calculations: when startup with Trash can selected (new note button hidden), then click on a note (new note button shown). No panel width changed but need to calculate breakpoint, or else the button will be big and overflow panel

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(new note button hidden), then click on a note (new note button shown).

So if this effect has a dependency on the button being hidden or shown, can this info be passed to it? As a result the effect should trigger again whenever the button visibility changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the showNewNoteButton is a useEffect dependency already in previous versions. Yet, the problem is still useRef above, if useRef is null, even effect trigger, it cant update.

Copy link
Owner

@laurent22 laurent22 Mar 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, fair enough, I feel there's something off with the implementation but the existing code wasn't great either so that might be related.

At least would you mind renaming your variables correctly please? You call them "ref" which implies they are React references but it's not what they are, they are HTML elements (you got the types right though). So please rename it to "newNoteButtonElement" for example.

@laurent22
Copy link
Owner

Looks good now, thanks @khuongduy354 for looking into this!

@laurent22 laurent22 merged commit 310a907 into laurent22:dev Mar 15, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants