What specifically gets rendered with forwardRef() in React - reactjs

I have the following snippet which I got from another source. I have a main functional component, called AgreementForm, which needs to support Print-to-PDF functionality provided by a plugin, React-to-Print. The way to do it is to "wrap" AgreementForm in a forwardRef and pass it to the next defined component after it, which is the React-to-Print component. The snippet is working, but I don't understand what it does,
My question is,
Does AgreementForm ever get rendered by itself?
If not, who defines that PrintComponent gets rendered at any time, rather than AgreementForm ?
What does the expression {(el) => (componentRef = el)} do?
export const AgreementForm = React.forwardRef((props, ref) => {
// State vars...
const [myVar, setMyVar] = useState(null);
// useEffects...
useEffect(() => {
}, [myVar]);
// Render...
return (
<>...</>
);
// This is print component to wrap the print button and content.
export default function PrintComponent(props) {
let componentRef = useRef();
return (
<>
<div>
<ReactToPrint
trigger={() => <Button style={{'margin-bottom':'15px'}}>Print</Button>}
content={() => componentRef}
/>
{/* component to be printed */}
<AgreementForm userInfo={props.userInfo} ref={(el) => (componentRef = el)} />
</div>
</>
);
}

First of all, it seems that provided code sample is a combination of two samples from React-to-PDF doc page: Calling from class components and Calling from functional components. Take a note, that when calling from class component, AgreementForm should be rendered like this <AgreementForm ref={el => (this.componentRef = el)} /> . When calling from function component, it should be used like this <AgreementForm ref={componentRef} />.
Code in provided example will not work as <AgreementForm userInfo={props.userInfo} ref={(el) => (componentRef = el)} /> will assign ref (el) to componentRef variable. On next render, let componentRef = useRef(); will reassign ref to componentRef variable. So ReactToPrint will never get ref to AgreementForm. It will always get undefined/null.
To cocrrect this, use <AgreementForm userInfo={props.userInfo} ref={(el) => (componentRef.current = el)} />. It is essential to assign component's ref to .current property of the ref variable.
And this line content={() => componentRef} also should be changed to content={() => componentRef.current}
And answers to questions:
AgreementForm is rendered when PrintComponent is rendered.
PrintComponent should be rendered by some higher level component for printing to work
As described above, line {(el) => (componentRef = el)} is incorrect. It should be {(el) => (componentRef.current = el)}. This line will assign AgreementForm ref to componentRef.current, which leter can be consumer by ReactToPrint

Related

Is it possible to pass components as props, or Images as props [duplicate]

Lets say I have:
import Statement from './Statement';
import SchoolDetails from './SchoolDetails';
import AuthorizedStaff from './AuthorizedStaff';
const MultiTab = () => (
<Tabs initialIndex={1} justify="start" className="tablisty">
<Tab title="First Title" className="home">
<Statement />
</Tab>
<Tab title="Second Title" className="check">
<SchoolDetails />
</Tab>
<Tab title="Third Title" className="staff">
<AuthorizedStaff />
</Tab>
</Tabs>
);
Inside the Tabs component, this.props has the properties
+Children[3]
className="tablist"
justify="start"
Children[0] (this.props.children) will look like
$$typeof:
Symbol(react.element)
_owner:ReactCompositeComponentWrapper
_self:null
_shadowChildren:Object
_source:null
_store:Object
key:null
props:Object
ref:null
type: Tab(props, context)
__proto__
Object
Children[0].props looks like
+Children (one element)
className="home"
title="first title"
Finally Children object looks like (this is what i want to pass):
$$typeof:Symbol(react.element)
_owner:ReactCompositeComponentWrapper
_self:null
_shadowChildren:undefined
_source:null
_store:
key:null
props:Object
__proto__:Object
**type: function Statement()**
ref:null
The question is this, if I rewrite MultiTab like this
<Tabs initialIndex={1} justify="start" className="tablisty">
<Tab title="First Title" className="home" pass={Statement} />
<Tab title="Second Title" className="check" pass={SchoolDetails} />
<Tab title="Third Title" className="staff" pass={AuthorizedStaff} />
</Tabs>;
Inside the Tabs component
this.props.children looks the same as above.
children[0].props looks like
classname:"home"
**pass: function Statement()**
title: "First title"
I want the pass property to look like. Above just prints out the Statement function.
$$typeof:Symbol(react.element)
_owner:ReactCompositeComponentWrapper
_self:null
_shadowChildren:undefined
_source:null
_store:
key:null
props:Object
__proto__:Object
**type: function Statement()**
ref:null
This is a weird question, but long story I'm using a library and this is what it comes down to.
Using this.props.children is the idiomatic way to pass instantiated components to a react component
const Label = props => <span>{props.children}</span>
const Tab = props => <div>{props.children}</div>
const Page = () => <Tab><Label>Foo</Label></Tab>
When you pass a component as a parameter directly, you pass it uninstantiated and instantiate it by retrieving it from the props. This is an idiomatic way of passing down component classes which will then be instantiated by the components down the tree (e.g. if a component uses custom styles on a tag, but it wants to let the consumer choose whether that tag is a div or span):
const Label = props => <span>{props.children}</span>
const Button = props => {
const Inner = props.inner; // Note: variable name _must_ start with a capital letter
return <button><Inner>Foo</Inner></button>
}
const Page = () => <Button inner={Label}/>
If what you want to do is to pass a children-like parameter as a prop, you can do that:
const Label = props => <span>{props.content}</span>
const Tab = props => <div>{props.content}</div>
const Page = () => <Tab content={<Label content='Foo' />} />
After all, properties in React are just regular JavaScript object properties and can hold any value - be it a string, function or a complex object.
As noted in the accepted answer - you can use the special { props.children } property. However - you can just pass a component as a prop as the title requests. I think this is cleaner sometimes as you might want to pass several components and have them render in different places. Here's the react docs with an example of how to do it:
https://reactjs.org/docs/composition-vs-inheritance.html
Make sure you are actually passing a component and not an object (this tripped me up initially).
The code is simply this:
const Parent = () => {
return (
<Child componentToPassDown={<SomeComp />} />
)
}
const Child = ({ componentToPassDown }) => {
return (
<>
{componentToPassDown}
</>
)
}
By using render prop you can pass a function as a component and also share props from parent itself:
<Parent
childComponent={(data) => <Child data={data} />}
/>
const Parent = (props) => {
const [state, setState] = useState("Parent to child")
return <div>{props.childComponent(state)}</div>
}
I had to render components conditionally, so the following helped me:
const Parent = () => {
return (
<Child componentToPassDown={<SomeComp />} />
)
}
const Child = ({ componentToPassDown }) => {
return (
<>
{conditionToCheck ? componentToPassDown : <div>Some other code</div>}
</>
)
}
In my case, I stacked some components (type_of_FunctionComponent) into an object like :
[
{...,one : ComponentOne},
{...,two : ComponentTwo}
]
then I passed it into an Animated Slider, and in the the slide Component, I did:
const PassedComponent:FunctionComponent<any> = Passed;
then use it:
<PassedComponent {...custom_props} />
How about using "React.createElement(component, props)" from React package
const showModalScrollable = (component, props) => {
Navigation.showModal({
component: {
name: COMPONENT_NAMES.modalScrollable,
passProps: {
renderContent: (componentId) => {
const allProps = { componentId, ...props };
return React.createElement(component, allProps);
},
},
},
});
};

Styled components: cannot find name 'ref' when trying to pass a ref

I'm trying to pass a ref to be read inside of emotion component (similar to styled-components)
But receving the error Cannot find name 'ref'
What should I do in order to be able to access the ref inside the emotion component?
const Label = styled('p')<{ ref: React.MutableRefObject<null> }>({
fontsize: ref.current...,
});
export const NodeDisplayer = ({ data }) => {
const size = useRef(null);
return (
<>
<Label ref={size} id="title">
</Label>
</>
What are you trying to do? Why would a component need to access its own DOM node to determine its styling? I doubt it will work properly.
But to answer your question, the ref prop is special in React so it isn't accessible in the props, but nothing prevents you from also adding it as a different prop:
<Label ref={size} myRef={size} id="title">
Then you should be able to access myRef.
You cannot get a reference to a component function, because it does not exist in the DOM. You need to reference the HTML element, which you might as well do in the child. And since the ref will not be populated until after the component has rendered, you need to use useEffect to get the referenced node.
function Label() {
const ref = useRef<HTMLParagraphElement>(null);
const [style, setStyle] = useState<CSSProperties>({});
useEffect(() => setStyle({ fontSize: ref.current.clientWidth + 'px' }), []);
return (
<p ref={ref} style={style}>
Hi
</p>
);
}
export const NodeDisplayer = () => <Label></Label>

Why do functional components wrapped in forwardRef receive null refs when I use the useContext hook?

I have a sidebarCart component basically a cart component and its is wrapped in forwardRef hook and contains useImperativeHandle to pass a function up to the parent which is App. Everything was working fine until I introduced the useContext hook and now the refs inside sidebarCart are becoming null, what is this weird behavior?
The refs I'm talking about are cartContainer and pageShdowCover
I discovered that when stop my nodejs server this problem disappears.
Here is the sidebarCart component.
import React ,{createRef,forwardRef,useImperativeHandle,useContext,useEffect}from 'react'
import CartItem from './CartItem'
import {MyContext} from '../../Context/ProductsProvider'
const SideBarCart=forwardRef((props,ref)=>{
const {setCart,cart} =useContext(MyContext)
const cartContainer = createRef()
const pageShdowCover = createRef()
const slideOut=e=>{
cartContainer.current.style.right='-400px'
pageShdowCover.current.style.opacity='0'
setTimeout(()=>pageShdowCover.current.style.display='none', 600);
}
useImperativeHandle(ref, () => ({
slideIn(){
pageShdowCover.current.style.display='block'
cartContainer.current.style.right='0'
pageShdowCover.current.style.opacity='1'
}
}),[pageShdowCover,cartContainer]);
return (
<div>
<div className="pageShdowCover" ref={pageShdowCover} ></div>
<div className="SideContainer cart" ref={cartContainer}>
<div className="cart__top">
<h1>Cart</h1>
<i className="far fa-times-circle Close" onClick={slideOut}></i>
<a onClick={slideOut}>x</a>
</div>
<div className="cart__body">
</div>
</div>
</div>
)
})
export default SideBarCart
I think that the useContext hook will result in children rerendering without the parent rerendering. Refs don't trigger rerenders when they are changed, so refs passed from the parent to children may be stale? (I think this is the answer - I may not be correct).
In summary (what worked for me): to solve the problem I was having, just store a ref in state to trigger re-renders when a ref changes.
===
Explanation
My use-case is that I have a parent component with 4 children. One of these children is a header, that I would like the siblings to be able to 'inject' controls into when they are rendered using ReactDOM.createPortal
- Parent
- Header
- A
- B
- C
I found that if I just used a ref, that when the ref is assigned to the DOM the parent isn't rerendered, therefore components A, B, C are not rerendered so they do not know about the newly assigned ref.current value.
I.E. The following does NOT work
const Parent = () => {
const ref = useRef()
return (
<div>
<Header ref={el => { ref.current = el } />
<C ref={ref} />
<B ref={ref} />
<C ref={ref} />
</div>
)
}
// A, B, C have the same signature
const A = forwardRef((props, ref) => {
return (
<div>
{createPortal(<SomeComponent />, ref.current)}
<OtherComponent />
</div>
)
})
I found the solution to this in THIS answer on StackOverflow. I came to this question because A, B, and C are using useContext(...). Previously for me, when the state was created in Parent instead of using a context, the above code WAS working - probably because the parent was rerendering and leaving stale refs around or something (I think it was working 'accidentally'). i.e. the problem wasn't related to context, but rather how rerenders are triggered.
This code DOES work
const Parent = () => {
const [ref, setRef] = useState()
return (
<div>
<Header ref={el => { setRef(el) } />
<C ref={ref} />
<B ref={ref} />
<C ref={ref} />
</div>
)
}
// A, B, C have the same signature
const A = forwardRef((props, ref) => {
return (
<div>
{createPortal(<SomeComponent />, ref)}
<OtherComponent />
</div>
)
})

When react perform componentDidMount and componentWillUnmount

I played with React for several years, still confused with mount/unmount mechanism in some case.
Since mount/unmount is the place to perform side effect, I do not want them to be invoked randomly. So I need to figure out how they work. As far as I can understand currently, when the virtual dom do not present in real dom, it tend to be unmounted. However, it seems not the whole story, and I can not reason it about
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
function App() {
const [count, setCount] = useState(0);
const Component = name => <TestMount name={name} />;
return (
<div className="App">
<h1>{count}</h1>
<Component name="one" />
{Component("two")}
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Component One is remount overtime the app render while Component two not?Why this happen?
Component is a new function each time App is rendered, so <Component name="one" /> is remounted each time too, they are considered different components.
The result of Component("two") call is <TestMount name={"two"} />, TestMount stays the same each time App is rendered, so it's not remounted.
Component is invalid component for what it's used for, to pass name string as name prop to TestMount component because name parameter is not a string but props object when Component is used like <Component name="one" />. name => <TestMount name={name} /> is render function, it's preferable to name it accordingly like renderTestMount for clarity because components aren't supposed to be called directly like Component("two").
In case a function is supposed be used as component or render function interchangeably, the signature should be changed to ({ name }) => <TestMount name={name} />.
The expected behaviour could be achieved for <Component name="one" /> by memoizing Component:
const Component = useCallback(({ name }) => <TestMount name={name} />, []);
But since Component doesn't depend on App scope, a correct way is to define it outside:
const Component = ({ name }) => <TestMount name={name} />;
function App() {...}
For instance, this is the reason React Router Route has separate component and render props for a component and render function. This allows to prevent unnecessary remounts for route components that need to be defined dynamically in current scope.
The key to such issue is the difference between the React Component and React element, put shortly React is smart with element not Component
Component vs element
Component is the template used to create element using <> operation. In my prospective, <> is pretty much like new operator in OOP world.
How React perform update between renders
Every time the render method(or functional component) is invoked. The new element is created using <>, however, React is smart enough to tell the element created between renders are actually the same one, i.e. it had been created before and can be reused as long as the element is created by the same Component
How about different Component
However when the identity of the Component using to generate element changes(Even if the Components look same), React believes something new come though, so it remove(unmount) the previous element and add(mount) the new one. Thus componentDidMount or componentWillUnmount is invoked.
How is confusing
Think we got a Component and when we generate element using <Component /> react can tell the same elements because they are generated by the same Component
However, HOCComponent=()=><Component />; element= <HOCComponent />, every time element is generated, it used a different Component. it is actually a HOC constructed dynamically. Because the HOC is created dynamically inside render function, it can be confusing on the first glance.
Is that true
I never found any offical document about the idea above.However the code below is enough to prove
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
let _Component;
function cacheComponent(C) {
if (C && !_Component) {
_Component = C;
}
return _Component || null;
}
const CacheComponent2 = once(({ name }) => <TestMount name={name} />, []);
function App() {
const [count, setCount] = useState(0);
// can be used as a HOC of TestMount or a plain function returnnung a react element
const Component = name => <TestMount name={name} />;
const CacheComponent1 = cacheComponent(Component);
const CacheComponent3 = useCallback(
({ name }) => <TestMount name={name} />,
[]
);
return (
<div className="App">
<h1>{count}</h1>
{/* used as HOC */}
<Component name="one" />
{/* used as function returnning the element */}
{Component("two")}
<CacheComponent1 name="three" />
<CacheComponent2 name="four" />
<CacheComponent3 name="five" />
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Also the code above provide three different ways to avoid the undesired mount/unmount. All the solutions are cache the identity of the HOC somehow

Reactjs not updating dom classes after splicing state

Hello I am new to react and have a question with changing classes and animation onClick.
I am trying to move up and down only with css classes that I add or remove to an array that is in my className.
app.js i have this in state
updown: ["containerMG"],
and here is how i render my components in app.js
render() {
return (
<div>
<div className="movies-container">
{this.state.filmovi.map((film, index) => {
return <Film
naslov={film.naslov}
naslovnaSlika={film.naslovnaSlika}
key={film.id}
openFilm={() => this.injectFilm(index)}/>
})}
</div>
<Gallery />
<ContainerMG
selectedFilm={this.state.selectedFilm}
klasa={this.state.updown}
zatvori={this.closePreview}/>
</div>
);
}
my component looks like this
const ContainerMG = (props) => {
return (
<div className={props.klasa.join(' ')}>
<img onClick={props.zatvori} src="xxx" alt="close" className="close-popup" />
<p>{props.selectedFilm.naslovFilma}</p>
</div>
)
}
this is how the div moves up
injectFilm = (filmIndex) => {
this.state.updown.push("position-top");
const selectedFilm = this.state.filmovi.find((film, index) => index === filmIndex)
this.setState((prevState) => ({
selectedFilm
}))
}
this is how i tried to move it down
closePreview = () => {
this.state.updown.splice(1);
}
i am pretty sure i should not change the state directly, and also when i change it to remove the "position-top" class the dom doesn't reload.
thank you for all the help in advance and if i didn't show something that you need please do write.
You're right, you should never change the state directly like that, but rather, use a setState() method. Doing this.state.updown.push("position-top"); will mutate it. Let's say you want to remove whatever is last from the array, you could do something like:
const {updown} = this.state;
updown.pop();
this.setState({updown: updown});
Which would cause a re-render. Treat the state as if it were immutable.

Resources