I would like to have a custom hook that would provide me with a ScrollView component and a function to imperatively control the scrolling of that given component.
I'm using a library called react-native-keyboard-aware-scroll-view, which allows me to catch the reference of the component trough a prop called innerRef. Through that reference I have access to a series of methods that I can use to manipulate the scrolling.
The only way that I found for that "control function" to have access to the reference while being outside the scope of the ScrollView component was to create that ref globally using React.createRef like below:
const ref = createRef();
const ScrollView = ({ children, ...props }) => {
return (
<KeyboardAwareScrollView
{...props}
innerRef={(inner) => {
ref.current = inner; //adding ref to scrollview
}}
>
{children}
</KeyboardAwareScrollView>
);
};
const useScrollTo = () => {
const scrollTo = (position) => {
if (ref.current && position) {
/* controlling scroll using ref */
ref.current.scrollTo({
x: 0,
y: position,
animate: true
});
}
};
return { ScrollView, scrollTo };
};
export default useScrollTo;
Even though const declarations at the global scope create globals that are not part of the global object it feels anti-pattern to me, so I'd like to know if there's a proper way for me to do this without the need of having a ref in the global scope like this. Is this even possible?
Here's an example of this hook being used to assist with form validation on a long form, pointing the user to the input that has failed to pass validation:
Thanks for the working example you provided, this is how I would manage the scenario: https://codesandbox.io/s/frosty-sea-83tvx6?file=/src/ScrollView.js
The custom hook now is a real hook, since it makes use of a react hook:
const useScrollTo = () => {
const ref = useRef();
const scrollTo = (position) => {
if (ref.current && position) {
ref.current.scrollTo({
x: 0,
y: position,
animate: true
});
}
};
return {
ref,
scrollTo,
ScrollView
};
};
The ref is created within the hook, retrieved by your App Component and passed down to your ScrollComponent:
function App() {
const { ref, scrollTo, ScrollView } = useScrollTo();
const [position, setPosition] = useState(0);
const [errorPresent, setErrorPresent] = useState(false);
const simulateValidation = () => {
scrollTo(position);
setErrorPresent(true);
};
return (
<ScrollView myRef={ref}>
...
...
Your ScrollView Component:
const ScrollView = ({ children, myRef, ...props }) => {
return (
<KeyboardAwareScrollView
{...props}
innerRef={(inner) => {
myRef.current = inner;
}}
>
{children}
</KeyboardAwareScrollView>
);
};
I did not use React.forwardRef wrapper for sake of simplicity, if you don't like the idea of having to name your refs, you can just wrap ScrollView in React.forwardRef() and pass the ref with <ScrollView ref={ref}>.
EDIT: I updated the example by returning the ScrollView Component from the hook, even though I personally don't like this practice, I understand that some people like to use it to avoid having to import repetitive modules in their Components.