-
Notifications
You must be signed in to change notification settings - Fork 87
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
Implement react-select for our select input components #515
Conversation
This is populated via eejs.data from the server.
This uses the react-select component
…r react-select component
0e1d753
to
8aa3f5d
Compare
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.
Overall everything looks exemplary! Good job!
I do have the following list of items:
- a few suggestions for minor improvements
- a couple of questions
- one potentially serious reservation
map = DEFAULT_MODEL_OPTIONS_MAP, | ||
) => { | ||
const MAP_FOR_MODEL = map[ modelName ] ? map[ modelName ] : false; | ||
const generatedOptions = entities && modelName && MAP_FOR_MODEL ? |
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.
Don't think we need && modelName
here since it is not actually used anywhere in this block of code other than the previous line.
If modelName
is not provided, then MAP_FOR_MODEL
will resolve to false, therefore checking for modelName
is a duplication of logic.
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.
ya agreed, I'll fix that.
label: addAllOptionLabel, | ||
value: OPTION_SELECT_ALL, | ||
} ); | ||
} |
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 think the previous ternary conditional should be an if else
and this if
conditional should be inside of that if
block.
Why?
Because as is, if the selected model has no entities returned for the current query, then this conditional will still prepend the options list with a "Select All" option (assuming a label is set), which imho does not make sense.
"Select All" ???
Select All of what?
All of nothing?
IMHO, if there are no entities, then this should just return a completely empty list.
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.
agreed. fixing.
PropTypes.object, | ||
PropTypes.array, | ||
] ), | ||
}; |
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.
this is awesome... but what about data
attributes???
how would one add something like data-some-info="123456"
to this input?
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.
The properties this comment is attached to are all props used by the react-select component. Our <ModelSelect />
component just passes through any set props specifically for the react-select component via the props.selectConfiguration
(which then gets added to the state
inModelSelect
). So, adding data-*
attributes (if necessary) would need to be implemented via the react-select
component. If we encounter a place where we absolutely need them, we can likely use the components prop to override one of the react select components to add the data attributes.
...selectConfiguration, | ||
...updated, | ||
}; | ||
} |
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.
According to the React documentation:
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
and this blog post from 4 days ago:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
derived state should be used sparingly
Our usage here, seems to fall under this use case:
If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
I think the information in that "You Probably Don't Need Derived State" post should be seriously considered and its alternate suggestions understood completely before moving forward with this implementation. I understand that that blog post did not exist when you first started developing this component so you can not be faulted in any way for using that approach (makes me almost think they wrote it just for you!).
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 originally intended to link to the "You probably don't need derived state" in my original description for this pull but linked to a different (but still related) post by mistake. For context, here's what I wrote in the description for the pull:
I think I may need to tweak how I'm using derivedStateFromProps in ModelSelect yet. Basically I should only be changing state if there is a change detected for the incoming props with the current state (see example here). I can probably utilize memoize a bit in the some of the helper functions as well. This can be something done after this pull is merged and we are using it in blocks (so we can measure improvements).
We talked about this in slack but to summarize:
- I'm aware of the potential issues.
- I'm not convinced (yet) that we're doing things wrong (or in a way that is problematic).
- I fully intend on investigating further to see if we can do what's needed without using
getDerivedStateFromProps
but think this is something that will be easier to investigate in actual implementations of this component. So I'd prefer to get this merged in then create another pull to test with and try different potential solutions.
selectConfiguration: { | ||
loadingMessage: () => __( 'Retrieving Datetimes.', 'event_espresso' ), | ||
noOptionsMessage: () => __( | ||
'There are no datetimes available to select from.', |
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.
imho something totally dead simple like: __( 'No Datetimes.', 'event_espresso' ),
will work better in more use cases because it will not expand the width of the input as much as this label.
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.
From my testing it doesn't expand the width of the input, but I like it more concise anyways so will fix.
export const getQueryString = ( queryData = {} ) => { | ||
queryData = { ...defaultQueryData.queryData, ...queryData }; | ||
return baseGetQueryString( queryData, whereConditions, mapOrderBy ); | ||
}; |
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.
missing newline at end of file
assets/src/data/model/event/index.js
Outdated
'ticket_start', | ||
'ticket_end', | ||
] ), | ||
order: PropTypes.oneOf( [ 'asc', 'desc' ] ), |
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.
same question as with the datetimes model... allow for caps, convert case, or use constants ?
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.
fixed
/** | ||
* Default attributes for this model | ||
* @type {{attributes: {limit: number, orderBy: string, order: string, | ||
* showExpired: boolean}}} |
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.
maybe this could be formatted better?
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.
ya will do. This was done automatically by PhpStorm but doesn't look like there is a way to fix the auto-formatting for this so manual fixes will be necessary.
assets/src/data/model/event/index.js
Outdated
/** | ||
* Default attributes for this model | ||
* @type {{attributes: {limit: number, orderBy: string, order: string, | ||
* showExpired: boolean}}} |
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.
better formatting ?
'&where[Datetime.DTT_EVT_end][]=' + | ||
moment().month( month ).endOf( 'month' ).local().format() ); | ||
} | ||
return where.join( '&' ); |
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.
If a component or block requires a where condition that is not here, then in your opinion should we:
- add the new condition and corresponding prop to this method and everywhere else it is required
- override this method (or another) and simply append the additional where condition to the query string
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.
One of the reasons I extracted these into their own files is so that they could be considered the "authority" for parsing where conditions for that model. So for now, the option would be "add the new condition and corresponding prop to this method and everywhere else it is required".
However, as we see patterns emerge, we may find that we can autogenerate some of these a bit (or at least start with a common base).
Problem this Pull Request solves
Right now in a few different places we are using
chosen
for a fancy select to help with auto-complete ux etc (Attendee Mover add-on, Barcode Scanner). Although chosen is okayish, there are some challenges bringing it into react for usage (encountered while working on the Barcode Scanner). Along with that, chosen has a number of accessibility issues to address. In some research, I came across react-select and although version2 is in beta it introduces some great flexibility and while still not fully accessible (nothing is really yet), it is somewhat accessible and they are working on improving that. Implementing this as a component for our select needs allows us flexibility in the various uses we may have for selects (single select, multi-select etc). Brent and I chatted about this in slack and he was in agreement with this as well.Along with the implementation of react-select, there's a number of other under the hood changes in this pull request.
Abstracting out things specific to model related data interaction into the
data/model
folder.Our existing selects had a lot of boilerplate code for building query's etc that will likely get used elsewhere beyond just selects. It is also more fitting to have it connected with the
data
business logic for our modules. So for the new selects, this has been abstracted more so that:Changing folder structure to more clearly reflect the component context.
Instead of
components/selection
, I've added the new selects incomponents/form/select
. This could likely even be further refined to becomponents/form/select/modelName
for the individual models when necessary.Building a base
ModelSelect
component with an accompanyingwithSelect
HOC wrapping it.The
ModelSelect
component provides all the logic that exposes thereact-select
component for use. Usage allows for indicating what model and modelentities are to be used for building the select as well as passing through thereact-select
component options to configure that component itself (via aselectConfiguration
prop).This then allows add-ons to utilize this as a base for their own composite components using this.
There's an example of a new
EventSelect
component that is composed of theModelSelect
. It becomes much easier to construct any selects that utilize data from a model endpoint.What remains to do?
ModelSelect
. I want to hold off doing this until its had a review (and merged into master).react-select
has a specific way it can be styled programatically so IF we decide to change the styles we'll want to investigate that.derivedStateFromProps
inModelSelect
yet. Basically I should only be changing state if there is a change detected for the incoming props with the current state (see example here). I can probably utilize memoize a bit in the some of the helper functions as well. This can be something done after this pull is merged and we are using it in blocks (so we can measure improvements).Important things to note for converting over older selects to this new component:
For the most part, the new
EventSelect
component can be used as a model (pun not intended) for how the conversion works. However it's important to note that the signature for the event callbacks are specific to react-select so you need to keep that in mind when handling the event in the top-level component (i.e.EventAttendees
). Namely:OnChange
callbackThis differs from the WP
<SelectControl>
in that instead of the signature recieving back the id that was selected, there are two arguments passed into callback with the following schema:{ value: 10, label: 'Some label' }
{ action: <string> }
and the values areselect-option
,deselect-option
,remove-value
,pop-value
,set-value
,clear
,create-option
How has this been tested
Checklist
esc_html__()
, see https://codex.wordpress.org/I18n_for_WordPress_Developers)