-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
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
[InputBase] Convert to function component #15446
Conversation
@material-ui/core: parsed: -0.27% 😍, gzip: -0.25% 😍 Details of bundle changes.Comparing: b20013f...6c8e958
|
I am having an issue setting the initial focus of the input field, somehow the label overlaps with the input fields I know it is something to do with something like - import { setRef } from '../utils/reactHelpers';
+ import { useForkRef } from '../utils/reactHelpers'; Maybe I am wrong here, I am not sure. 🙍♂️ Also I tried replacing + const useStyles = makeStyles(theme => {}, { name: 'MuiInputBase' });
- export const styles = theme => {}
- export default withStyles(styles, { name: 'MuiInputBase' })(withFormControlContext(InputBase));
+ export default withFormControlContext(InputBase); But using Can you please point me in the right direction, @joshwooding |
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.
Nice start! Looks like you forgot to use .current
when accessing the stored ref,
We use instance
to refer to the actual instance (context: #15347 (comment))
The next step would be to swap the setRef implementation for the useForkRef implementation. 👍
using Sorry for asking again 😅@joshwooding In order for me to replace - const InputBase = React.forwardRef(function InputBase(props, ref) {
+ const InputBase = props => { // can be removed once we drop support for non ref forwarding class components
const handleOwnRef = React.useCallback(ref => {
inputRef.current = ReactDOM.findDOMNode(ref);
}, []);
const handleRef = useForkRef(innerRef, handleOwnRef); const handleRefInput = instance => {
const { inputProps } = props;
inputRef.current = instance;
warning(
!instance || instance instanceof HTMLInputElement || instance.focus,
[
'Material-UI: you have provided a `inputComponent` to the input component',
'that does not correctly handle the `inputRef` property.',
'Make sure the `inputRef` property is called with a HTMLInputElement.',
].join('\n'),
);
- let refProp;
- if (inputRefProp) {
- refProp = inputRefProp;
- } else if (inputProps && inputProps.ref) {
- refProp = inputProps.ref;
- }
- setRef(refProp, instance);
}; + return (
- <div className={className} onClick={handleClick} ref={innerRef} {...other}>
_ <div className={className} onClick={handleClick} ref={handleRef} {...other}> Or am I doing it absolutely wrong |
I don't know if this is the right approach to it or not but is this how it was suppose to be function onGetRef() {
let refProp;
if (inputRefProp) {
refProp = inputRefProp;
} else if (props.inputProps && props.inputProps.ref) {
refProp = props.inputProps.ref;
}
return refProp;
}
const handleOwnRef = onGetRef()
const handleRefIntermediary = useForkRef(inputRef, handleOwnRef);
const handleRef = useForkRef(ref, handleRefIntermediary); const handleRefInput = instance => {
- const { inputProps } = props;
inputRef.current = instance;
warning(
!instance || instance instanceof HTMLInputElement || instance.focus,
[
'Material-UI: you have provided a `inputComponent` to the input component',
'that does not correctly handle the `inputRef` property.',
'Make sure the `inputRef` property is called with a HTMLInputElement.',
].join('\n'),
);
- let refProp;
- if (inputRefProp) {
- refProp = inputRefProp;
- } else if (inputProps && inputProps.ref) {
- refProp = inputProps.ref;
- }
- setRef(refProp, instance);
}; Am I even looking in the right direction here? Can you kindly just point me to the right direction 😅 |
I would do: const handleInputRefWarning = instance => {
warning(
!instance || instance instanceof HTMLInputElement || instance.focus,
[
'Material-UI: you have provided a `inputComponent` to the input component',
'that does not correctly handle the `inputRef` property.',
'Make sure the `inputRef` property is called with a HTMLInputElement.',
].join('\n'),
);
};
const handleInputPropsRefProp = useForkRef(inputPropsProp.ref, handleInputRefWarning);
const handleInputRefProp = useForkRef(inputRefProp, handleInputPropsRefProp);
const handleRef = useForkRef(inputRef, handleInputRefProp); I don't see any benefit preferring inputRefProp over inputPropsProp.ref so we should probably just fork both. |
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.
Just a bit more polishing and then on to tests 👍
@joshwooding Can you kindly review again. I will update the API docs. Once you give me the go ahead. 😄 & then move on to the tests. |
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 good so far!
I am running into an issue where when I ran the script I have tried doing all 3 options
I get the same result .. This is the same issue as #12584 I can try running Details on OS: |
@adeelibr That happens to me too, when I |
Yes you are absolutely right 🎉 Since Thank you for the help @joshwooding 🙇 Should I move on to the tests now? 😅 |
No worries. Yep, tests now :P They will fail due to functional components not having instances. We try to test our components using their public API i.e. props so anything that tests internals should be changed (refs and state) to do so and we are moving away from shallow rendering so if you have a change it would be nice if you could change that too. |
I have been trying to wrap my head around using testing for hooks & I have been struggling with all day. I saw these files to get some context on how I could test a react hook based component But I think I got confused more 😿 I just wanted to verify that the approach I am doing is the right way to do it or not, can you kindly help (point me in some direction) @joshwooding
it('should reset the focused state', () => {
const handleBlur = spy();
const wrapper = mount(<InputBase muiFormControl={{ onBlur: handleBlur }} />);
// We simulate a focused input that is getting disabled.
setState(wrapper, {
focused: true,
});
wrapper.setProps({
disabled: true,
});
assert.strictEqual(wrapper.find('InputBase').instance().state.focused, false);
assert.strictEqual(handleBlur.callCount, 1);
});
it('should reset the focused state', () => {
const handleBlur = spy();
const wrapper = mount(<InputBase muiFormControl={{ onBlur: handleBlur }} />);
// We simulate a focused input that is getting disabled.'
const inputEl = wrapper.find('input');
inputEl.simulate('focus');
wrapper.setProps({ disabled: true });
assert.strictEqual(inputEl.is(':focus'), false);
assert.strictEqual(handleBlur.callCount, 1);
}); |
@adeelibr Looks good to me, I would add an assert that after you simulate focus that the input is actually focused. |
Okay, so on doing
Back to square one, 🖼 Let me try/finding another approach. 😨
Can you kindly tell as to what am I doing wrong here .. |
Everything related to setState or mocking of instance variables was only an escape hatch. You should try to test how this component would be used. For library components this should be done with setProps 90% of the time.
|
I thought this PR solved that issue for enzyme enzymejs/enzyme#1965 Thank you for pointing that out, I was scratching my head all day for this.
On it. 😄 |
I am sorry but testing hooks implementation with enzyme is so difficult. I wish we could use |
No worries, I've fixed a couple of tests for you. |
Thank you, there was a lot for me learn here. 🥇 💯 😄 |
assert.strictEqual(handleFilled.callCount, 2); | ||
const { wrapper } = setup(); | ||
wrapper.find('input').instance().value = 'stub'; | ||
assert.strictEqual(wrapper.find('input').instance().value, 'stub'); |
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 asserting what you did in the first line. Could you just dispatch a input change event?
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 not sure what benefit this test has realistically. Testing that checkDirty is called when uncontrolled and isn't called when controlled seems like a better test
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.
To be honest I don't even know why we checkDirty only once when uncontrolled. Not sure what the use case is. Maybe a better test could help here.
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 not sure why either.
Edit: I know now :P
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 the test 'should check that the component is uncontrolled' would it be okay if i did something like this would that be okay 💭
1- Pass in a default value to the component
2- Then use an onChange (spy) method for trying to change the input (which won't work)
3- Assert that default value remains intact & default value is the same as the input.instance().value
(absolutely not sure about this approach =P) 😅
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 have this test
it('should check that the component is uncontrolled', () => {
const { wrapper } = setup();
wrapper.setProps({ defaultValue: 'stub' });
const handleChange = spy();
wrapper.setProps({
onChange: handleChange
});
wrapper.update();
const event = {
target: {
value: 'foo'
}
};
wrapper.find('input').simulate('change', event);
assert.strictEqual(handleChange.callCount, 1);
const inputEl = wrapper.find('input');
console.log(inputEl.debug());
console.log(inputEl.props());
console.log(inputEl.html());
assert.strictEqual(wrapper.find('input').instance().value, 'stub'); // THIS FAILS 😞
});
Response for console.log(inputEl.debug());
<input aria-invalid={[undefined]} aria-describedby={[undefined]} autoComplete={[undefined]} autoFocus={[undefined]} className=""
+ defaultValue="stub"
disabled={[undefined]} id={[undefined]} name={[undefined]} onBlur={[Function: handleBlur]} onChange={[Function: handleChange]} onFocus={[Func
tion: handleFocus]} onKeyDown={[undefined]} onKeyUp={[undefined]} placeholder={[undefined]} readOnly={[undefined]} required={[undefined]} rows={[undefined]}
value={[undefined]} type="text" />
Response for console.log(inputEl.props())
{
'aria-invalid': undefined,
'aria-describedby': undefined,
autoComplete: undefined,
autoFocus: undefined,
className: '',
defaultValue: 'stub',
disabled: undefined,
id: undefined,
name: undefined,
onBlur: [Function: handleBlur],
onChange: [Function: handleChange],
onFocus: [Function: handleFocus],
onKeyDown: undefined,
onKeyUp: undefined,
placeholder: undefined,
readOnly: undefined,
required: undefined,
rows: undefined,
value: undefined,
type: 'text'
}
But the response for 'console.log(inputEl.html());` is 😭 😥
<input class="" type="text" value="hell">
Is my understanding of the testing wrong, shouldn't this be true assert.strictEqual(wrapper.find('input').instance().value, 'stub');
but it is false
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 some reason your defaultValue isn’t being set through setProps. I can have a look later but I don’t think you need wrapper.update there?
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 won't really test a lot since the value is set outside the InputBase and not in onChange. Your test would only check that defaultValue sets value and onChange is called. My guess why the current test doesn't work is due to jsdom and it's weird input value
updating.
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 am sorry, but I can't think of a way to test this case... 😞 😞
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 removed the check state tests. I don't think they provide any value, all we care is that the component works in an uncontrolled and controlled state, which the other tests already test.
Rebasing on the next branch […] |
3c34a8c
to
9aad0c4
Compare
9aad0c4
to
4a33463
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.
I have tried to push the changes one step further. I might have done too ambitious changes, let me know by reviewing the changes.
Related to #15231