Attach a property to a functional component wrapped with React.forwardRef - reactjs

I have a component wrapped with React.forwardRef and it so happens that it is a compound component as well.
I'm not sure how do I maintain
Form.Item = FormItem;
while the Form component function being wrapped with forwardRef.
const Form = React.forwardRef((props, ref) => { return <form></form>});
Typescript gives me an error (rigthly) saying
Property 'Item' does not exist on type 'ForwardRefExoticComponent>'.ts(2339)

Just to make it more clear of the solution, as it's on an external site (Credits to original author here, and thank you #Antoan Elenkov)
export interface Props = {
...yourPropsHere;
};
export interface CompoundedComponent extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLInputElement>> {
yourStaticFunctionOrSomethingLikeThat: () => void;
}
const Component = React.forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref={ref} {...props} />
)) as CompoundedComponent;
Component.yourStaticFunctionOrSomethingLikeThat = () => {};

This solution from the same GH thread seems nicer to me:
const Form = React.forwardRef(function Form(...) {
...
})
const FormItem = React.forwardRef(...)
const FormNamespace = Object.assign(Form, {Item: FormItem})
export {FormNamespace as Form}

Related

Convert functional component from class component

I want to convert my class component to functional component and t is difficult for me to convert render() of class component.
Here is my code sandbox link. https://codesandbox.io/s/snowy-smoke-s65vx4?file=/src/App.js
Can anyone please help me with this
The render part is just going to be the body of the function and the return.
const myfunccomp : React.FC = ({id, info, msgList}) => {
const chatContentHeader = () => {...}
const chatContentBody = () => {...}
const chatContentFooter = () => {...}
...
return (
<div className="chat-content">
{chatContentHeader(info.name)}
{chatContentBody(msgList, id)}
{chatContentFooter()}
</div>
);
}

Passing object to child component loses its properties and methods

I have App.js that has a component in it where I pass down props:
let chess = new Chess();
// lots of code...
console.log("chess in App.js", chess);
return <ChildComponent chess={chess} />;
The ChildComponent.js receives this prop as follows:
const ChildComponent = (chess) => {
console.log("chess", chess);
};
When I inspect that in Chrome, I get this:
So, I am somehow losing the object detail when I pass it down as props. Is there something obvious that I am doing wrong?
It needs to be in a {} because it's inside a props object
const ChildComponent = ({ chess }) => {
console.log("chess", chess);
};
OR
const ChildComponent = (props) => {
console.log("chess", props.chess);
};
Your code, log the props object,
try this
const ChildComponent = (props) => {
console.log('chess', props.chess)
}
or
const ChildComponent = ({chess}) => {
console.log('chess', chess)
}

Property 'ref' does not exist on type 'IntrinsicAttributes'

I'm working on a React typescript project. The code works but ts compiler keeps complaining about my ref. Here is the code:
First I have a high order component to handle errors:
export class ErrorBoundary extends React.Component<ErrorBoundaryProps> {
constructor(props: ErrorBoundaryProps) {
super(props);
}
static getDerivedStateFromError(error: any) {
// some error handling related code
}
public componentDidCatch(error: any) {
// some error handling related code
}
public render() {
return this.props.children;
}
}
And a high order component:
export function withErrorBoundary<T = {}>(Component: React.ComponentType<T>): React.ComponentType<T> {
function Wrapped(props: T) {
return (
<ErrorBoundary>
<Component {...props} />
</ErrorBoundary>
);
}
return Wrapped;
}
This has worked well like this: const NewComponent = withErrorBoundary(MyComponent)
But now I'm building a new component that has the need of React.forwardRef:
interface ILabelProps {
value: string;
mark: boolean;
// other props with basic types
}
export const Label = React.forwardRef(
(props: ILabelProps, ref) => {
React.useImperativeHandle(ref, () => ({
// some custom logic
}), []);
return (
<>{...my UI}</>
);
});
export MyLabel = withErrorBoundary(Label);
However when I use it like this it errors out about the ref:
Property 'ref' does not exist on type 'IntrinsicAttributes & ILabelProps'
const App = () => {
const labelRef = React.useRef();
return <MyLabel {...props} ref={labelRef} />
}
However if I just use Label without the errorBoundary HOC, it stops complaining:
const App = () => {
const labelRef = React.useRef();
return <Label {...props} ref={labelRef} />
}
I think the issue might be related the use of React.forwardRef because this is the first time I used it and I've never had this kind of issue before. Any one knows how to fix this?
Thanks!
I don't see any error when replicate your code.
But it does have 1 error at runtime:
Warning: Function components cannot be given refs.
Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?
Basically, your Component MyLabel = withErrorBoundary(Label) is the new Component returned from withErrorBoundaray hence it's lost the forwardRef provided by Label.
To fix that you just need to pass labelRef to Label Component like below:
const MyLabel2 = withErrorBoundary(() => <Label {...props} ref={labelRef} />);
Codesandbox here.

React - transfer props to variable that's already a React element

Assume you have a code:
const method = Component => {
const someProps = { foo: 'bar' };
// Add those props to the Component and render it
};
And you use it like this:
method(<MyComponent />)
What should I put in method so I can pass the someProps further?
Class-based syntax
render() {
return <Component {...this.props}>;
}
Functional based syntax
const method = Component => {
const someProps = { foo: 'bar' };
return <Component {...someProps} />;
};
If you wish to find out more on this topic, it's aka HOC, higher order component
Unfortunately, this piece code will not return a component for rendering:
const method = Component => {
const someProps = { foo: 'bar' };
// Add those props to the Component and render it
};
You want your HOC method to be like this:
const method = Component => props => {
const someProps = { foo: 'bar' }
return <Component {...someProps} {...props} />
}
...someProps is the extra props that is 'injected' into Component through HOC. Usually this comes from some API calls inside HOC method.
While ...props is the 'normal' props that is passed down into Component when calling it.
To illustrate what I meant:
import FooComponent from './FooComponent'
// Using the HOC:
const FooComponentWithMethod = method(FooComponent)
// ...rest of code
render() {
return <FooComponent hello={'world'} />
}
// ...rest of code
When you console.log(this.props in FooComponent, you will see both
{
foo: 'bar', // injected by 'method' HOC
hello: 'world' // passed down from parent
}
Seems like the morning brought the answer:
To get this Component with new props, and to keep the old one as well:
const method = (Component) => {
const customProps = { foo: 'bar' };
const elemProps = Component.props;
const mergedProps = { ...customProps, ...elemProps };
const cloned = React.cloneElement(Component, mergedProps);
};

React - Forwarding multiple refs

I have a SideNav component that contains dynamically created links that need to scroll to a corresponding header in an adjacent html table (InfoTable). I've tried multiple different ways of accomplishing this to no avail.
export default class Parent extends Component {
state = {
categories: [],
}
scrollToHeader = (tableRefString) => {
// Function to pass to SideNav to access refs in InfoTable
this[tableRefString].scrollIntoView({ block: 'start' });
}
render() {
return (
<div>
<SideNav
categories={this.state.categories}
scrollToHeader={this.scrollToHeader} />
<InfoTable
categories={this.state.categories} />
</div>
);
}
}
export default class InfoTable extends Component {
render() {
return (
<div>
<table>
<tbody>
{this.props.categories.map(category => (
<>
// Forward the ref through InfoTableHeader to be set on the parent DOM node of each InfoTableHeader
<InfoTableHeader />
{category.inputs.map(input => <InfoTableRow />)}
</>
))}
</tbody>
</table>
</div>
);
}
}
In order to click a link on SideNav and scroll to the corresponding header on InfoTable, I believe that I need to forward refs that are dynamically created on Parent based on names in my categories array and set these refs to the DOM nodes for each header in InfoTable. From there I would pass a function to SideNav that could access the refs in Parent in order to scroll to the header.
How can I go about forwarding multiple refs at once to my InfoTable component?
Is there a cleaner way to accomplish what I'm trying to do? I've looked into React.findDOMNode() but refs seem to be the better option.
I know there is an already accepted answer, and while I find #nicholas-haley's solution acceptable.
I think a better way to go about it would be to use the built-in useImperativeHandle hook.
IMPORTANT: The React Hooks Api is available as of
react#16.8.0 and later
react-native#0.59.0 and later
The React hooks API Docs state:
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with `forwardRef
This note is followed by the following example:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
Thus, in my opinion, a much cleaner solution would be to delegate the needed refs through the useImperativeHandle hook.
This way there is no need for a special ref syntax, and the component can simply return a specific type of a FatherRef; Example:
// LabelInput.js
function LabelInput(props, ref) {
const labelRef = useRef();
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
get input() {
return inputRef.current;
},
get label() {
return labelRef.current;
},
// ... whatever else one may need
}));
return (
<div>
<label ref={labelRef} ... />
<input ref={inputRef} ... />;
</div>
)
}
LabelInput = forwardRef(LabelInput);
function MyScreen() {
const labelInputRef = useRef();
const onClick = useCallback(
() => {
// labelInputRef.current.focus(); // works
// labelInputRef.current.input.focus(); // works
// ... etc
},
[]
);
return (
...
<LabelInput ref={labelInputRef} ... />
....
)
}
I had a similar situation where I needed multiple refs to be forwarded to the child of my Parent component.
I still have not found an elegant solution, however you might try passing your refs as an object, and destructuring within the forwardRef callback:
// Parent
ref={{
ref1: this.ref1,
ref2: this.ref2
}}
// Child
export default React.forwardRef((props, ref) => {
const { ref1, ref2 } = ref;
return (
<Child1
{...props}
ref={ref1}
/>
<Child2
{...props}
ref={ref2}
/>
);
});
I'm not a big fan of the naming here (I'd prefer ref to be called refs), but this works in case you're in a pinch.
EDIT:
In 2020 I believe #samer-murad's answer is the best solution to this problem.
I actually just picked this up from react-hook-form, but they presented a nice way to share ref and assign multiple refs at once:
<input name="firstName" ref={(e) => {
register(e) // use ref for register function
firstNameRef.current = e // and you can still assign to ref
}} />
I still don't understand what goes on in that ref property, but the following React source code typeof type.render === 'function' + the other answers led me to try passing a function of hoisted refs from parent, and it works!
class Child extends React.Component {
render() {
return (
<div>
<div
ref={this.props.pa}
style={this.props.style}
onClick={async () => {
this.storeAuth(...this.props.storableAuth);
this.props.clearStore();
}}
/>
<div
ref={this.props.fwd}
style={this.props.style}
onClick={async () => {
this.props.onStart();
const res = await this.getUserInfo(verbose, auth, storedAuth);
if (res === "login?") this.props.onPromptToLogin();
if (res) this.props.onFinish(); //res.isAnonymous
}}
/>
</div>
);
}
}
export default React.forwardRef((props, getRefs) => {
const { pa, fwd } = props.getRefs();
return <Child fwd={fwd} pa={pa} {...props} />;
});
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.pa = React.createRef();
this.fwd = React.createRef();
}
render() {
return (
<Child
getRefs={() => {
return {
pa: this.pa,
fwd: this.fwd
};
}}
storableAuth={this.state.storableAuth}//[]
clearAuth={() => this.setState({ storableAuth: null })}
/>
);
}
}
This is not exactly what the author of this question asked, but this title could feet to this question as well: how do I allow developers using my React component to pass a ref if I pass ref internally in my component also (mean, passing multiple refs that will get a ref for this element),
this is the solution i came with:
import { useState, useRef } from "react";
export default function App() {
const [, render] = useState({});
const reRender = () => render({});
const divRef = useRef();
console.log("App", divRef);
return <Component reRender={reRender} passPropsToDiv={{ ref: divRef }} />;
}
const Component = ({ passPropsToDiv, reRender }) => {
const div1Ref = useRef();
const { ref: extraRef = null, ...passPropsToDivNoRef } = passPropsToDiv ?? {};
extraRef.current = div1Ref.current;
console.log("Component", div1Ref);
return (
<div className="App">
<div ref={div1Ref} {...passPropsToDivNoRef}>
i will have a ref
</div>
<button onClick={reRender}>reRender</button>
</div>
);
};
codesandbox: https://codesandbox.io/s/react-use-pass-multiple-refs-legm7p?file=/src/App.js

Resources