How to get react-pose working with class components? - javascript

I've followed the documentation and this blog post but I'm struggling to get anything to work.
Locally, I get the following error: HEY, LISTEN! No valid DOM ref found. If you're converting an existing component via posed(Component), you must ensure you're passing the ref to the host DOM node via the React.forwardRef function.
So I've attempted to forward the ref:
class ColorCheckbox extends Component {
setRef = ref => (this.ref = ref);
constructor(props) {
super(props);
}
render() {
const { key, children, color } = this.props;
return (
<button
ref={this.setRef}
key={key}
style={{
...style.box,
background: color,
}}
>
{children}
</button>
);
}
}
export default forwardRef((props, innerRef) => (
<ColorCheckbox ref={innerRef} {...props} />
));
Which is working as I'm able to console.log the ref inside my parent Component:
ColorCheckbox {props: Object, context: Object, refs: Object, updater: Object, setRef: function ()…}
"ref"
However, I still receive the message (above) of No valid DOM ref found....
Here's a simple Codesandbox describing my issue.
About the Codesandbox:
I am getting cross-origin errors in this Sandbox (they do not occur locally). If you change line 14 to be ColorCheckbox the cross-origin error goes...
Any ideas?

When you call forwardRef on a class based component and try to pass the ref through the ref attribute it will not work. The documentation example will only work for regular DOM elements. Rather try doing the following:
export default forwardRef((props, innerRef) => (
<ColorCheckbox forwardRef={innerRef} {...props} />
));
I've just used an arbitrary name, so in this case forwardRef, to pass the ref as a prop. In your class based component I've changed the part where the ref is set on the button to this:
const { key, children, selected, color, forwardRef } = this.props;
return (
<button
ref={forwardRef}
key={key}
style={{
...
The following approach, which they feature in their blog post, will only work for regular DOM elements and styled-components:
const MyComponent = forwardRef((props, ref) => (
<div ref={ref} {...props} />
));
Please refer to my Codesandbox fork to see a working example.

Related

TypeError: Cannot read properties of undefined (reading 'style')

Been stuck on debugging this for quite a while. I'm trying to have a group of items change onClick but with the use of transform but 'style' is undefined. I've also included the Card component functions. Help would be greatly appreciated
import React,{useRef} from 'react';
import { Card } from '../components';
import { CardItemContainer } from './card-item';
export function CardContainer()
{
const listRef=useRef()
const handleClick=(direction)=>
{
if(direction==="left")
{
listRef.current.style.transform=`translate(230)`
}
}
return(
<Card>
<Card.ListTitle> Continue to watch</Card.ListTitle>
<Card.Wrapper >
<Card.ArrowSliderLeft onClick={()=>handleClick('left')}/>
<Card.List ref={listRef}>
<CardItemContainer index={0}/>
<CardItemContainer index={1}/>
<CardItemContainer index={2}/>
<CardItemContainer index={3}/>
<CardItemContainer index={4}/>
<CardItemContainer index={5}/>
<CardItemContainer index={6}/>
</Card.List>
<Card.ArrowSliderRight onClick={() => handleClick("right")}/>
</Card.Wrapper>
</Card>
)
}
Card Components
import {ArrowBackIosOutlined,ArrowForwardIosOutlined} from "#material-ui/icons";
import React, {} from 'react';
import {
Container,
List,
ListTitle,
Wrapper,
ArrowSliderLeft,
ArrowSliderRight
} from './styles/card';
export default function Card({ children, ...restProps }) {
return <Container {...restProps}>{children}</Container>
}
Card.ListTitle=function CardListTitle({children,...restProps})
{
return <ListTitle{...restProps}> {children} </ListTitle>
}
Card.Wrapper=function CardWrapper({children,...restProps})
{
return <Wrapper{...restProps} > {children} </Wrapper>
}
Card.List=function CardList({children,...restProps})
{
return <List{...restProps} >{children}</List>
}
Card.ArrowSliderLeft = function HeaderArrowBackIosOutlinedSymbol({...restProps })
{
return <ArrowSliderLeft {...restProps }>
{/*id allows me to style the icon directly */}
<ArrowBackIosOutlined id="sliderLeft"/>
</ArrowSliderLeft>
}
Card.ArrowSliderRight = function HeaderArrowForwardIosOutlinedSymbol({...restProps}) {
return (
<ArrowSliderRight {...restProps}>
<ArrowForwardIosOutlined id="sliderRight"/>
</ArrowSliderRight>
);
};
Ignore:
Been stuck on debugging this for quite a while. I'm trying to have a group of items change onClick but with the use of transform but 'style' is undefined. I've also included the Card component functions. Help would be greatly appreciated
Function components like CardList don't have a ref property, only class components or DOM elements do.
You haven't posted List component's implementation, but let's assume it has a <ul> tag, and that is what you eventually need to manipulate its .style.transform
CardList >>> List >> ul (this is the element you need to pass the ref)
To pass the listRef all the way to ul from CardList you need to use the forwardRef technique.
Card.List=React.forwardRef(function CardList (props,ref)
{
const {children,...restProps} = props
return <List{...restProps} ref={ref} >{children}</List>
})
the List component itself :
const List = React.forwardRef(function (props,ref) {
return <ul ref={ref}>
... the implementation of your List
Now you can pass listRef in here and it goes down the chain:
<Card.List ref={listRef}>
Side Note: taking from Drew Reese's comment on this answer, since CardList is just transfering the same props from a parent component to List, you can simply assign List to Card.List, then only one step of ref forwarding would be enough:
Card.List = List // CardList component isn't used anymore.
The same thing could work for Card.ListTitle and Card.Wrapper:
Card.ListTitle=ListTitle
Card.Wrapper=Wrapper
I too have just faced this same issue, and have tried to get my code working again. Checking similarity between your given code and my erroneous code snippet helped me fix the error.
Strangely, I have faced this error with a JSX multi-line comment in place after my element (MUI <Snackbar> element, in my case).
Error(s):
My code snippet looked something like:
<Snackbar open={snackbarOpen} autoHideDuration={5000} onClose={()=>setSnackbar(false)} > {/* My Comment Here */}
<>...</>
</Snackbar>
Quite similar place of JSX comment as your Card Component
Card.ArrowSliderLeft = function
...
return <ArrowSliderLeft {...restProps }>
{/*id allows me to style the icon directly */}
<ArrowBackIosOutlined ... />
</ArrowSliderLeft>
Removing just the comment part {/* */} immediately following an opening tag worked for me.
So, try removing your JSX comment or placing it elsewhere,and see if it helps.
Sharing it here just for my and others future reference. :)

How to access ref that was set in render

Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />

Clone a React component that implements forwardRef

Lets say I have a base component that uses forwardRef like so:
const BaseMessage = React.forwardRef((props, ref) => (
<div ref={ref}>
{props.icon}
<h2>{props.title}</h2>
<p>{props.message}</p>
</div>
)
Now I want to create a second component, ErrorMessage that is essentially a copy of the BaseMessage but with a predefined value for props.icon, such that the icon prop is not needed to be set. Otherwise, its an exact copy of BaseMessage.
<ErrorMessage title="Oops!" message="Something went wrong when submitting the form. Please try again." />
I don't want to have to do this, since it feels weird to have two layers of forwardRef going on here:
const ErrorMessage = React.forwardRef(({icon, ...props}, ref) => (
<BaseMessage ref={ref} icon={<svg></svg>} {...props} />
))
Is there a way I can make a clone/copy of BaseMessage without having to reimplement forwardRef for ErrorMessage as well? I know there are utils out there like withProps from recompose but I'd like to avoid using a library if I can.
Try cloneElememt
React.cloneElement(BaseMessage, { icon: '' })

Using React.forwardRef inside render function directly

Is it safe to use React.forwardRef method directly inside render function of another component -
Example -
function Link() {
// --- SOME EXTENSIVE LOGIC AND PROPS CREATING GOES HERE ---
// --- OMITTED FOR SIMPLICITY ---
// TO DO: Remove forward ref as soon Next.js bug will be fixed -
// https://github.com/zeit/next.js/issues/7915
// Please note that Next.js Link component uses ref only to prefetch link
// based on its availability in view via IntersectionObserver API -
// https://github.com/zeit/next.js/blob/canary/packages/next/client/link.tsx#L119
const TempShallow = React.forwardRef(props =>
cloneElement(child, {
...props,
...baseProps,
onClick: handleClick
})
);
return (
<NextLink href={href} as={as} prefetch={prefetch} passHref {...otherProps}>
<TempShallow />
</NextLink>
);
}
As you see it's a temporary workaround for a bug in Next.js v9 - https://github.com/zeit/next.js/issues/7915.
Beware forwardRef affects reconciliation: element is always re-created on parent re-rendering.
Say
function App() {
const [,setState] = useState(null);
const Input = React.forwardRef((props, ref) => <input {...props} />)
return (
<div className="App">
<h1>Input something into inputs and then click button causing re-rendering</h1>
<Input placeholder="forwardRef" />
<input placeholder="native" />
<button onClick={setState}>change state to re-render</button>
</div>
);
}
You may see that after clicking button forwardRef-ed input is dropped and re-created so it's value becomes empty.
Not sure if this could be important for <Link> but in general it means things you'd expect to run only once per life time(say fetching data in componentDidMount or useEffect(...,[]) as alternative) will happen much more frequently.
So if choosing between this side effect and mocking warning I'd rather ignore Warning. Or create own <Link > that will not cause warnings.
[UPD] missed one thing: React checks forwardRef by reference in this case. So if you make forwardRef out of the render(so it's referentially the same) it will not be recreated:
const Input = React.forwardRef((props, ref) => <input {...props} />)
function App() {
const [,setState] = useState(null);
return (
<div className="App">
<h1>Input something into inputs and then click button causing re-rendering</h1>
<Input placeholder="forwardRef" />
<input placeholder="native" />
<button onClick={setState}>change state to re-render</button>
</div>
);
}
But still I believe it's safer to ignore warning than to introduce such a workaround.
Code above has worse readability to me and is confusing("why ref is not processed at all? was it intentional? why this forwardRef is here and not in component's file?")
I concurr with skyboyer, I'll add that it might be possible to create the forwardRef component outside of the render function to avoid re-creating the component each render. To be checked.
const TempShallow = React.forwardRef(({ child, ...props }) => React.cloneElement(child, props))
function Link() {
// --- SOME EXTENSIVE LOGIC AND PROPS CREATING GOES HERE ---
// --- OMITTED FOR SIMPLICITY ---
// TO DO: Remove forward ref as soon Next.js bug will be fixed -
// https://github.com/zeit/next.js/issues/7915
// Please note that Next.js Link component uses ref only to prefetch link
// based on its availability in view via IntersectionObserver API -
// https://github.com/zeit/next.js/blob/canary/packages/next/client/link.tsx#L119
return (
<NextLink href={href} as={as} prefetch={prefetch} passHref {...otherProps}>
<TempShallow {...props} {...baseprops} child={child} onClick={onClick} />
</NextLink>
)
}

How to solve Warning: React does not recognize the X prop on a DOM element

I'm using a thing called react-firebase-js to handle firebase auth, but my understanding of react and of the provider-consumer idea is limited.
I started with a built a very big JSX thing all at the top level, and that works without warnings. But when I try to break it into components, I got the warning shown in the title and a few others.
This works without warning...
// in App.js component
render() {
return (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<FirebaseAuthConsumer>
{({ isSignedIn, user, providerId }) => {
if (isSignedIn) {
return (
// ui for signed in user
);
} else {
if (this.state.confirmationResult) {
return (
// ui to get a phone number sign in
);
} else {
return (
// ui to verify sms code that was sent
);
}
}
}}
</FirebaseAuthConsumer>
</header>
);
}
But this, better design, I thought, generates errors/warnings...
// in App.js component
render() {
return (
<MuiThemeProvider>
<FirebaseAuthProvider {...config} firebase={firebase}>
<div className="App">
<IfFirebaseAuthed>
<p>You're authed buddy</p>
<RaisedButton label="Sign Out" onClick={this.signOutClick} />
</IfFirebaseAuthed>
<IfFirebaseUnAuthed>
<Authenticater /> // <-- this is the new component
</IfFirebaseUnAuthed>
</div>
</FirebaseAuthProvider>
</MuiThemeProvider>
);
}
// in my brand new Authenticator component...
render() {
return (
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<FirebaseAuthConsumer>
{({ isSignedIn, user, providerId }) => {
if (isSignedIn) {
return (
<div>
<pre style={{ height: 300, overflow: "auto" }}>
{JSON.stringify({ isSignedIn, user, providerId }, null, 2)}
</pre>
</div>
);
} else {
if (this.state.confirmationResult) {
return (
// ui to get a phone number sign in
);
} else {
return (
// ui to verify an sms code that was sent
);
}
}
}}
</FirebaseAuthConsumer>
</header>
);
}
The errors/warnings look like this...
[Error] Warning: React does not recognize the isSignedIn prop on a
DOM element. If you intentionally want it to appear in the DOM as a
custom attribute, spell it as lowercase issignedin instead. If you
accidentally passed it from a parent component, remove it from the DOM
element.
[Error] Warning: React does not recognize the providerId prop on a
DOM element. If you intentionally want it to appear in the DOM as a
custom attribute, spell it as lowercase providerid instead. If you
accidentally passed it from a parent component, remove it from the DOM
element.
[Error] Error: Unable to load external reCAPTCHA dependencies!
(anonymous function) (0.chunk.js:1216) [Error] Error: The error you
provided does not contain a stack trace.
Am I misunderstanding how to use provider-consumers, or is there an error in the react-firebase code, or am I doing some other thing wrong? Thanks.
Presumably, this line must be the culprit:
<FirebaseAuthProvider {...config} firebase={firebase}>
Your config object currently holds fields isSignedIn and providerId, and you must be sending those down to children components, and ultimately to a DOM element. Try removing those fields from the object before you send them down:
const { providerId, isSignedIn, ...authProviderConfig } = config
That way, your object authProviderConfig will not hold the providerId or isSignedIn attributes.
Even better, you can rebuild the configuration object explicitly to avoid any further confusion:
const authProviderConfig = { /* The fields from config FirebaseAuthProvider actually needs */ }
You should also check your FirebaseAuthProvider component to see how it's using those props, and avoid spreading them down to DOM elements.
Related documentation: https://reactjs.org/warnings/unknown-prop.html
This warning appears because you passed a prop on a component that it is not valid.
For example, this
<Component someUnknowprop='random-text' />
will trigger the warning. In order to get rid of the warning you should find out where that warning is coming from. The stack trace should give you a hint.
Adding $ to the prop name fixed it for me.
.tsx file:
<Wrapper $offset={isOffset}>
And on the .style.tsx file:
height: ${({ $offset }) => ($offset ? 'calc(100% + 20px)' : '100%')};
In my case, I was getting this error when using the IfFirebaseAuthed component from react-firebase.
You must make sure that you return a function inside of this component.
I changed this:
<IfFirebaseAuthed>
... My authenticated code here ...
</IfFirebaseAuthed>
To this:
<IfFirebaseAuthed>
{() => (
... My authenticated code here ...
)}
</IfFirebaseAuthed>
And this issue went away.
Check your custom props
In my case, I created a reusable hide component. (Initially, it mounts a button with text masked(******) on clicking this button the key( API key ) will be revealed which is a CopyToClipboard component )
const [hide, setHide] = useState(true);
If hide is true, I am rendering a Button ( spreading all the props )
<Button onClick={() => setHide(false)} {...props}>
******
</Button>
When this button is Clicked hide is false and I am rendering a CopyToClipboard component.
<CopyToClipboard
{...props}
>
{value}
</CopyToClipboard>
The Problem
In the above scenario, I am spreading {...props} to both Button and CopyToClipboard components.
But some props of CopyToClipboard are not compatible with that of Button's.
Fix
So at the top of the component destructure the props that are specific to a component (here CopyToClipboard).
Now safely spread the rest of the props to both the components and pass the new prop separately ( to CopyToClipboard component )
const {onCopy, ...restProps} = props
<Button onClick={() => setHide(false)} {...restProps}>
******
</Button>
<CopyToClipboard
onCopy={onCopy}
{...props}
>
{value}
</CopyToClipboard>

Categories

Resources