-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Finish making InputControl et al. more controllable
#40568
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
Changes from 25 commits
f943b5b
e662bde
0077735
295035c
739372f
cd1b4df
9a3804f
4dc4674
302096d
f3c881f
19fd100
68a02ea
d6b43bb
b3e9d88
547a7c7
89affab
06d16fd
ce0ac42
3800d28
ff6e10a
e427a0e
38c0158
8930905
cb00749
fa2723d
085fd03
29634d7
2b14b0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,14 @@ function inputControlStateReducer( | |
| composedStateReducers: StateReducer | ||
| ): StateReducer { | ||
| return ( state, action ) => { | ||
| // Updates state and returns early when there's no action type. These | ||
| // are controlled updates and need no exposure to additional reducers. | ||
| if ( ! ( 'type' in action ) ) { | ||
| return { | ||
| ...state, | ||
| value: `${ action.value ?? '' }`, | ||
| }; | ||
| } | ||
| const nextState = { ...state }; | ||
|
|
||
| switch ( action.type ) { | ||
|
|
@@ -98,7 +106,7 @@ function inputControlStateReducer( | |
| case actions.RESET: | ||
| nextState.error = null; | ||
| nextState.isDirty = false; | ||
| nextState.value = action.payload.value || state.initialValue; | ||
| nextState.value = action.payload.value ?? state.initialValue; | ||
| break; | ||
|
|
||
| /** | ||
|
|
@@ -109,10 +117,6 @@ function inputControlStateReducer( | |
| break; | ||
| } | ||
|
|
||
| if ( action.payload.event ) { | ||
| nextState._event = action.payload.event; | ||
| } | ||
|
|
||
| /** | ||
| * Send the nextState + action to the composedReducers via | ||
| * this "bridge" mechanism. This allows external stateReducers | ||
|
|
@@ -135,12 +139,14 @@ function inputControlStateReducer( | |
| * @param stateReducer An external state reducer. | ||
| * @param initialState The initial state for the reducer. | ||
| * @param onChangeHandler A handler for the onChange event. | ||
| * @param isFocused Focus state. | ||
| * @return State, dispatch, and a collection of actions. | ||
| */ | ||
| export function useInputControlStateReducer( | ||
| stateReducer: StateReducer = initialStateReducer, | ||
| initialState: Partial< InputState > = initialInputControlState, | ||
| onChangeHandler: InputChangeCallback | ||
| onChangeHandler: InputChangeCallback, | ||
| isFocused: Boolean | ||
| ) { | ||
| const [ state, dispatch ] = useReducer< StateReducer >( | ||
| inputControlStateReducer( stateReducer ), | ||
|
|
@@ -151,15 +157,7 @@ export function useInputControlStateReducer( | |
| nextValue: actions.ChangeEventAction[ 'payload' ][ 'value' ], | ||
| event: actions.ChangeEventAction[ 'payload' ][ 'event' ] | ||
| ) => { | ||
| /** | ||
| * Persist allows for the (Synthetic) event to be used outside of | ||
| * this function call. | ||
| * https://reactjs.org/docs/events.html#event-pooling | ||
| */ | ||
| if ( event && event.persist ) { | ||
| event.persist(); | ||
| } | ||
|
|
||
| refEvent.current = event; | ||
| dispatch( { | ||
| type, | ||
| payload: { value: nextValue, event }, | ||
|
|
@@ -169,30 +167,25 @@ export function useInputControlStateReducer( | |
| const createKeyEvent = ( type: actions.KeyEventAction[ 'type' ] ) => ( | ||
| event: actions.KeyEventAction[ 'payload' ][ 'event' ] | ||
| ) => { | ||
| /** | ||
| * Persist allows for the (Synthetic) event to be used outside of | ||
| * this function call. | ||
| * https://reactjs.org/docs/events.html#event-pooling | ||
| */ | ||
| if ( event && event.persist ) { | ||
| event.persist(); | ||
| } | ||
|
|
||
| refEvent.current = event; | ||
| dispatch( { type, payload: { event } } ); | ||
| }; | ||
|
|
||
| const createDragEvent = ( type: actions.DragEventAction[ 'type' ] ) => ( | ||
| payload: actions.DragEventAction[ 'payload' ] | ||
| ) => { | ||
| refEvent.current = payload.event; | ||
| dispatch( { type, payload } ); | ||
| }; | ||
|
|
||
| /** | ||
| * Actions for the reducer | ||
| */ | ||
| const change = createChangeEvent( actions.CHANGE ); | ||
| const invalidate = ( error: unknown, event: SyntheticEvent ) => | ||
| const invalidate = ( error: unknown, event: SyntheticEvent ) => { | ||
| refEvent.current = event; | ||
| dispatch( { type: actions.INVALIDATE, payload: { error, event } } ); | ||
| }; | ||
| const reset = createChangeEvent( actions.RESET ); | ||
| const commit = createChangeEvent( actions.COMMIT ); | ||
|
|
||
|
|
@@ -206,31 +199,33 @@ export function useInputControlStateReducer( | |
|
|
||
| const currentState = useRef( state ); | ||
| const currentValueProp = useRef( initialState.value ); | ||
| const refEvent = useRef< SyntheticEvent | null >( null ); | ||
| useLayoutEffect( () => { | ||
| currentState.current = state; | ||
| currentValueProp.current = initialState.value; | ||
| } ); | ||
| useLayoutEffect( () => { | ||
| if ( ! refEvent.current ) return; | ||
|
|
||
| if ( | ||
| state.value !== currentValueProp.current && | ||
| ! currentState.current.isDirty | ||
| ) { | ||
| onChangeHandler( state.value ?? '', { | ||
| event: currentState.current._event as | ||
| event: refEvent.current as | ||
| | ChangeEvent< HTMLInputElement > | ||
| | PointerEvent< HTMLInputElement >, | ||
| } ); | ||
| } | ||
| }, [ state.value ] ); | ||
| refEvent.current = null; | ||
| }, [ state.value, state.isDragging, isFocused ] ); | ||
|
||
| useLayoutEffect( () => { | ||
| if ( | ||
| ! refEvent.current && | ||
| initialState.value !== currentState.current.value && | ||
| ! currentState.current.isDirty | ||
| ) { | ||
| reset( | ||
| initialState.value, | ||
| currentState.current._event as SyntheticEvent | ||
| ); | ||
| dispatch( { value: initialState.value } ); | ||
| } | ||
| }, [ initialState.value ] ); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When does this happen (action without type)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be in what I'm calling the intake effect
gutenberg/packages/components/src/input-control/reducer/reducer.ts
Lines 218 to 226 in 2b14b0f
That was dispatching via
resetin the base PR but in looking for a way to distinguish the update from props I landed on making the baredispatchwith no action type. I'd considered implementing another action (or bringing backupdatethat used to exist and was used only for this purpose (props coming in)) but I went with the baredispatchbecause I thought it would make for a little less changes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd have just used another action but it's not important :)