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

Refactor: Add data component for editor initialization to replace __unstableInitialize action. (fixes #15403) #15444

Merged
merged 17 commits into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/edit-post/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Master

### Refactor

- Create EditorInitializer component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. ([#15444](https://github.com/WordPress/gutenberg/pull/15444))

## 3.4.0 (2019-05-21)

### New Feature
Expand All @@ -8,7 +14,6 @@

- convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740)


## 3.2.0 (2019-03-06)

### Polish
Expand Down
37 changes: 37 additions & 0 deletions packages/edit-post/src/components/editor-initialization/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';

/**
* Internal dependencies
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
*/
import {
useAdjustSidebarListener,
useBlockSelectionListener,
useUpdatePostLinkListener,
} from './listener-hooks';

/**
* Data component used for initializing the editor and re-initializes
* when postId changes or on unmount.
*
* @param {number} postId The id of the post.
* @return {null} This is a data component so does not render any ui.
*/
export default function( { postId } ) {
useAdjustSidebarListener( postId );
useBlockSelectionListener( postId );
useUpdatePostLinkListener( postId );
const { triggerGuide } = useDispatch( 'core/nux' );
useEffect( () => {
triggerGuide( [
'core/editor.inserter',
'core/editor.settings',
'core/editor.preview',
'core/editor.publish',
] );
}, [ triggerGuide ] );
return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect, useRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import {
STORE_KEY,
VIEW_AS_LINK_SELECTOR,
VIEW_AS_PREVIEW_LINK_SELECTOR,
} from '../../store/constants';

/**
* This listener hook monitors for block selection and triggers the appropriate
* sidebar state.
*
* @param {number} postId The current post id.
*/
export const useBlockSelectionListener = ( postId ) => {
const {
hasBlockSelection,
isEditorSidebarOpened,
} = useSelect(
( select ) => ( {
hasBlockSelection: !! select(
'core/block-editor'
).getBlockSelectionStart(),
isEditorSidebarOpened: select( STORE_KEY ).isEditorSidebarOpened(),
} ),
[ postId ]
);

const { openGeneralSidebar } = useDispatch( STORE_KEY );

useEffect( () => {
if ( ! isEditorSidebarOpened ) {
return;
}
if ( hasBlockSelection ) {
openGeneralSidebar( 'edit-post/block' );
} else {
openGeneralSidebar( 'edit-post/document' );
}
}, [ hasBlockSelection, isEditorSidebarOpened ] );
};

/**
* This listener hook is used to monitor viewport size and adjust the sidebar
* accordingly.
*
* @param {number} postId The current post id.
*/
export const useAdjustSidebarListener = ( postId ) => {
const { isSmall, sidebarToReOpenOnExpand } = useSelect(
( select ) => ( {
isSmall: select( 'core/viewport' ).isViewportMatch( '< medium' ),
sidebarToReOpenOnExpand: select( STORE_KEY ).getActiveGeneralSidebarName(),
} ),
[ postId ]
);

const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( STORE_KEY );

const previousOpenedSidebar = useRef( '' );
Copy link
Member

Choose a reason for hiding this comment

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

I might think null serves better as representing an unset value (this is also what was used previously). Unless for some reason null can't be used as the argument of useRef?

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 used empty string because of a habit I have of using the same type for empty values (previousOpenedSidebar.current would have a string value). I think it's a good signal of what type to expect?

Copy link
Member

Choose a reason for hiding this comment

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

I used empty string because of a habit I have of using the same type for empty values (previousOpenedSidebar.current would have a string value). I think it's a good signal of what type to expect?

Hm, to me, we're representing an explicitly empty value, which is the purpose that a null type serves (reference), and isn't really a semantic guarantee of an empty string.

Copy link
Contributor Author

@nerrad nerrad Jun 26, 2019

Choose a reason for hiding this comment

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

The other option we may have here is to not set any value (just initialize with useRef())?

Copy link
Contributor Author

@nerrad nerrad Jun 26, 2019

Choose a reason for hiding this comment

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

Although that may mean previousOpenedSidebar.current will be undefined (I'd have to check).

Edit: just confirmed useRef() results in the expected { current: undefined } value.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'd still think null would be preferred over undefined, as the distinction between the two is in intentionality / explicitness of emptiness (vs. merely "unset").

Copy link
Contributor Author

@nerrad nerrad Jun 26, 2019

Choose a reason for hiding this comment

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

However, in your reference is stated:

represents the intentional absence of any object value

And the accompanying example illustrates where null is used to reference an empty value for something that would return an object otherwise. That's typically how I use null as the initial value for a variable, when the type returned would be an object as opposed to a primitive. Of course this isn't a hard and fast "rule" because context is important (i.e. it may not always be good to initialize value that represents a number primitive with 0) but in general I find value when reading code with initialized values that communicate expected type.

Copy link
Member

Choose a reason for hiding this comment

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

when the type returned would be an object as opposed to a primitive

I'm not sure I follow. You're suggesting to distinguish it based on whether it's typeof 'object' vs. some other primitive type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. In this case previousOpenedSidebar.current will always return a string. So it's empty representation would be an empty string (keeping type equality). If previousOpenedSidebar.current is expected to return an object, then it's empty representation would be null (which seems to be the use-case for how MDN describes using null).

On line 75 I also followed the same line of thought by resetting previousOpenedSidebar.current to an empty string:

I think null would be also be an appropriate initial value to use in the case where a value may represent any number of data types.


useEffect( () => {
if ( isSmall && sidebarToReOpenOnExpand ) {
previousOpenedSidebar.current = sidebarToReOpenOnExpand;
closeGeneralSidebar();
} else if ( ! isSmall && previousOpenedSidebar.current ) {
openGeneralSidebar( previousOpenedSidebar.current );
previousOpenedSidebar.current = '';
}
}, [ isSmall, sidebarToReOpenOnExpand ] );
};

/**
* This listener hook monitors any change in permalink and updates the view
* post link in the admin bar.
*
* @param {number} postId
*/
export const useUpdatePostLinkListener = ( postId ) => {
const { newPermalink } = useSelect(
( select ) => ( {
newPermalink: select( 'core/editor' ).getCurrentPost().link,
} ),
[ postId ]
);
const nodeToUpdate = useRef();

useEffect( () => {
nodeToUpdate.current = document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) ||
document.querySelector( VIEW_AS_LINK_SELECTOR );
}, [ postId ] );

useEffect( () => {
if ( ! newPermalink || ! nodeToUpdate.current ) {
return;
}
nodeToUpdate.current.setAttribute( 'href', newPermalink );
}, [ newPermalink ] );
};
Loading