Stateless functional component vs class based: why won't my props change? - reactjs

I have a dumb component that renders a link. I have a simple ternary checking if the prop, props.filter is the same as its props.filterType, then it will render a <span> instead of a <a> tag.
This dumb component is being passed filter from a parent component, which is connected to a Redux store.
The bug that I'm running into is this: my parent component does receive changes/updates to filter, I am console logging it and able to see that in the parent component filter does change.
However in my dumb component, I am console.logging props.filter, which doesn't change at all. On the flip side, using the React dev tools and inspecting the component and checking its props, it DOES change. WHAT?!
Changing the stateless functional component to a class does work. The console.log(props.filter) with the component as a class, does change.
Here is the code for the component, both as a stateless functional and a class:
import React from 'react';
import './styles.css';
/* props.filter DOES CHANGE HERE */
class FilterLink extends React.Component {
render() {
console.log('this.props.filter: ', this.props.filter);
console.log('this.props.filterType: ', this.props.filterType);
console.log(this.props.filter === this.props.filterType);
return (
this.props.filter === this.props.filterType ?
<span className='active-link'>{this.props.children}</span>
:
<a id={this.props.filterType} className='link' href='' onClick={this.props.setFilter}>
{this.props.children}
</a>
);
}
};
/* props.filter DOESN'T CHANGE HERE */
const FilterLink = props => ({
render() {
console.log('props.filter: ', props.filter);
console.log('props.filterType: ', props.filterType);
console.log(props.filter === props.filterType);
return (
props.filter === props.filterType ?
<span className='active-link'>{props.children}</span>
:
<a id={props.filterType} className='link' href='' onClick={props.setFilter}>
{props.children}
</a>
);
},
});
export default FilterLink;
I think there is a huge hole in my understanding of stateless functional components. Any help or advice or direction would be greatly appreciated.
Thank you,

You implemenentation of stateless component is just wrong. It should do render instead of returning an object with render method.
const FilterLink = props => {
console.log('props.filter: ', props.filter);
console.log('props.filterType: ', props.filterType);
console.log(props.filter === props.filterType);
return (
props.filter === props.filterType ?
<span className='active-link'>{props.children}</span>
:
<a id={props.filterType} className='link' href='' onClick={props.setFilter}>
{props.children}
</a>
);
};

Related

Passing ref to child component to animate with GSAP

I'm new to React and i am trying to integrate GSAP to animate a child component using refs. The process to try and animate worked fine before I seperated out the elements into their different components!
There are no error codes from React, but I do get the following console error:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
I've looked into the forwardRef mentioned but not sure if it's right for what i'm trying to achieve.
/**
* Parent Component
*/
import React, { Component } from 'react';
import Spirit from './Spirit';
import { TweenMax, Power1 } from 'gsap';
class SpiritResults extends Component {
constructor(props) {
super(props);
this.multiElements = [];
}
componentDidMount() {
TweenMax.staggerFromTo(
this.multiElements,
0.5,
{ autoAlpha: 0 },
{ autoAlpha: 1, ease: Power1.easeInOut },
0.1
);
}
render() {
return (
<ul className="mm-results">
{this.props.spirits.map(({ id, title, featImg, link, slug, index }) => (
<Spirit
key={id}
slug={slug}
title={title}
featImg={featImg}
link={link}
ref={li => (this.multiElements[index] = li)}
/>
))}
</ul>
);
}
}
/**
* Child Component
*/
import React from 'react';
const Spirit = ({ slug, link, featImg, title, index }) => (
<li id={slug} className="mm-item" ref={index}>
<a href={link}>
<div className="inner">
<img src={featImg} alt={title} />
<h3>{title}</h3>
</div>
</a>
</li>
);
export default Spirit;
Any tips that can be given to get the animation to work would be appreciated. If there are any better ways of animating react with GSAP please let me know your thoughts.
Thanks
There are a few mistakes here.
ref is not a prop. You can't just pass it to a custom component and access it like props.ref
You're appending the wrong ref inside <li>, index is not a valid ref object
To pass down a ref to a custom component you need to use React.forwardRef and append the ref to your Child's <li>. Something like this
const Parent = () =>{
const ref = useRef(null)
return <Child ref={ref} title='hey i\'m a prop'/>
}
const Child = React.forwardRef((props, ref) =>(
<li ref={ref}>
{props.title}
</li>
))

Re-render React descendant components without re-rendering ancestors

I'm trying to build a Navbar using React 16.8.3. I would like to use composition to pass the Navbar content instead of passing a config object via props, in order to have more flexibility. Something like this:
<Navbar>
<NavItem>Some label</NavItem>
<NavItem>
<span>Some arbitrary content</span>
<NavItem>
</Navbar>
instead of:
const navItems = [
{
label: 'Some label'
},
{
label: 'Some other label'
}
]
<Navbar items={navItems} />
So far the Navbar is working fine. I've added some logic in the shouldComponentUpdate method to prevent multiple re-renders:
shouldComponentUpdate(nextProps) {
return nextProps.selectedItem !== this.props.selectedItem;
}
so the Navbar only re-renders when its selected item changes, and not, for instance, when the Navbar parent re-renders.
Problem is that one NavItem contains a badge with a task count that must be updated whenever the user does some tasks:
Todos screenshot
and the item markup is:
<Navbar>
<NavItem>
<div className="has-badge">
<span>Label</span>
<span className="badge">{this.props.toDoCount}</span>
</div>
</NavItem>
</Navbar>
this.props.toDoCount is a prop of the Navbar parent, and not of the Navbar itself.
How can I update the badge number without re-rendering the whole Navbar?. So far I've tried creating a Badge component, adding some state, and a method to update the badge number using a ref in the Navbar parent:
import React, { PureComponent } from 'react';
interface BadgeProps {
number: number;
}
class Badge extends PureComponent<BadgeProps> {
state = {
number: 0
};
setCount(number) {
this.setState({
number
});
}
render() {
return <span className="badge">{this.state.number}</span>;
}
}
In the Navbar parent:
private todos = createRef<Badge>();
...
componentDidUpdate(prevProps: EhrProps) {
this.todos.current.setCount(toDosCount);
}
and it's working, but... is there an easier or cleaner way of doing this in React??
Thanks!
PS: We are using Redux in the project, but I would like to avoid using the store in the Navbar or its items.
EDIT:
I'm using React.children and React.cloneElement in the Navbar's render method:
render() {
const { className, children, selectedItem, ...rest } = this.props;
const classes = classNames(
{
navbar: true
},
className
);
return (
<nav className={classes} {...rest}>
{React.Children.map(children, child => {
if (child.type === NavItem) {
return React.cloneElement(child, {
onClick: this.handleItemClick,
selected: child.props.name === selectedItem
});
}
return child;
})}
</nav>
);
}
And each NavItem handles its own render:
return (
<div className={classes} onClick={handleClick} onKeyPress={handleKeyPress} role="menuitem" tabIndex={0}>
{children}
</div>
);
Presumably, you have some code for the the Navbar component that looks a bit like this.
class Navbar extends React.Component<Props> {
render() {
return (
<div>
{this.props.navItem.map(item => <NavItem key={item.label}>{item.label}</NavItem>)};
</div>
);
}
}
and then some code to render each child NavItem.
To make the component fairly efficient, it's sufficient to have the whole of Navbar rerender, but only not re-render each child.
What I would recommend is:
Make each child of Navbar be rendered in its own component; in the component above it's called NavItem
Use either componentShouldUpdate or React.PureComponent (look into this! Once you understand it, it's a great general solution to use by default instead of React.Component for every component) to make sure that each child only re-renders when its value changes
What will happen when you update the badge for the single NavItem is that Navbar will re-render. Most of the NavItems will see their Props haven't changed, and not re-render. The single child of Navbar that has the badge will have changed, and will re-render. With this, the real overhead is actually quite low.
If your Navbar has a ton of children or your badge for that single child changes a lot, you can probably optimize it more by using React.Context or Redux to pass in the value for that single child, but that feels messy and seems like premature optimization.
Good luck!

How to check whether React updates dom or not?

I wanted to check how to react does reconciliation so I updated the inner HTML of id with the same text. Ideally, it shouldn't update the dom but it is paint reflashing in chrome.
I have tried paint reflashing in chrome it is showing green rectangle over that same text
import React from 'react';
function App() {
return (
<div >
<p id="abc" key="help">abc is here</p>
<button onClick={function () {
// document.getElementById("may").innerHTML = "";
document.getElementById("abc").innerHTML = "abc is here";
}} > Btn</button>
</div>
);
}
export default App;
Expected result should be that paint reflashing shouldn't happen but it is happening.
You are not using React here to update the text of your p tag but directly updating the DOM with JavaScript.
So React reconciliation algorithm doesn't even run here.
In React, the output HTML is a result of the state and the props of your component.
When a change in state or props is detected, React runs the render method to check if it needs to update the DOM. So, in order to do this check, you need to store the parameters that determine your view in state or props.
Given your example, we could save the text you want to show in the p tag in the state of your component (using hooks):
import React, { useState } from 'react';
function App () {
const [text, setText] = useState('abc is here');
render() {
return (
<div >
<p id="abc" key="help">{this.state.text}</p>
<button onClick={() => setText('abc is here') }>Btn</button>
</div>
);
}
}
export default App;
If you are using a version of React that does not support hooks, you will need to transform your functional component into a class to use state:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = { text: 'abc is here' };
}
render() {
return (
<div >
<p id="abc" key="help">{this.state.text}</p>
<button onClick={() => this.setState({ text: 'abc is here' }) }>Btn</button>
</div>
);
}
}
export default App;

React Hooks function component with controlled rendering based on state/prop values

One of the benefits of being able to use shouldComponentUpdate on a React Class component is the ability to control the render based on a condition rather than just a change in state/prop values.
What is the preferred way to make this optimization using react hooks in a function component?
In the example below, the class component does not re-render if it is (or is staying) in a closed state, even if it has new children.
class DrawerComponent extends React.Component {
static propTypes = {
children: PropTypes.any,
}
state = {
isOpen: false,
}
// only re-render if the drawer is open or is about to be open.
shouldComponentUpdate(nextProps, nextState) {
return this.state.isOpen || nextState.isOpen;
}
toggleDrawer = () => {
this.setState({isOpen: !this.state.isOpen});
};
render() {
return (
<>
<div onClick={this.toggleDrawer}>
Drawer Title
</div>
<div>
{this.state.isOpen ? this.props.children : null}
</div>
</>
)
}
}
Function component counterpart (without optimization):
function DrawerComponent({ children }) {
const [isOpen, setIsOpen] = useState(false);
function toggle() {
setIsOpen(!isOpen);
}
return (
<>
<div onClick={toggle}>
Drawer Title
</div>
<div>{isOpen ? children : null}</div>
</>
);
}
In this example, in my opinion there's no need for a shouldComponentUpdate optimization. It will already be fast since you're not rendering the children when the drawer is closed. The cost of running the functional component will be fairly negligible.
That said, if you did want to implement the equivalent behavior in a functional component, you could use React.memo and supply a custom areEqual function: https://reactjs.org/docs/react-api.html#reactmemo.

React constructor called only once for same component rendered twice

I expected this toggle to work but somehow the constructor of component <A/> is called only once. https://codesandbox.io/s/jvr720mz75
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
state = { toggle: false };
render() {
const { toggle } = this.state;
return (
<div>
{toggle ? <A prop={"A"} /> : <A prop={"B"} />}
<button onClick={() => this.setState({ toggle: !toggle })}>
toggle
</button>
</div>
);
}
}
class A extends Component {
constructor(props) {
super(props);
console.log("INIT");
this.state = { content: props.prop };
}
render() {
const { content } = this.state;
return <div>{content}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I already found a workaround https://codesandbox.io/s/0qmnjow1jw.
<div style={{ display: toggle ? "none" : "block" }}>
<A prop={"A"} />
</div>
<div style={{ display: toggle ? "block" : "none" }}>
<A prop={"B"} />
</div>
I want to understand why the above code is not working
In react if you want to render same component multiple times and treat them as different then you need to provide them a unique key. Try the below code.
{toggle ? <A key="A" prop={"A"} /> : <A key="B" prop={"B"} />}
Since that ternary statement renders results in an <A> component in either case, when the <App>'s state updates and changes toggle, React sees that there is still an <A> in the same place as before, but with a different prop prop. When React re-renders it does so by making as few changes as possible. So since this is the same class of element in the same place, React doesn't need to create a new element when toggle changes, only update the props of that <A> element.
Essentially, the line
{toggle ? <A prop="A"/> : <A prop="B"/> }
is equivalent to
<A prop={ toggle ? "A" : "B" }/>
which perhaps more clearly does not need to create a new <A> component, only update the existing one.
The problem then becomes that you set the state.content of the <A> using props.prop in the constructor, so the state.content is never updated. The cleanest way to fix this would be to use props.prop in the render method of the <A> component instead of state.content. So your A class would look like this:
class A extends Component {
render() {
const { prop } = this.props;
return <div>{ prop }</div>;
}
}
If you must take the prop prop and use it in the <A> component's state, you can use componentDidUpdate. Here's an example:
class A extends Component {
constructor(props) {
super(props);
this.state = {content: props.prop};
}
componentDidUpdate(prevProps) {
if (prevProps.prop !== this.props.prop) {
this.setState({content: this.props.prop});
}
}
render() {
const { content } = this.state;
return <div>{ content }</div>
}
}
React will only call the constructor once. That's the expected outcome.
Looks like you're trying to update the state of the component A based on the props.
You could either use the prop directly or use the componentDidUpdate lifecycle method, as Henry suggested. Another way is using the static method getDerivedStateFromProps to update the state based on the prop passed.
static getDerivedStateFromProps(props, state) {
return ({
content: props.prop
});
}

Resources