-
Notifications
You must be signed in to change notification settings - Fork 54
feat(Input): adding variation for clearable input #37
feat(Input): adding variation for clearable input #37
Conversation
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've left some comments on cleaning up the code for the current direction.
I would also like to discuss changing the course a little bit. It would be ideal if the user could just add a clearable
prop to the Input and get the behavior for free. In that case, it would achieved using the AutoControlledComponent for now. Feedback?
class InputExampleIconChangeShorthand extends React.Component< | ||
{}, | ||
{ icon: string; inputValue: 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.
No typings in examples, please. The public facing code is not typescript but JS. We want to advertise the most minimal syntax possible to users in order to focus on our features. We also don't want to assume they are using typescript since this is a small percentage of the community.
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
icon: 'search', | ||
inputValue: '', | ||
} | ||
} |
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.
Can be simplified to:
state = { value: '' }
For simplicity, I've also renamed the inputValue
key above to value
to reduce more boilerplate below.
I removed icon
as there is no need to persist this on state
since it can be computed inline in the render function.
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
icon: value ? 'close' : 'search', | ||
inputValue: 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.
Example refactor given the above comment:
handleChange = (e, { value }) => this.setState({ 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.
fixed
placeholder="Search..." | ||
value={inputValue} | ||
onChange={this.handleChange} | ||
onIconClick={e => this.onIconClick(e, icon)} |
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.
Avoid creating functions in the render method. This is an anti-pattern since it causes the prop to receive a new value on every render. This means you cannot compare the previous and current value of this prop for optimizations.
That said, I'm not sure we should have this prop since the user can pass an onClick
prop to the icon
shorthand prop:
<Input icon={{ name: 'search', onClick: this.handleIconClick }} />
Thoughts?
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.
Cannot think of a real example of the usage of the onIconClick handler right now. I believe that the callback can be added to the onClick property of an Icon and same thing should happen. I can remove the extra handler for now and add it further if needed.
public onIconClick = (e, value) => { | ||
if (value === 'close') { | ||
this.setState({ | ||
icon: 'search', |
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.
It is safe to not have icon here since setting value
to empty will result in a re-render and the correct icon will be derived from state.
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 introduced the clearable prop that will take care of it
|
||
return ( | ||
<Input | ||
icon={icon} |
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.
Per the above, you can this: icon={value ? 'close' : 'search'}
.
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.
change the approach
src/components/Input/Input.tsx
Outdated
...icon, | ||
...(icon.onClick && { tabIndex: '0' }), | ||
}), | ||
} |
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.
You can avoid the logic required to create these props by adding the tabIndex
in the overrideProps
function:
computeTabIndex = props => {
if (!_.isNil(props.tabIndex)) return props.tabIndex
if (props.onClick || props.onIconClick) return 0
}
Icon.create(icon, {
overrideProps: predefinedProps => ({
tabIndex: this.computeTabIndex(predefinedProps)
})
})
The predefinedProps
handed to the overrideProps function are the final computed props of the factory function. The factory has already taken into account all factors necessary to compute the final props.
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.
sounds like a good idea, thanks
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
da9d255
to
47dbe7c
Compare
Codecov Report
@@ Coverage Diff @@
## master #37 +/- ##
==========================================
- Coverage 88.39% 88.37% -0.02%
==========================================
Files 45 45
Lines 741 757 +16
Branches 97 109 +12
==========================================
+ Hits 655 669 +14
- Misses 83 85 +2
Partials 3 3
Continue to review full report at Codecov.
|
@levithomason I addressed the requested changes. Please have a look. |
src/components/Input/Input.tsx
Outdated
value: props.value || '', | ||
} | ||
|
||
this.handleChange = this.handleChange.bind(this) |
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.
Looks like useless line because handleChange
is an arrow function.
https://medium.com/@machnicki/handle-events-in-react-with-arrow-functions-ede88184bbb
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
src/components/Input/Input.tsx
Outdated
|
||
computeTabIndex = props => { | ||
if (!_.isNil(props.tabIndex)) return props.tabIndex | ||
if (props.onClick) return 0 |
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 that desctruction is preferred in this case.
src/components/Input/Input.tsx
Outdated
const inputValue = _.get(e, 'target.value') | ||
const { clearable } = this.props | ||
|
||
_.invoke(this.props, 'onChange', e, { ...this.props, inputValue }) |
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 that inputValue
should be just value
, too
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
public render() { | ||
return <Input icon="search" clearable placeholder="Search..." /> | ||
} | ||
} |
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 example can be a stateless component.
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
937af84
to
0e8584e
Compare
@@ -9,6 +9,11 @@ const Variations = () => ( | |||
description="An input can have an icon." | |||
examplePath="components/Input/Variations/InputExampleIcon" | |||
/> | |||
<ComponentExample | |||
title="Clearable icon" |
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.
"Clearable", titles should be the prop name when possible.
src/components/Input/Input.tsx
Outdated
this.state = { | ||
value: props.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.
Looks like we have a default prop here. Also, no need for a constructor:
state = { value: this.props.value }
static defaultProps = { value: '' }
This will ensure the default value is advertised.
src/components/Input/Input.tsx
Outdated
|
||
_.invoke(this.props, 'onChange', e, { ...this.props, value }) | ||
|
||
this.setState({ 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.
If the user passes a value
prop, their value will no longer be respected. Clear should only work if the user does not have a value
prop set.
You can paste this in an example to try it:
import React from 'react'
import { Input } from '@stardust-ui/react'
const InputExampleIconClearableShorthand = () => (
<Input
clearable
icon="search"
placeholder="Search..."
value='keep me'
/>
)
export default InputExampleIconClearableShorthand
The value of props should always be reflected in the DOM. This is idiomatic React. Just as when using an input
and passing a value
prop, the input does not update when typing in it unless you capture that event and update the prop also. User props always win.
The AutoControlledComponent will handle this logic for you if you want to use that for now. Otherwise, you will need to implement it yourself until we get the state manager for inputs. I would not advise doing that as there are many edge cases (see AutoControlledComponent).
@@ -9,6 +9,11 @@ const Variations = () => ( | |||
description="An input can have an icon." | |||
examplePath="components/Input/Variations/InputExampleIcon" | |||
/> | |||
<ComponentExample | |||
title="Clearable icon" | |||
description="An input can have a search icon that can change into clear button on typing." |
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 feature is only showing that an input can make its value clearable.
The use of the search icon is unrelated and should not be in the description. In fact, it may warrant a separate example showing that the clearable icon will override a normal icon when there is a value. The simplest example doesn't actually require an existing icon in the input, correct?
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'm trying to imagine the example where the input is clearable and doesn't have an icon. I might misunderstand the original ask...
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.
For example, I would expect as a user that I could do this:
<Input clearable />
And it would result in an input that can clear its value. As a user, I do not have to also supply an icon='search'
prop in order for the clearable feature work. I can supply one if I wish, and it will be replaced with an x
when there is an input value, but it is not required for the clearable
feature to work.
SUIR Loading Input
Here's a corresponding example from SUIR. An Input can be in the loading
state. The input can be defined with or without an icon
prop. Here is the same loading input, but shown with the icon defined:
<Input placeholder='With an icon' icon='search' loading />
<Input placeholder='Without an icon' loading />
Here I am toggling the loading
prop:
The clearable
prop would work similar. The use of the icon='search'
is not required for the clearable icon to show up when there is a 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.
Let's clean up docs a bit. Also, the user's value prop is not being respected. See comments.
0e8584e
to
92ad327
Compare
src/components/Input/Input.tsx
Outdated
|
||
_.invoke(this.props, 'onChange', e, { ...this.props, value }) | ||
|
||
!this.props.value && this.setState({ 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.
AutoControlledComponent
allows you to this.trySetState()
which will automatically handle the proper checks against props of the same name as the state keys you are trying to set.
src/components/Input/Input.tsx
Outdated
this.setState({ value: '' }) | ||
} else if (clearable && this.props.value && this.props.value.length !== 0) { | ||
this.setState({ value: this.props.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.
This can safely be replaced with:
if (clearable) this.trySetState({ value: '' })
This method handles these checks and more edge cases. Also, note that when using AutoControlledComponent you no longer access this.props
for the value, but this.state
only.
src/components/Input/Input.tsx
Outdated
|
||
getInitialAutoControlledState() { | ||
return { 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.
The state = {}
on line 96 seems to conflict with the getInitialAutoControlledState
here on 98. The result of getInitialAutoControlledState
is applied after the state member.
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 applying defaultValue
, the value is not respected on first render. As an example, I had updated the examples to use the below code to make it more clear that the input can be cleared:
<Input clearable defaultValue="Clear me" placeholder="Search..." />
This does not render an input with a value of Clear me
. The current PR seems to be overriding the AutoControlledComponent's handling of the default props.
…erty usage; keeping the value provided by the user for the input as the meaningful one
e14840d
to
97e22b1
Compare
Heads up, I merged master here to get latest CI speed up benefits. |
@@ -1,4 +1,4 @@ | |||
import React from 'react' | |||
import * as React from 'react' |
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.
No TS in examples please.
|
||
describe('Input', () => { | ||
isConformant(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.
💡 Tip
You can use git mv <old-path> <new-path>
to move files. This will ensure git can track changes between filenames instead of appearing as a file delete and and file create. It improves readability and also makes rebase/merge more robust.
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.
Changes look good here. Left a couple comments for consideration but not blocking merge. Any further updates we can make it separate PRs.
Input clear icon variation
Adding variation for clearing the input on click on Cancel icon
TODO
API Proposal
default:
typing text:
click on remove text: