Proper way to use function properties in React components - reactjs

I'm new to React and from the limited tutorials etc. that I've done I've seen that the application state lives at a level that may be different than the component that's rendering it, for the component to be able to edit the state a function is passed in as a property and used that way. This is all fine but at what point is it ridiculous to be passing a function into a nested component? For example, I have
<App>
<Container>
<Item>
<ItemChangeMenu/>
</Item>
</Container>
</App>
App has the state and the function changeItem, should the Container, Item, and ItemChangeMenu all have an onItemChanged property that passes it along?

There are 2 main approaches to pass props down to the children:
Explicit - passing the props as individuals on each level:
const Container = (props) => (
<Item
onItemChange={props.onItemChange}
value={props.value} />
);
const Item = (props) => (
<ItemChangeMenu
onItemChange={props.onItemChange}
value={props.value} />
);
const ItemChangeMenu = (props) => (
<input
onChange={props.onItemChange}
value={props.value} />
);
class App extends React.Component {
state = { value: 'Hello World...' }
onItemChange = ({ target }) => {
this.setState(state => ({ value: target.value }))
}
render() {
const { value } = this.state;
return (
<Container
onItemChange={this.onItemChange}
value={value} />
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Implicit - passing the entire props object but spreading it (new
ES2018 feature):
const Container = (props) => <Item {...props} />;
const Item = (props) => <ItemChangeMenu {...props} />;
const ItemChangeMenu = (props) => (
<input
onChange={props.onItemChange}
value={props.value} />
);
class App extends React.Component {
state = { value: 'Hello World...' }
onItemChange = ({ target }) => {
this.setState(state => ({ value: target.value }))
}
render() {
const { value } = this.state;
return (
<Container
onItemChange={this.onItemChange}
value={value} />
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
There are pros and cons for each pattern of course.
With the explicit pattern, you get a clear view of which props are being passed down, and you can manually decide what props you want to pass.
The main caveat with this approach though, it is tedious and quite hard to refactor.
With the implicit pattern, you just pass an object and forget about it, you need to pass a new prop? it will already go down the chimney. If you ever need to refactor, change names of props or re-order the components hierarchy, then no need to change the props names.
However, the main caveat with this approach, is that you will probably pass more props then you need to. this can lead to unnecessary calls for componentWillReceiveProps.
Of course you can use destructure to minimize it:
const {someProp, someOtherProp, ...rest} = this.props;
// ...
<Child {...rest} />
But this is almost the same thing as taking the explicit approach.
There is a 3rd option though it is not related to the props API.
It called context. With the context API you can "skip" levels and grab objects directly from the grand parents.
Sounds great, but if you will look at the DOCS:
Why Not To Use Context
The vast majority of applications do not need
to use context.
If you want your application to be stable, don’t use context. It is an
experimental API and it is likely to break in future releases of
React.
As said in the docs, this is an "experimental API and it is likely to break" and that day has come, a new context API is on it's way and will might change the way we work with props or react all together.
The context feature is not something that no one use by the way, important libraries like react-redux, react-router, react-mobx are using and dependent on it.

Related

React - easy solution for global state or antipattern?

In my React app I have main component that contians the whole state of the application and also functions used to modify the state.
In order to get access to the state and the functions in all subcomponents I'm passing this from the main component through subcomponent attributes (app):
class App extends React.Component {
state = DefaultState;
funcs = getController(this);
render() {
return (
<Header app={this} />
<Page app={this} />
<Footer app={this} />
)
}
}
function getController(app: App) {
return {
switchPage: (page: string) => {
app.setState({ page: page });
},
}
}
Therefore in subcomponents I can access the state variables and modify them like this:
const HeaderComponent: React.FC<CompProps> = (props) => {
return (
<div>
<h1>{props.app.state.currentPage}</h1>
<button onClick={(e) => {props.app.funcs.switchPage('home')}}>Home</button>
</div>
)
}
This is the quickest/simplest solution I've found for global state. However I've never seen this in any tutorial or so. I guess main problem is the whole app will rerender when a single value is changed in global state, but the same goes for React Context API.
Question is that are there any disadvantages of this approach or a reason to not use this?
You can save func to an exported let variable and utilize the most recent version of func without re-rendering, but as this isn't a common occurrence, you won't find much information about it. Since it's simply javascript, any known hack will work. Also, the part of your question regarding react-context re-rendering is correct although you must consider that it will re-render and it will be more pruned for optimization of unmodified siblings.
You may alternatively supply a simple ref (useRef) to those components, which will allow them to access the most recent version of func, but because the ref reference itself does not change when the page is re-rendered, they will not be updated for function change.
I'm using react functional component but the class base may be so similar
export let funcs = null
const App = () => {
funcs = getController();
render() {
return (
<Header />
<Page />
<Footer />
)
}
}
// header component
import { funcs as appFuncs } from '~/app.js'
const HeaderComponent: React.FC<CompProps> = (props) => {
return (
<div>
{/* same thing can be happened for the state */}
<button onClick={(e) => {appFuncs.switchPage('home')}}>Home</button>
</div>
)
}
Hooks version
const App = () => {
const ref = useRef();
funcs = getController();
ref.current = {state, funcs};
// note ref.current changes not the ref itself
render() {
return (
<Header app={ref} />
<Page app={ref} />
<Footer app={ref} />
)
}
}
// header
const HeaderComponent: React.FC<CompProps> = (props) => {
return (
<div>
<h1>{props.app.current.state.currentPage}</h1>
<button onClick={(e) => {props.app.current.func.switchPage('home')}}>Home</button>
</div>
)
}
Any suggestions or other techniques will be much appreciated.

React - performance when returning a component from a parent component

I recently read an article about performance optimization in React and i just wanted to make sure i understand one of the key concepts.
Let's say i have this component:
class NewComponent extends React.Component {
render() {
return (
<User>
name={"test"}
</User>
)
}
}
If NewComponent's state changes, it will not re-render User, right? Since User will be the same instance with the same String prop every time.
However, if i write something like this:
class NewComponent extends React.Component {
render() {
return (
<User>
onChange={(value1, value2) => {
// do some stuff
}}
name={"test"}
</User>
)
}
}
User will always get re-rendered since React is creating and passing a new instance of the onChange function on every render.
Is that true?
When it's just a component, the same instance is being passed, but when there are objects/arrays/functions involved, React creates a new instance on every parent render?
Without using React.memo for functional components or PureComponents for class components, <User /> will re-render every time <NewComponent /> will receive an updated prop and/or updates its state.
As you can see below, even though props for the <Child /> component don't change, they're still being re-rendered due to the updates in state of the <Parent /> component.
class Parent extends React.Component {
state = { count: 0 };
updateState = () => {
this.setState(prevState => {
return {
count: prevState.count + 1
};
});
};
render() {
console.log(`Parent.count`, this.state.count);
return (
<div>
<button type="button" onClick={this.updateState}>
Update state
</button>
<Child id="1" />
<Child id="2" />
</div>
);
}
}
function Child(props) {
console.log(`Child ${props.id} re-/rendered!`);
return null;
}
ReactDOM.render(
<Parent />,
document.getElementById("app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
If you want to skip re-renders, you could use React.memo, which according to React's documentation, "by default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument." Meaning, if you're passing simple scalar values, such as string, number, or boolean, it will skip the re-render. However, if you have more complex objects, this might not work.
Note below how the complexProp, for the second button, causes a re-render, even though it's using React.memo.
class Parent extends React.Component {
state = { count: 0 };
updateState = () => {
this.setState(prevState => {
return {
count: prevState.count + 1
};
});
};
render() {
console.log(`Parent.count`, this.state.count);
return (
<div>
<button type="button" onClick={this.updateState}>
Update state
</button>
<Child id="1" complexProp={() => { console.log(`I am function!`) }} />
<Child id="2" />
</div>
);
}
}
const Child = React.memo(
function Child(props) {
console.log(`Child ${props.id} re-/rendered!`);
return null
}
)
ReactDOM.render(
<Parent />,
document.getElementById("app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Read more about shallow comparison:
How does shallow compare work in react - stackoverflow.com
As for your performance question, I'd suggest to be careful with premature optimizations because they could cause performance problems too. If you have visible performance issues that can be measured, then I'd suggest reaching for tools like React.memo and/or PureComponent. Otherwise, I would not worry about it, even though your components re-render - it's not always a bad thing.
Here's a good read how premature optimizations could cause issues:
When to useMemo and useCallback - Kent C. Dodds
Performance optimizations are not free. They ALWAYS come with a cost but do NOT always come with a benefit to offset that cost.
Therefore, optimize responsibly.

Is there a better/another way to create list in react?

In React, to render a list you generally map over and render elements like this:
list.map((itm, idx) => <li>{itm}</li>)
Can I refactor this to a component that is generic enough to handle different element types but still do the mapping for me? Aka,
<EasyList data={list} /> // tag would be "li" by default
<EasyList data={list} tag="p" /> // a customized tag name
What would be the best way to implement something like this?
You can achieve exactly what you are describing in the way you are describing it. The implementation of EasyList could look something like this
const EasyList = ({data, tag: Elem = 'li'}) => {
return data.map( (item, idx) => <Elem key={idx}>{item}</Elem>)
}
and usage would look like
<EasyList data={list} /> // tag would be "li" by default
<EasyList data={list} tag="p" /> // a customized tag name
Demo to play with
There are multiple ways you could implement this, so depending on needs you could go the route of a render prop, where you define the exact elements you need. Really it depends on what kind of control / granularity you want.
<EasyList data={seed} render={ (item, idx) => <li>{item}</li>} />
and the implementation with a render prop would look like
const EasyList = ({data, render}) => {
return data.map(render)
}
This answer isn't much different than John Ruddell's, but I had already completed it before seeing his.
You can make this EasyList component as sophisticated as you need it to be. My example below shows that you can have it control the parent component (ul by default) and that the component types passed can be html element types (e.g. ul, li, p) or custom components such as DivWithHeaderText in my example.
import React from "react";
import ReactDOM from "react-dom";
const EasyList = ({ data, ListComponent = "ul", ItemComponent = "li" }) => {
return (
<ListComponent>
{data.map(item => (
<ItemComponent>{item}</ItemComponent>
))}
</ListComponent>
);
};
const myItems = ["Here", "are", "my", "list", "items"];
const DivWithHeaderText = ({ children }) => {
return (
<div style={{ border: "solid black 1px" }}>
<div>Here is my header</div>
<hr />
{children}
</div>
);
};
function App() {
return (
<>
<EasyList data={myItems} />
<EasyList
data={myItems}
ListComponent={DivWithHeaderText}
ItemComponent="p"
/>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React: Is it bad practice to import a child component directly rather than pass in as a dependency?

I may be over thinking this, but I am curious if importing a child component directly is bad practice with regards to coupling and testing.
Below is a simple example:
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
<Header></Header>
<div>{this.props.importantContent}</div>
</div>
)
}
}
To me it looks like there is now coupling between Widget and Header. With regards to testing, I don't see an easy way to mock the Header component when testing the Widget component.
How do other larger React apps handle cases like this? Should I pass Header in as a prop? If using react-redux, I can inject header with the Connect method like below to reduce boilerplate. Is that sound?
import { connect } from 'react-redux';
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
{this.props.header}
<div>{this.props.importantContent}</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
header: Header
}
}
export default connect(mapStateToProps)(Widget)
I am interested is simple doing what the community is generally doing. I see that one solution is doing shallow rendering to test on the main part of the component and not the child components using something like Enzyme.
Thoughts or other ideas?
Passing elements / components as props is a good idea. Having default props is a good idea too:
const Widget = ({
header = <div>Default Header.. </div>,
content = <div>Default Content.. </div>
}) =>
<div>
{header}
{content}
</div>
Then elsewhere in your app:
<Widget header={<Header title="Foo" />} content="content from props" />
No need to inject using connect
You can also pass a component, not just an element if you want to interact with props / send data back to parent:
const Widget = ({
Header = props => <div>Default Header.. </div>,
Content = props => <div>Default Content.. </div>
}) =>
<div>
<Header />
<Content />
</div>
Elsewhere:
<Widget Header={Header} Content={props => <Content />} />
As long as the component always renders the same thing it can be directly rendered as a child rather than the parent.
If all other portions of the Component remain constant and only the Header can be different across pages then you could actually implement it as an HOC instead of passing it as a props
const MyCompFactory = ({CustomHeader = DefaultHeader}) => {
return class Widget extends React.Component {
render() {
return (
<div>
<CustomHeader/>
<div>{this.props.importantContent}</div>
</div>
)
}
}
}
and use it like
const CustomComponent = MyCompFactory({CustomComponent: Header})
as long as testing is concerned in your case, you could just shallow render your component and then Search if the Header component is rendered something like
import Header from 'path/to/header'
const component = shallow(
<Widget {...customProps}/>
)
test('test' , () => {
expect(component.find(Header).exists()).toBe(true)
})

React lambda in render [duplicate]

I'm running lint with my React app, and I receive this error:
error JSX props should not use arrow functions react/jsx-no-bind
And this is where I'm running the arrow function (inside onClick):
{this.state.photos.map(tile => (
<span key={tile.img}>
<Checkbox
defaultChecked={tile.checked}
onCheck={() => this.selectPicture(tile)}
style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
/>
<GridTile
title={tile.title}
subtitle={<span>by <b>{tile.author}</b></span>}
actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
>
<img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
</GridTile>
</span>
))}
Is this a bad practice that should be avoided? And what's the best way to do it?
Why you shouldn't use inline arrow functions in JSX props
Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render.
Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.
Using an inline arrow function will cause PureComponents, and components that use shallowCompare in the shouldComponentUpdate method to rerender anyway. Since the arrow function prop is recreated each time, the shallow compare will identify it as a change to a prop, and the component will rerender.
As you can see in the following 2 examples - when we use inline arrow function, the <Button> component is rerendered each time (the console shows the 'render button' text).
Example 1 - PureComponent without inline handler
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Example 2 - PureComponent with inline handler
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Binding methods to this without inlining arrow functions
Binding the method manually in the constructor:
class Button extends React.Component {
constructor(props, context) {
super(props, context);
this.cb = this.cb.bind(this);
}
cb() {
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Binding a method using the proposal-class-fields with an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 preset or the Class properties transform to your babel configuration.
class Button extends React.Component {
cb = () => { // the class property is initialized with an arrow function that binds this to the class
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Function Components with inner callbacks
When we create an inner function (event handler for example) inside a function component, the function will be recreated every time the component is rendered. If the function is passed as props (or via context) to a child component (Button in this case), that child will re-render as well.
Example 1 - Function Component with an inner callback:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
To solve this problem, we can wrap the callback with the useCallback() hook, and set the dependencies to an empty array.
Note: the useState generated function accepts an updater function, that provides the current state. In this way, we don't need to set the current state a dependency of useCallback.
Example 2 - Function Component with an inner callback wrapped with useCallback:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Using inline functions like this is perfectly fine. The linting rule is outdated.
This rule is from a time when arrow functions were not as common and people used .bind(this), which used to be slow. The performance issue has been fixed in Chrome 49.
Do pay attention that you do not pass inline functions as props to a child component.
Ryan Florence, the author of React Router, has written a great piece about this:
https://reacttraining.com/blog/react-inline-functions-and-performance/
This is because an arrow function apparently will create a new instance of the function on each render if used in a JSX property. This might create a huge strain on the garbage collector and will also hinder the browser from optimizing any "hot paths" since functions will be thrown away instead of reused.
You can see the whole explanation and some more info at https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
Why shouldn't JSX props use arrow functions or bind?
Mostly, because inline functions can break memoization of optimized components:
Traditionally, performance concerns around inline functions in React have been related to how passing new callbacks on each render breaks shouldComponentUpdate optimizations in child components. (docs)
It is less about additional function creation cost:
Performance issues with Function.prototype.bind got fixed here and arrow functions are either a native thing or are transpiled by babel to plain functions; in both cases we can assume it’s not slow. (React Training)
I believe people claiming function creation is expensive have always been misinformed (React team never said this). (Tweet)
When is the react/jsx-no-bind rule useful?
You want to ensure, that memoized components work as intended:
React.memo (for function components)
PureComponent or custom shouldComponentUpdate (for class components)
By obeying to this rule, stable function object references are passed. So above components can optimize performance by preventing re-renders, when previous props have not changed.
How to solve the ESLint error?
Classes: Define the handler as method, or class property for this binding.
Hooks: Use useCallback.
Middleground
In many cases, inline functions are very convenient to use and absolutely fine in terms of performance requirements. Unfortunately, this rule cannot be limited to only memoized component types. If you still want to use it across-the-board, you could e.g. disable it for simple DOM nodes:
rules: {
"react/jsx-no-bind": [ "error", { "ignoreDOMComponents": true } ],
}
const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
To avoid creating new functions with the same arguments, you could memoize the function bind result, here is a simple utility named memobind to do it: https://github.com/supnate/memobind
You can remove this error by wrapping the function inside useCallback.
For those wondering when you need to pass data in the callback. Ex.:
const list = todos.map((todo, index) => (
<Todo
onClick={() => { onTodoClick(todo.id, todo.title, index) }
/>
));
Solution
According to the official documentation, you should do:
Move the function arguments into the children component:
const list = todos.map((todo, index) => (
<Todo
onClick={onTodoClick}
todoId={todo.id}
todoTitle={todo.title}
indexOnList={index}
/>
));
In the children component (<Todo />), pass the arguments in the call:
function Todo(props) {
// component properties
const { onClick, todoId, todoTitle, indexOnList } = props;
// we move the call from the parent to the children
const handleClick = useCallback(() => {
onClick(todoId, todoTitle, indexOnList);
}, [todoId, todoTitle, indexOnList]);
return (
<div onClick={handleClick}>
{/* the rest of the component remains the same */}
</div>
);
}
Is this the best solution?
I dislike this solution. You end up with parent's data and logic in the children component. This makes the children component dependent on the parent component, breaking the dependency rule.
That's a big no-no for me.
What I do is just disable this rule. According to Ryan Florence (React Router author), this is not a big deal anyway:
https://medium.com/#ryanflorence/react-inline-functions-and-performance-bdff784f5578
The new (in beta, jan 2023) React tutorial uses both functions and arrow functions as JSX props. This hints strongly at this not being a major concern.
You can use arrow functions using react-cached-handler library, no need to be worried about re-rendering performance :
Note : Internally it caches your arrow functions by the specified key,
no need to be worried about re-rendering!
render() {
return (
<div>
{this.props.photos.map((photo) => (
<Photo
key={photo.url}
onClick={this.handler(photo.url, (url) => {
console.log(url);
})}
/>
))}
</div>
);
}
Other features:
Named handlers
Handle events by arrow functions
Access to the key, custom arguments and the original event
Component rendering performace
Custom context for handlers
You may also see this this error if the function you are using in your onClick handler is a regular (non-inline) function defined outside of the render method but using the function keyword.
Declare your handler function as an arrow function using const, outside of you render method, and React will stop complaining...

Resources