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>
)
}
Related
I would like to prevent component re-rendering using React. I've read some guides but I'm still having trouble getting my code to work.
The CreateItem component creates an input form from the json object. When the input states change, React re-renders all components. I would avoid this situation as it causes some problems.
I have used React.memo but my code still doesn't work. Is this a good way to implement this code? How can I correct my code? Thank you
function MyComponent() {
return(
<div className="row">
{Array.from(new Map(Object.entries(json))).map((data) => (
<CreateItem obj={data} />
))}
</div>
);
}
//function CreateDiv(props) {
const CreateDiv = React.memo((props) => {
console.log("rendering ");
return (
<form name="myForm" onSubmit= {formSubmit}>
<div className="row">
{Array.from(new Map(Object.entries(props.obj[1]))).map((data) => (
<>
{(() => {
return(
<div className="col-sm-2">
<CreateItem obj={data[1]} />
</div>
)
})()}
</>
))}
</div>
</form>
);
});
--- EDIT ---
CreateItem uses CreateCheckBoxComponent function to create my custom checkbox with default status from json value.
CreateCheckBoxComponent code is follwing:
function CreateCheckBoxComponent(props) {
if(parseInt(props.obj.defaultValue) === 5)
setChecked(false);
else
setChecked(true);
return(
<FormCheck
label={props.obj.simbolName}
name={props.obj.idVar}
type="checkbox"
checked={checked}
onChange={handleCheckBoxChange}
sm={10}
/>
);
}
HandleCheckBoxChange works fine and changes state, but when I click on checkbox to change the flag, CreateCheckBoxComponent is re-render and
it sets the default state again. I would like to avoid this problem and I think preventing re-rendering can be a solution..
React.memo only prevents own rerendering.
You have considered the following things.
If the children are using React.memo but the parent re-renders
the children will render also.
React.memo prevents re-rendering if the component's state changes. but if the prop changes, the component re-renders.
Note: make sure when you render elements/Components with the map function or any iteration always provide a unique key to them.
For more information click here
I am trying to call PopupDialog.tsx inside Content.tsx as a sibling of Item.tsx.
Previously PopupDialog.tsx is called inside C.tsx file but due to z index issue i am trying to bring it out and call it in Content.tsx
Is it possible to somehow pass the whole component(popupDialog and its parameters) in Content.tsx so that i could avoid passing back and forth the parameters needed for popupdialog in content.tsx.
Code in C.tsx where PopupDialog component is called.
const C = (props: Props) => (
<>
{props.additionalInfo ? (
<div className="infoButton">
<PopupDialog // need to take this code out and want to add in Content.tsx
icon="info"
callback={props.callback}
position={Position.Right}
>
<div className="popuplist">{props.additionalInfo}</div>
</PopupDialog>
</div>
) : (
<Button className="iconbutton"/>
)}
</>
);
Content.tsx where i would like to call PopupDialog.tsx with its parameters
const Content = (props: Props) => {
const [componentToRender, docomponentToRender] = React.useState(null);
const [isAnimDone, doAnim] = React.useState(false);
return (
<div className="ContentItems">
<PWheel agent={props.agent} />
{isAnimDone && (
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog/> //want to call here with all its parameters to be passed
}
</>
)}
</div>
);
};
Folder Structure
App.tsx
->ViewPort.tsx
->Content.tsx
->PWheel.tsx
->Item.tsx
->A.tsx
->B.tsx
->C.tsx
{props.additionalinfo &&
->PopupDialog.tsx
->PopupDialog.tsx
So if I understand the question correctly you want to pass one component into another so that you can use the properties or data of the passed componenet in your current component.
So there are three ways to achieve this.
1)Sending the data or entire component as prop.This brings disadvantage that even though components which don't require knowledge
about the passed component will also have to ask as a prop.So this is bascially prop drilling.
2)The other is you can use context api.So context api is a way to maintain global state variale.so if you follow this approach you don't need to pass data or componenet as props.Wherever you need the data you can inport context object and use it in componenet.
3)Using Redux library.This is similar to context api but only disadavantage is that we will have to write lot of code to implement this.Redux is a javascript library.
Let me know if you need more info.
You need to :
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog abc={componentToRender} /> //you must call in this component, in this case i name it is abc , i pass componentToRender state to it
}
</>
and then PopupDialog will receive componentToRender as abc, in PopupDialog , you just need to call props.abc and done .
If you need to know more about prop and component you can see it here
I think what you want to use is Higher-Order-Components (HOC).
The basic usage is:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Below is such an implementation that takes a component (with all its props) as a parameter:
import React, { Component } from "react";
const Content = WrappedComponent => {
return class Content extends Component {
render() {
return (
<>
{/* Your Content component comes here */}
<WrappedComponent {...this.props} />
</>
);
}
};
};
export default Content;
Here is the link for higher-order-components on React docs: https://reactjs.org/docs/higher-order-components.html
Make use of
useContext()
Follow this for details:
React Use Context Hook
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} />
I'm using gatsby and have a functional component that loops through some data to create radio button group with an onchange event and checked item. When i update the state whole page component rerenders. i though adding memo was meant to stop this but it doesn't seem to work.
here is the code
const BikePage = React.memo(({ data }) => {
console.log("page data", data)
const [selectedColor, setColor] = useState(data.bike.color[0])
const onColorChange = e => {
setColor(e.target.value)
}
return (
<div>
{data.treatment.price.map((value, index) => {
return (
<div>
<input
id={`bike-option-${index}`}
name="treatment"
type="radio"
value={value}
checked={selectedColor === value}
onChange={e => onColorChange(e)}
/>
<label
htmlFor={`treatment-option-${index}`}
>
{value}
</label>
</div>
)
})}
<Link
to="/book"
state={{
bike: `${data.bike.title}-${selectedColor}`,
}}
className="c-btn"
>
Book Now
</Link>
</div>
)
});
If you update the state the component will re-render, that's fundamentally how react works. the memoised data prop is coming from outside of the component.
"If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result" react.memo
You're not changing the incoming props though, you're changing the state
Side note: i imagine that on changing this value you probably want to be changing the state of the data on the server through some means also ( REST POST / graphql mutation). Subsequent refetches of this data would re-render this component as well. It depends what you're trying to ultimately achieve.
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>