Pass component instance directly into prop or use a render callback? - javascript

What would be the idiomatic and/or more effective way of handling a situation whereby the parent component wants the child to render some custom JSX?
a render callback?
or passing a component instance directly into the prop?
Example:
// Option 1
// -
const rightChild = <View />
<NavigationBar rightChild={rightChild} />
// Option 2
// -
const renderRightChild = () => <View />
<NavigationBar renderRightChild={rightChild}>
(lambdas are used for simplicity, I am aware of the performance implications)

This:
const rightChild = <View />
// ...
<NavigationBar rightChild={rightChild} />
...isn't "passing JSX into the prop." It's passing a component instance. The JSX is processed by const rightChild = <View /> (turned into React.createElement call) and the result (a component instance) is stored in rightChild. That's normal, though it's usually as a child (ending up on the children prop) rather than a separate named prop. But either way, it's a prop, so...
As far as I know, that would be the standard way to do it.

Either way will work. The case for when you really want to use a render prop is when you will need the props passed from NavigationBar in order to render the component or fragment. The benefit to passing in a component is that you will not be passing in a new function to be rendered each time. From a performance standpoint it would be a bit easier to get it to not for a re-render.

Related

Render only components with changes

I have an array with thousands of strings and is passed to a component:
Main component:
const array = ['name1', 'name2', 'name3'];
const [names, setNames] = useState(array);
const onClick = (index) => {
setNames(names.map((name, i) => {
if (i === index) {
return 'name changed';
}
};
};
return (
<ul>
{array.map((name, index) => (
<li key={index}>
<ShowName name={name} key={index} onClick={() => onClick(index)} />
</li>
)}
</ul>
);
ShowName component:
let a = 0;
export default function ShowName({ name, onClick }) {
a += 1;
console.log(a);
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
There's also a button which changes a name randomly. But whenever the button is pressed, all the ShowName components are rerendering. I've been trying to use useCallback and useMemo, but the components are still rerendering x times (x is the length of the array).
const ShowNameHoc = ({ name }) => {
return <ShowName name={name} />
};
return (
<div>
{array.map((name, index) => <ShowNameHoc name={name} key={index} />)}
</div>
);
What should I do if I only want to rerender the component with a change?
You have a misunderstanding in the concepts here. The default is for React to call render on all children, regardless of whether the props changed or not.
After that happened, React will compare that new Virtual DOM to the current Virtual DOM and then only update those parts of the real DOM that changed.
That's why the code in a render method should be quick to execute.
This behavior can be changed by using features like useMemo, PureComponents or shouldComponentUpdate.
References:
https://reactjs.org/docs/rendering-elements.html (Bottom):
Even though we create an element describing the whole UI tree on every tick, only the text node whose contents have changed gets updated by React DOM.
https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation
Even though React only updates the changed DOM nodes, re-rendering still takes some time. In many cases it’s not a problem, but if the slowdown is noticeable, you can speed all of this up by overriding the lifecycle function shouldComponentUpdate, which is triggered before the re-rendering process starts.
...
In most cases, instead of writing shouldComponentUpdate() by hand, you can inherit from React.PureComponent. It is equivalent to implementing shouldComponentUpdate() with a shallow comparison of current and previous props and state.
Also, read this for some more background info: https://dev.to/teo_garcia/understanding-rendering-in-react-i5i
Some more detail:
Rendering in the broader sense in React means this (simplified):
Update existing component instances with the new props where feasible (this is where the key for lists is important) or create a new instance.
Calling render / the function representing the component if shouldComponentUpdate returns true
Syncing the changes to the real DOM
This gives you these optimization possibilities:
Ensure you are reusing instances instead of creating new ones, e.g. by using a proper key when rendering lists. Why? New instances always result in the old DOM node to be removed from the real DOM and a new one to be added. Even when unchanged. Reusing an instance will only update the real DOM if necessary. Please note: This has no effect on whether or not render is being called on your component.
Make sure your render method doesn't do heavy lifting or if it does, memoize those results
Use PureComponents or shouldComponentUpdate to prevent the call to render altogether in scenarios where props didn't change
Answering your specific question:
To actually prevent your ShowName component from being rendered - into the Virtual DOM - if their props changed, you need to perform the following changes:
Use React.memo on your ShowName component:
function ShowName({ name, onClick }) {
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
export default memo(ShowName);
Make sure the props are actually unchanged by not passing a new callback to onClick on each render of the parent. onClick={() => onClick(index)} creates a new anonymous function every time the parent is being rendered.
It's a bit tricky to prevent that, because you want to pass the index to this onClick function. A simple solution is to create another component that is passed the onClick with the parameter and the index and then internally uses useCallback to construct a stable callback. This only makes sense though, when rendering your ShowName is an expensive operation.
That is happening because you are not using the key prop on <ShowName/> component.
https://reactjs.org/docs/lists-and-keys.html
it could look something like this
return (
<div>
{array.map(name => <ShowName key={name} name={name} />)}
</div>
);

Function inside component in reactjs

I have the following snippet of code
return (
<InputMask
mask="99:99"
*some other prop*
>
{(inputProps) => (
<Input
{...inputProps}
onSubmit={*Does something*}
/>
</InputMask>)
this is the return of a custom component, my question is regarding the syntax... how can a function be defined inside a component? Is this function being called whenever this component is rendered? What is passed into this function (in this case, how is InputProps passed in and what does it contain)?
As you can deduce, I am quite new to frontend development and react in general so any guidance or reference to any documentation will be of great help!
So firstly a quick definition. Here's some JSX where I'm using children:
<MyComponent>
<b>
This bold element
</b>
And this text are children of MyComponent.
</MyComponent>
Inside MyComponent is a regular render method, except importantly, it also has a props.children that it can do whatever it wants with:
render(){
return <div className="my-component">{this.props.children}</div>;
}
Importantly, children is just a prop like anything else. It can be anything you'd like it to be, including a function. This can be particularly powerful if a component wants to give some information to its unknown children. A nice example is some sort of iterator:
<Loop over={[1,2,3]}>
{item => <span>{item}</span>}
</Loop>
Where the Loop component is calling this.children, as it expects it to be a function:
render(){
// For each item in the 'over' prop, call this.props.children
return this.props.over.map(this.props.children);
}
Essentially this is what you have - in terms of when that function runs and what the inputProps object is, it's entirely up to that InputMask component, but it almost certainly will be running it when it renders.

React - changing key prop doesn't cause rerender of child component

I am using react-infinite-scroll to render a feed of data. Whenever the user changes their settings, I want to refresh the feed of data.
To do this, I have a key prop for the InfiniteScroll component. The idea being whenever the user changes their settings
this.state.renderCount is incremented by 1 in setState
React notices that the key prop of InfiniteScroll is different, and re-renders the component from scratch.
<Content style={{ padding: "0px 20px" }}>
<InfiniteScroll
key={this.state.renderCount}
style={{ width: "100%", height: "100%" }}
pageStart={0}
loadMore={this.loadPosts.bind(this)}
hasMore={true}
loader={<div>Loading....</div>}
useWindow={false}
>
{listItems}
</InfiniteScroll>
</Content>
The refresh code is in componentDidUpdate and is called if I determine that a refresh is needed:
this.setState({ renderCount: this.state.renderCount + 1 })
However, the component is not reset. Why is this?
key and ref are special keywords in react. See
Most props on a JSX element are passed on to the component, however,
there are two special props (ref and key) which are used by React, and
are thus not forwarded to the component.
For instance, attempting to access this.props.key from a component
(i.e., the render function or propTypes) is not defined. If you need
to access the same value within the child component, you should pass
it as a different prop (ex: <ListItemWrapper key={result.id}
id={result.id} />). While this may seem redundant, it’s important to
separate app logic from reconciling hints.
To pass they key prop to the child element use another name.
See also : Why is ref not a property in children components?
Turns out I was overthinking it.
With the way react-infinite-scroll works, I just have to clear the listItems array.
In my case, I just had to do:
this.setState({ templates: [] })
(listItems is generated by a map function that maps templates to jsx).

Is it an anti-pattern to define a function component inside the render() function?

I want to know if it's an anti-pattern or if it affects the component somehow to do something like this:
render() {
const MyFuncComponent = ({ prop1, prop2 }) => (
// code here
)
return (
<div>
<MyFuncComponent prop1={something} prop2={else} />
</div>
)
}
Yes, this is an anti-pattern for the same reason we shouldn't use a Higher-Order-Component inside of render.
Don’t Use HOCs Inside the render Method
React’s diffing algorithm (called reconciliation) uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render is identical (===) to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they’re not equal, the previous subtree is unmounted completely.
Normally, you shouldn’t need to think about this. But it matters for HOCs because it means you can’t apply a HOC to a component within the render method of a component:
render() {
// A new version of EnhancedComponent is created on every render
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// That causes the entire subtree to unmount/remount each time!
return <EnhancedComponent />;
}
The problem here isn’t just about performance — remounting a component causes the state of that component and all of its children to be lost.
This means that the new component will appear in the React tree (which can be explored with the react-devtools) but it won't retain any state and the lifecycle methods like componentDidMount, componentWillUnmount, useEffect will always get called each render cycle.
Solutions
Since there are probably reasons for dynamically creating a component, here are some common patterns that avoid the pitfalls.
Define the new component outside
Either in its own file, or directly above the parent component's definition. Pass any variable as props instead of using the parent component's scope to access the values.
const MyFuncComponent = ({ prop1, prop2 }) => <>{/* code here */}</>;
const MyComponent = props => (
<div>
{props.list.map(({ something, thing }) => (
<MyFuncComponent prop1={something} prop2={thing} />
))}
</div>
);
Helper function
A regular function that returns JSX can be defined and used directly inside another component. It won't appear as a new component inside React's tree, only its result will appear, as if it was inlined.
That way, we can also use variables from the enclosing scope (like props.itemClass in the following example) in addition to any other parameters.
const MyComponent = props => {
// Looks like a component, but only serves as a function.
const renderItem = ({ prop1, prop2 }) => (
<li className={props.itemClass}> {/* <-- param from enclosing scope */}
{prop1} {prop2} {/* other code */}
</li>
);
return <ul>{props.list.map(renderItem)}</ul>;
};
It could also be defined outside the component since it's really flexible.
const renderItem = (itemClass, { prop1, prop2 }) => (
<li className={itemClass}>
{prop1} {prop2} {/* other code */}
</li>
);
const MyComponent = props => (
<ul>
{props.list.map(item => renderItem(props.itemClass, item))}
</ul>
);
But at that point, we should just define a React component instead of faking it with a function. Use React in a predictable manner and to its full potential.
Inline the logic
It's really common to inline JSX inside of a condition or a map callback.
const MyComponent = (props) => (
<ul>
{props.list.map(({ something, thing }) => (
<li className={props.itemClass}>
{something} {thing} {/* other code */}
</li>
))}
</ul>
);
If we find ourselves copy-pasting this same inlined JSX everywhere, it might be time to wrap it up in its own reusable component.
I think in general people avoid defining functions in render but according to this blog post it is not neccesarily a bad practice. The blog post focuses on inline event handler functions being defined in render but I would guess it applies to any function defined in render. Defining functions in render means there is the overhead of redfining them each time render is called but that may not make a noticible performance difference depending on your component.
For the particular example you gave I would reccomend not to define another react component in render. If you do define any functions in render they should be cohesive to what render is doing. Defining another component or adding a bunch of functions inside of render can make in unwieldy and hard to understand what the code is doing.

What's the proper way of passing a ref to a prop?

I'm trying to pass a ref of a component to another component. Since string refs are being deprecated I'm using callback refs.
So I have something similar to this:
<One ref={c => this.one = c}/>
<Two one={this.one}/>
The problem is that whenever I try to access this.props.one inside Two I get undefined.
I have even tried this on Two:
componentDidMount(){
setTimeout(()=>{
console.log(this.props.one);
},5000)
}
It seems the problem is that when the prop is created, the ref doesn't exist yet since it's created once One is mounted. But I don't know how to "refresh" the props on Two to get the ref to the mounted component.
So what's the proper way of passing a ref to another component?
Edit
Some users have suggested to encapsulate that logic in a higher component, which in itself renders those other child components.
The problem with that approach is that you can't create reusable logic and you have to repeat the same logic over and over in those encapsulating components.
Let's say you want to create a generic <Form> component which encapsulates the submit logic to your store, error checking, etc. And you do something like this:
<Form>
<Input/>
<Input/>
<Input/>
<Input/>
<SubmitButton/>
</Form>
In this example <Form> can't access the instances (and methods) of the children since this.props.children doesn't return those instances. It returns some list of pseudo components.
So how can you check if a certain <Input/> has detected a validation error without passing a ref?
You have to encapsulate those components in another component with the validation logic. For example in <UserForm>. But since each form is different the same logic has to be copied in <CategoryForm>, <GoupForm>, etc. This is terribly inefficient which is why I want to encapsulate the validation logic in <Form> and pass references of the <Input> components to <Form>.
In general the "ref" feature is an anti-pattern in React. It exists to enable side-effect driven development, however in order to benefit the most from the React way of programming you should try to avoid "refs" if possible.
As for your particular issue, passing a child a ref to it's sibling is a chicken vs. egg scenario. The ref callback is fired when the child is mounted, not during render which is why your example doesn't work. One thing you can try is pushing the ref into state and then reading from state into the other child. So:
<One ref={c => !this.state.one && this.setState({ one: c })}/>
<Two one={this.state.one}/>
Note: without the !this.state.one this will cause an infinite loop.
Here is a codepen example of this working (look at the console to see the sibling ref logged): http://codepen.io/anon/pen/pbqvRA
This is now much simpler using the new ref api (available since React 16 - thanks to perilandmishap for pointing that out).
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.oneRef = React.createRef();
}
render () {
return (
<React.Fragment>
<One ref={this.oneRef} />
<Two one={this.oneRef} />
</React.Fragment>
}
}
}
You would consume the prop in Two like:
this.props.one.current
A few things of note with this approach:
The ref will be an object with a current property. That property will be null until the element/component is mounted. Once it's mounted, it will be the instance of One. It should be safe to reference it once <Two /> is mounted.
Once the <One /> instance is unmounted, the current property on the ref returns to being null.
In general, if you need to pass a reference to something that may not be set at call time, you can pass a lambda instead:
<One ref={c => this.one = c}/>
<Two one={() => this.one}/>
and then reference it as
this.props.one()
If it has been set when you call it, you'll get a value. Before that, you'll get undefined (assuming it hasn't otherwise been initialized).
It bears noting that you won't necessarily re-render when it becomes available, and I would expect it to be undefined on the first render. This is something that using state to hold your reference does handle, but you won't get more than one re-render.
Given all that, I would recommend moving whatever code was using the ref to One in Two up into the component that is rendering One and Two, to avoid all the issues with both this strategy, and the one in #Carl Sverre's answer.

Categories

Resources