I have a commponent where I use the new React.createRef() api, how to test document.activeElement should be equal current ref commponent.
component :
export class Automatic extends Component {
componentDidMount = () => this.focusContainer()
componentDidUpdate = () => this.focusContainer()
container = React.createRef()
focusContainer = () => this.container.current.focus()
render = () => {
return (
<div
name='automatic'
onKeyPress={this.captureInput}
onBlur={() => setTimeout(() => this.focusContainer(), 0)}
ref={this.container}
tabIndex={0}
>
...
</div>
}
old testing (works):
it('should focus container on mount', () => {
automatic = mount(<Automatic classes={{}} />, mountContext)
document.activeElement.should.be.equal(automatic.ref('container'))
})
new one (doesn't work):
it.only('should focus container on mount', () => {
const container = React.createRef()
automatic = mount(<Automatic classes={{}} />, mountContext)
document.activeElement.should.be.equal(automatic.ref(container.current))
})
Updated with working examples. Added a styled-components example.
Here's how I solved it with Jest (uses different assertions, but concept is the same):
// setup
const MyComponent = React.forwardRef((props, ref) => (
<div>
<span ref={ref}>some element</span>
</div>
))
// test
it('should contain the forwarded ref in the child span', () => {
const ref = React.createRef()
const component = mount(
<Fragment>
<MyComponent ref={ref} />
</Fragment>,
)
expect(component.find('span').instance()).toEqual(ref.current)
})
The idea is to get the instance of the element that has the ref.
It seems to only work when wrapping MyComponent in another element, I used Fragment.
I ran into some trouble when using **Styled-Components. This is because it creates a number of extra elements. Try debugging with console.log(component.debug()). It will show you what enzyme renders.
When debugging you'll see that Styled-Components uses the recommended way to forward props.
You can find the right element using the property selector for forwardedRef:
// setup
const El = styled.div`
color: red;
`
El.displayName = 'El'
const MyComponentWithStyledChild = React.forwardRef((props, ref) => (
<El ref={ref}>some element</El>
))
// test
it('should contain the forwarded ref in a rendered styled-component', () => {
const ref = React.createRef()
const component = mount(
<Fragment>
<MyComponentWithStyledChild ref={ref} />
</Fragment>,
)
// Styled-components sets prop `forwardedRef`
const target = component
.find('[forwardedRef]')
.childAt(0)
.instance()
expect(target).toEqual(ref.current)
})
You can use the same trick if you created a Higher order Component (HoC) where you need to pass ref
it('should focus container on mount', () => {
automatic = mount(<Automatic classes={{}} />, mountContext)
document.activeElement.should.be.equal(automatic.instance().container.current)
})
Related
I want to pass ref to a child component with forwardRef, but current for the given ref is always null. Why?
Consider this simple example:
// Details.jsx
import { useRef, forwardRef } from 'react';
const Details = () => {
const usernameRef = useRef(null);
const InputClipboardButton = React.forwardRef((props, ref) => (
<ClipboardButton targetInputRef={ref} />
));
return (
<div>
<input type="text" ref={usernameRef} />
<InputClipboardButton ref={usernameRef} />
</div>
);
};
// ClipboardButton.jsx
const ClipboardButton = ({ targetInputRef }) => {
const copyToClipboard = () => {
console.log(targetInputRef);
}
<button onClick={copyToClipboard}>
Copy
</button>
};
When using forwardRef you must be mindful of the order of the parameters passed (props and ref):
You can define the component like so:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
Note how forwardRef takes two parameters:
The props
The ref being forwarded
You may also destructure the props value, as well as call the ref property a different name:
const FancyButton = React.forwardRef(({
btnType,
children
}, forwardedRef) => (
<button ref={forwardedRef} type={btnType} className="FancyButton">
{children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref} btnType="button">Click me!</FancyButton>;
Example adapted from React Docs
Clipboard Button is a component, so you must use forwardRef in this component as well
const ClipboardButton = forwardRef((props,ref) => {
const copyToClipboard = () => {
console.log(ref);
}
<button onClick={copyToClipboard}>
Copy
</button>
});
I need to access the ref to a textarea inside a component. Within the component, its easy enough:
const MyComponent = () => {
const inputRef = useRef();
return <textarea ref={inputRef} />
}
Now the ref is available within MyComponent and I can use it for some internal logic.
There are cases where I need to access the ref from the parent component as well. In that case, I can use forwardRef:
const MyComponent = React.forwardRef((props, ref) => {
return <textarea ref={ref} />
})
// In some parent
const MyParent = () => {
const inputRefFromParent = useRef();
return <MyComponent ref={inputRefFromParent} />
}
Now I can access to ref of the textarea from the parent component, and use it for logic within the parent component.
I find myself in a situation where I need to do some internal logic with the ref within MyComponent, but I may also need to get that ref from MyParent. How can I do this?
You can keep a ref in the MyComponent and expose what you would need in the parent component using useImperativeHandle hook using the ref passed from the MyParent.
Try like below. It exposes the focus method in the textarea to parent. And you can do any other internal things with the access to textAreaRef.
import { useRef, forwardRef, useImperativeHandle } from "react";
const MyComponent = forwardRef((props, ref) => {
const textAreaRef = useRef();
// all the functions or values you can expose here
useImperativeHandle(ref, () => ({
focus: () => {
textAreaRef.current.focus();
}
}));
const internalFunction = () => {
// access textAreaRef
};
return <textarea ref={textAreaRef} />;
});
// In some parent
const MyParent = () => {
const inputRefFromParent = useRef();
// you can call inputRefFromParent.current.focus(); in this compoenent
return <MyComponent ref={inputRefFromParent} />;
};
In addition to Amila's answer, I found another way to do it, by using a ref callback:
const MyComponent = React.forwardRef((props, parentRef) => {
const localRef = useRef();
return <textarea ref={ref => {
parentRef.current = ref;
localRef.current = ref;
}} />
})
So the callback ref keeps finer grain control of the ref to the textarea, and simply assigns its value to both the local ref and the parent ref.
You could do also the following:
const MyComponent = React.forwardRef((props, externalRef) => {
const internalRef = useRef<HTMLElement>();
const ref = useMemo(
() => externalRef || internalRef,
[externalRef, internalRef]
) as React.MutableRefObject<HTMLElement>;
return <textarea ref={ref} />
})
I would like to create the exact same behaviour in function component ref as there is in class component ref.
Heres an example:
const AnotherComponent = () => {
const DoSomething () => console.log(something);
return <div> There is a content </div>;
}
const MyComponent () => {
let newRef = useRef();
useEffect(() => {
newRef.current.DoSomething();
}, []);
return <AnotherComponent ref={newRef} />;
}
I know about forwardRef, but from what I understand it allows you to bind the ref to a specific input to get its events, but not to the WHOLE component with all its functions.
Is it even possible to achieve this with function components ?
Yes, it's completely possible. React refs are for more than just getting access to DOMNodes. Use a combination of React.forwardRef and the useImperativeHandle React hook to forward a React ref to a function component and expose out functions/values.
const AnotherComponent = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
doSomething
}));
const doSomething () => console.log(something);
return <div> There is a content </div>;
});
...
const MyComponent () => {
const newRef = useRef();
useEffect(() => {
newRef.current.doSomething();
}, []);
return <AnotherComponent ref={newRef} />;
}
Forwarding refs
forwardRef
useImperativeHandle
I've found myself needing to retrieve the element ref for every parent component that my hook, useExample, is used in. However, I'm stumped as to how I might be able to retrieve something like this or how to even check if there is an element to target?
Usually I would just do something a little "hacky" in a functional component like so:
const Example = WrappedComponent => {
const ref = createRef();
return <WrappedComponent ref={ref} />;
};
However, due to it being a hook and returning information and not a component, I can't target any component, and thus I'm very stumped.
My current code:
const useExample = () => {
const [stateValue, setStateValue] = useState("example");
useEffect(() => {
// Run some code...
}, []);
return stateValue;
};
const Component = () => {
const data = useExample();
return (
<div> /* <--- How do I gain access to this element */
<span>{ data }</span>
</div>
);
};
I could probably pass a created ref which has been attached to the parent div as a parameter to useExample, however this feels cheap and hacky, and I feel there should be a much easier solution.
In the ideal world something like this would be amazing:
const ref = React.getParentRef();
Apologies if there is an obvious answer in the documentation, I'm very new to React and am unsure of the correct question to be asking or what to be looking for in order to find it in the docs.
You can return the ref from the hook
const useExample = () => {
const myRef = React.useRef(null)
const [stateValue, setStateValue] = useState("example");
useEffect(() => {
// Run some code...
}, []);
return [myRef , stateValue];
};
const Component = () => {
const [myRef , data] = useExample();
return (
<div ref={myRef}> /* <--- How do I gain access to this element */
<span>{ data }</span>
</div>
);
};
If data can be a component:
const useExample = () => {
const myRef = React.useRef(null);
const [stateValue, setStateValue] = React.useState("example");
React.useEffect(() => {
const parent = myRef?.current?.parentNode;
console.log(parent);
}, []);
return <div ref={myRef}>{stateValue}</div>;
};
const Component = () => {
const data = useExample();
return (
<div>
<span>{data}</span>
</div>
);
};
export default function App() {
return <Component />;
}
But then you have to access the parent node from the ref, I believe this may cause problems as a component is being returned, and its anti pattern
I have two React components, Parent and Child. Both must be function components. I am trying to change the state of Child from Parent. I believe the best way to do this is using refs, but I haven't been able to get it to work.
I've tried creating a ref in Parent and passing it down to child, but this causes an error. I considered forwardRef() but I'm not sure that will work either.
const Parent = () => {
const ref = React.useRef();
const closeChild = () => {
ref.current.setOpen(false);
};
return (
<div>
<Child ref={ref} onClick={closeChild} />
</div>
);
};
const Child = () => {
const [open, setOpen] = useState(false);
return (
<div>
{open ? <p>open</p> : <p>closed</p>}
</div>
);
};
The code as it is now produces this error message:
react-dom.development.js:506 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?`
Only stateful React components can expose the ref automatically. If using functional component, I think you'll need to use forwardRef for the child component:
e.g.
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
From the docs,
Refs provide a way to access DOM nodes or React elements created in the render method.
refs are not meant for changing the state.
You actually need useState in parent component, and later you can manage it from child component.
const Parent = () => {
const [open, setOpen] = useState(true);
const toggleChild = () => {
setOpen(!open)
};
return (
<div>
<Child onClick={toggleChild} open={open}/>
</div>
);
};
const Child = (props) => {
return (
<div>
{props.open ? <p onClick={props.onClick}>open</p> : <p onClick={props.onClick}>closed</p>}
</div>
);
}
Demo
I don't think you need refs here, you should really try to avoid them.
avoid using refs for anything that can be done declaratively. They are useful for:
1.Managing focus, text selection, or media playback.
2.Triggering imperative animations.
3.Integrating with third-party DOM libraries.
https://reactjs.org/docs/refs-and-the-dom.html
why not just send the value into the child through props?
const Parent = () => {
const [open] = useState(false);
const toggleChild = () => {
this.setState(prevState => {open: !prevState.open});
};
return (
<div>
<Child onClick={toggleChild} open={this.state.open}/>
</div>
);
};
const Child = (props) => {
return (
<div>
{props.open ? <p>open</p> : <p>closed</p>}
</div>
);
};
EDIT: forgot you were using hooks. This is the way then
const [open, setOpen] = useState(false);
const toggleChild = () => {
setOpen(!open);
};
return (
<div>
<Child onClick={toggleChild} open={open}/>
</div>
);
EDIT 2: #ravibagul91 pointed out that you need to put the onClicks in the children <p> elements as well, look at his answer