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

NavigatorProvider: move all state management to one reducer #60190

Merged
merged 3 commits into from
Mar 27, 2024

Conversation

jsnajdr
Copy link
Member

@jsnajdr jsnajdr commented Mar 26, 2024

This PR refactors the routing logic in NavigatorProvider. It removes all the useRefs and useEffects, and moves all state management to a useReducer hook. There is now one reducer to rule them all: the list of routes in screens, the navigation history in locationHistory, and the currently matched route in matchedPath. They are all updated on action dispatches. The only job left to do for NavigationProvider is to create a very simple binding of the reducer state to a context value.

Makes the NavigatorProvider easier to understand and also to replace it in the future with some standardized router.

How to test: Everything should work the same way as before: try out various routing scenarios in Site Editor and other places.

@jsnajdr jsnajdr added [Type] Code Quality Issues or PRs that relate to code quality [Package] Components /packages/components labels Mar 26, 2024
@jsnajdr jsnajdr self-assigned this Mar 26, 2024
@jsnajdr jsnajdr requested a review from ajitbohra as a code owner March 26, 2024 09:31
Copy link

github-actions bot commented Mar 26, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@jsnajdr jsnajdr force-pushed the update/navigator-provider-state branch from 98769f7 to 8537f21 Compare March 26, 2024 09:39
Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

Left some questions, but nothing blocking - this looks amazing 👏

The new reducer version is more concise and readable and can help us make improvements and additions easier.

I've spent some extensive time testing both in the site editor and in storybook, and couldn't manage to break it.

Great work @jsnajdr 🚀

}

return state;
// Return early in case there is no change
Copy link
Member

Choose a reason for hiding this comment

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

Can we return even earlier in a default case when the action type was not one of the ones we care about? Or are we relying on the types to do their job to prevent that from happening?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is not a public reducer, it's not part of a larger tree and doesn't need to accept an action of any type. We have all the action dispatches under tight control.

If you dispatch some bogus action, the reducer will still behave correctly, the "early return" check will work.

: undefined;

// If the new match is the same as the previous match,
// return the previous one to keep immutability.
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adding and improving comments, they're really helpful 🙌

} from '../types';

type MatchedPath = ReturnType< typeof patternMatch >;
type ScreenAction = { type: string; screen: Screen };
type RouterAction =
Copy link
Member

Choose a reason for hiding this comment

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

I assume you don't expect these types to have any utility outside of this module, which is why you opted to keep them private?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the reducer is private to the component, it's all self-contained in this file.

state: Screen[] = [],
action: ScreenAction
): Screen[] {
type RouterState = {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I'd move this type up, next to the other types.

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved.

| { type: 'add' | 'remove'; screen: Screen }
| { type: 'goback' }
| { type: 'goto'; path: string; options?: NavigateOptions }
| { type: 'gotoparent'; options?: NavigateToParentOptions };

const MAX_HISTORY_LENGTH = 50;
Copy link
Member

Choose a reason for hiding this comment

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

I couldn't help but wonder what's the rationale behind keeping such a short history, and whether it would be costly to increase it. Could potentially turn useful as the router gets used more and more. Not something we should aim for in this PR of course.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is probably not needed. There are createMemoryHistory functions both in history and in @tanstack/react-router, and neither does bother to limit the history size. You can freely push new items to the array with no limit.

I'll do a followup that removes this. I also suspect that the replace action doesn't work very well.

dispatch( { type: 'add', screen } ),
removeScreen: ( screen: Screen ) =>
dispatch( { type: 'remove', screen } ),
} ),
[]
Copy link
Member

Choose a reason for hiding this comment

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

Should dispatch be a dependency of this useMemo()? In practice, it won't make a difference since dispatch is a stable reference and that useMemo() will be called just once, but still, could be useful to pass it to satisfy the linter.

Copy link
Member

Choose a reason for hiding this comment

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

The linter shouldn't complain here; it's aware that native setState and dispatch have stable references.

Comment on lines +175 to +195
// Compute the matchedPath
const currentPath =
locationHistory.length > 0
? locationHistory[ locationHistory.length - 1 ].path
: undefined;
matchedPath =
currentPath !== undefined
? patternMatch( currentPath, screens )
: undefined;

// If the new match is the same as the previous match,
// return the previous one to keep immutability.
if (
matchedPath &&
state.matchedPath &&
matchedPath.id === state.matchedPath.id &&
isShallowEqual( matchedPath.params, state.matchedPath.params )
) {
matchedPath = state.matchedPath;
}
Copy link
Member

Choose a reason for hiding this comment

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

Looks so much cleaner and readable like this 👏

@jsnajdr jsnajdr force-pushed the update/navigator-provider-state branch from c3f1bd5 to c187d7b Compare March 27, 2024 12:25
@jsnajdr jsnajdr enabled auto-merge (squash) March 27, 2024 12:26
@jsnajdr jsnajdr merged commit 4024041 into trunk Mar 27, 2024
57 checks passed
@jsnajdr jsnajdr deleted the update/navigator-provider-state branch March 27, 2024 12:59
@github-actions github-actions bot added this to the Gutenberg 18.1 milestone Mar 27, 2024
carstingaxion pushed a commit to carstingaxion/gutenberg that referenced this pull request Mar 27, 2024
…s#60190)

* NavigatorProvider: move all state management to one reducer

* Changelog update

* Move types together

Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
cbravobernal pushed a commit to garridinsi/gutenberg that referenced this pull request Apr 9, 2024
…s#60190)

* NavigatorProvider: move all state management to one reducer

* Changelog update

* Move types together

Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Components /packages/components [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants