Why is React.memo not working with useEffect? - javascript

I'm kind of new to react, so what i wanted was that, I have a toggle button to toggle a persons component and I have a cockpit component. But whenever I toggle the persons component, I don't want to always re-render the cockpit component.
So this is my Cockpit.js component file.
import React, { useEffect } from 'react';
import classes from './Cockpit.css';
const cockpit = props => {
useEffect(() => {
console.log('[Cockpit.js] useEffect');
// Http request...
setTimeout(() => {
alert('Saved data to cloud!');
}, 1000);
return () => {
console.log('[Cockpit.js] cleanup work in useEffect');
};
}, []);
useEffect(() => {
console.log('[Cockpit.js] 2nd useEffect');
return () => {
console.log('[Cockpit.js] cleanup work in 2nd useEffect');
};
});
// useEffect();
const assignedClasses = [];
let btnClass = '';
if (props.showPersons) {
btnClass = classes.Red;
}
if (props.personsLength <= 2) {
assignedClasses.push(classes.red); // classes = ['red']
}
if (props.personsLength <= 1) {
assignedClasses.push(classes.bold); // classes = ['red', 'bold']
}
return (
<div className={classes.Cockpit}>
<h1>{props.title}</h1>
<p className={assignedClasses.join(' ')}>This is really working!</p>
<button className={btnClass} onClick={props.clicked}>
Toggle Persons
</button>
</div>
);
};
export default React.memo(cockpit);
And this is my App.js
import React, { Component } from 'react';
import Persons from '../Components/Persons/Persons';
import classes from './App.css';
import Cockpit from '../Components/Cockpit/Cockpit'
class App extends Component {
constructor(props) {
super(props);
console.log("[App.js] constructor");
}
state = {
persons: [{id: "abc", name: "", age: 45},
{id: "azz", name: "", age: 56},
{id: "asq", name: "", age: 62}],
showPersons: false,
showCockpit: true
}
static getDerivedStateFromProps(props, state) {
console.log("[App.js] getDerivedStateFromProps", props)
return state;
}
componentDidMount() {
console.log('[App.js] componentDidMount')
}
shouldComponentUpdate(nextProps, nextState) {
console.log('[App.js] shouldCompoentUpdate');
return true;
}
componentDidUpdate() {
console.log('[App.js] componentDidUpdate')
}
deletePersonHandler = (i) => {
const persons = [...this.state.persons];
persons.splice(i, 1);
this.setState({persons: persons})
}
switchNameHandler = (newName) => {
this.setState({persons: [{name: newName, age: 50}, {name: "Aysha", age: 56}, {name: "Momma", age: 62}]})
}
nameSwitchHandler = (event, id) => {
const personIndex = this.state.persons.findIndex(p => {
return p.id === id;
})
const person = {...this.state.persons[personIndex]}
person.name = event.target.value;
const persons = [...this.state.persons]
persons[personIndex] = person;
this.setState({persons: persons})
}
togglePersonHandler = () => {
let doesChange = this.state.showPersons;
this.setState({showPersons: !doesChange})
}
render() {
console.log("[App.js] render");
let person = null;
if(this.state.showPersons) {
person = (<Persons
persons={this.state.persons}
clicked={this.deletePersonHandler}
changed={this.nameSwitchHandler} />
);
}
return (
<div className={classes.App}>
<button onClick={() => this.setState({showCockpit: false})}>Remove Cockpit</button>
{this.state.showCockpit ? (<Cockpit
title={this.props.appTitle}
showPersons={this.state.showPersons}
personsLength={this.state.persons.length}
clicked={this.togglePersonHandler} />) : null}
{person}
</div>
);
}
}
export default App;
But even when I toggle it, useEffect in cockpit component still console logs in the browser console when its not supposed to. I can't seem to find what I am doing wrong.
As you can see in this image the useEffect component in cockpit still renders in the console......
Browser Console

React.memo will do a shallow equal comparison on the props object by default. That means it will check every top level item in the props for equality and if any of them changed it will re-render.
When you click your persons toggle button it will change showPersons in your App component wich is also a prop that you pass to <Cockpit>. Therefore it will re-render even with React.memo. If it wouldn't re-render it wouldn't correctly update your Button class adding or removing classes.Red because this is dependent on the showPersons prop.
It has nothing to do with your useEffect inside of cockpit which will only get called after it re-renders but doesn't cause it to re-render in the first place.

On the click of Toggle Persons, you are changing the state in App Component.
This results in the re-rendering of the App and Cockpit components.
useEffect(() => {
console.log('[Cockpit.js] 2nd useEffect');
return () => {
console.log('[Cockpit.js] cleanup work in 2nd useEffect');
};
});
The above code will trigger every render as you haven't provided dependency.
To fix this, you need to add a dependency to the above code.

Since showPersons change it detects it as changed props.
You can add an equality function in React.memo that tells react when to consider the memoization stale:
// Will only rerender when someValue changes
export default React.memo(Cockpit, (oldProps, newProps) => oldProps.someValue === newProps.someValue)

Related

Converting componentDidMount into useEffect

I am trying to learn React hooks, and trying to convert existing codebase to use hooks, but I am confused.
Is it normal to set state inside useEffect? Would I be causing the dreaded infinite loop if I do so?
import React, { useState, useEffect } from 'react';
import App from 'next/app';
import Layout from './../components/Layout';
function MyApp({ Component, pageProps }) {
const [ cart, setCart ] = useState([]);
const addToCart = (product) => {
setCart((prevCartState) => {
return [ ...prevCartState, product ];
});
localStorage.setItem('cart', JSON.stringify(cart));
};
//mimicking componentDidMount to run some effect using useEffect
//useEffect(() => setCount((currentCount) => currentCount + 1), []);
useEffect(() => {
const cartCache = JSON.parse(localStorage.getItem('cart'));
//cart = cartCache; Good or bad?
cartCache || setCart(() =>{
});
}, []);
return <Component {...pageProps} />;
}
My original class based component:
/*
export default class MyApp extends App {
state = {
cart: []
}
componentDidMount = () => {
const cart = JSON.parse(localStorage.getItem('cart'));
if (cart) {
this.setState({
cart
});
}
};
addToCart = (product) => {
this.setState({
cart: [...this.state.cart, product]
});
localStorage.setItem('cart', JSON.stringify(this.state.cart));
}
render() {
const { Component, pageProps } = this.props
return (
<contextCart.Provider value={{ cart: this.state.cart, addToCart: this.addToCart }}>
<Layout>
<Component {...pageProps} />
</Layout>
</contextCart.Provider>
)
}
}*/
It's okey to set state inside useEffect as long as you don't listen to changes of the same field inside dependency array. In your particular case you are calling useEffect only once (since you have passed an empty dependency array).
useEffect(() => {
const cartCache = JSON.parse(localStorage.getItem('cart'));
if (cartCache) {
setCart(cartCache);
}
}, []);
Also would be cool to add the second useEffect to listen to cart changes and keep the localStorage up-to-date.
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(cart));
}, [cart]);
Because localStorage.getItem() is a synchronous call thus for this scenario you can also use the function callback version of useState in order to set initial value. In this way you don't need to use useEffect. Usually if it is possible I try to avoid introducing any new side effects in my functional components.
You can try as the following instead:
const [ cart, setCart ] = useState(() => {
const cartCache = JSON.parse(localStorage.getItem('cart'));
if (cartCache) {
return cartCache;
}
localStorage.setItem('cart', JSON.stringify([]));
return [];
});
In this way if the cart element is missing from the localStorage the code will create it with a default [] empty array and set it also for your state. Other case it will set your state the value from storage.
Please note: Also I agree with the answer from kind user here in terms of listening cart state changes to keep localStorage up to date with useEffect. My suggestion is only for the initial state.

Why is my onChange event not working in React?

I am coding a simple search input component for an app that will eventually become larger, but I am at a loss for why the onChange prop associated with it isn't being called. Here I will show my search input component and the app component into which I import it:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class SearchInput extends Component {
static defaultProps = {
onChange: () => Promise.resolve(),
}
static propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
}
render() {
const { value } = this.props;
return (
<input className="search-input" type='text' onChange={this.handleChange} value={value}/>
)
}
handeChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
}
And then here's my main app component (very simple still, and keep in mind that I have list-rendering functionality, but that isn't where my issue lies). I'm pretty sure the issue lies somewhere in the handleSearchDidChange method that I wrote up and tacked onto the onChange prop for the SearchInput component:
import React, { Component } from 'react';
import Container from './components/container'
import List from './components/list'
import SearchInput from './components/search-input';
// Styles
import './App.css';
export default class App extends Component {
constructor(props){
super(props)
this.state = {
searchValue: undefined,
isSearching: false,
}
// this.handleSearchDidChange = this.handleSearchDidChange.bind(this);
}
render() {
// in the main render, we render the container component (yet to be styled)
// and then call renderList inside of it. We need "this" because this is
// a class-based component, and we need to tell the component that we are
// using the method associated with this class
return (
<div className="App">
<Container>
{this.renderSearchInput()}
{this.renderList()}
</Container>
</div>
);
}
renderSearchInput = () => {
const { searchValue } = this.state;
return (<SearchInput onChange={this.handleSearchDidChange} value={searchValue}/>)
}
renderList = () => {
// return the list component, passing in the fetchData method call as the data prop
// since this prop type is an array and data is an array-type prop, this is
// acceptable
return <List data={this.fetchData()}/>
}
// possibly something wrong with this method?
handleSearchDidChange = (e) => {
const { target } = e;
const { value } = target;
this.setState({
searchValue: value,
isSearching: true,
});
console.log('value: ', value);
console.log('searchValue: ', this.state.searchValue);
console.log('-------------------------')
}
fetchData = () => {
// initialize a list of items
// still wondering why we cannot put the listItems constant and the
// return statement inside of a self-closing setTimeout function in
// order to simulate an API call
const listItems = [
{title: 'Make a transfer'},
{title: 'Wire money'},
{title: 'Set a travel notice'},
{title: 'Pop money'},
{title: 'Edit travel notice'},
{title: 'test money things'},
{title: 'more test money things'},
{title: 'bananas'},
{title: 'apples to eat'},
{title: 'I like CocaCola'},
{title: 'Christmas cookies'},
{title: 'Santa Claus'},
{title: 'iPhones'},
{title: 'Technology is amazing'},
{title: 'Technology'},
{title: 'React is the best'},
];
// return it
return listItems;
}
You have a typo! Missing the "l" in handleChange :)
handleChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
i run your code in sandBox:
https://codesandbox.io/s/onchange-problem-37c4i
there is no issue with your functionality as far as i can see.
but in this case if onChange dose not work for you is because maybe inside of < SearchInput /> component you don't pass the value up to the parent element.
check the sandBox and notice to the SearchInput1 and SearchInput2

Table Component is not re-rendered after state update

i'm having a table component for displaying some data. After dispatching an action the table data in the state are channging. However my table component is not updated. It is updated only when i click on another radio button in another row of my table. I want my component to rerender when the data are changed. Here is my code:
const mapStateToProps = state => ({
evaluationData: evaluationResultsSelector(state)
});
const mapDispatchToProps = dispatch => ({
setSelectedEvaluationRecord: record =>
dispatch(setSelectedEvaluationRecord(record))
});
export default connect(mapStateToProps,
mapDispatchToProps
EvaluationDataTable,
);
and my component is this:
import React from 'react';
import Table from 'antd/lib/table';
import 'antd/lib/table/style/css';
import "antd/dist/antd.css";
import { columnEvaluation } from './evaluationDataStructure';
class EvaluationDataTable extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowKeys: [0], // Check here to configure the default column
};
}
// shouldComponentUpdate(prevProps, prevState) {
// return (prevProps.results !== this.props.results || prevState.selectedRowKeys !== this.state.selectedRowKeys);
// }
onRowChange = selectedRowKeys => {
if (selectedRowKeys.length > 1) {
const lastSelectedRowIndex = [...selectedRowKeys].pop();
this.setState({ selectedRowKeys: lastSelectedRowIndex });
}
this.setState({ selectedRowKeys });
};
onRowSelect = (record) => {
this.props.setSelectedEvaluationRecord(record)
};
render() {
const { selectedRowKeys } = this.state;
const rowSelection = {
type: 'radio',
selectedRowKeys,
onChange: this.onRowChange,
onSelect: this.onRowSelect
};
return (
<React.Fragment>
<div style={{ marginBottom: 16 }} />
<Table
rowSelection={rowSelection}
columns={columnEvaluation}
dataSource={this.props.evaluationData}
/>
</React.Fragment>
);
}
}
export default EvaluationDataTable;
When i click in another row the table is rerendered as my setState is triggered but when the data are channged the table is not rerendered. Only when i click in another row. How to deal with it? Thanks a lot
Also my reducer which mutates the table is this:
case ACTION_TYPES.EDIT_EVALUATION_RESULTS: {
const evaluationResults = state.evaluationResults;
const editedRecord = action.payload.editedEvaluationData;
evaluationResults.forEach((item, i) => {
if (item.id === editedRecord.id) {
evaluationResults[i] = editedRecord;
}
});
return {
...state,
evaluationResults
};
}
Problem was here as OP has already deduced.
const evaluationResults = state.evaluationResults;
This was causing a state-mutation which goes against Redux principles. Although the state values were being updated in OP's proceeding code, the changes were being made to the same, initial object in reference. Redux does not register it as a new-state so it found no need to re-render our component. To get your connected-component to re-render we need a completely new redux-state.
To achieve this, we need to create a brand-new copy of evaluationResults like so and then the OP's feature will work as expected:
const evaluationResults = [...state.evaluationResults];

React Context API and avoiding re-renders

I have updated this with an update at the bottom
Is there a way to maintain a monolithic root state (like Redux) with multiple Context API Consumers working on their own part of their Provider value without triggering a re-render on every isolated change?
Having already read through this related question and tried some variations to test out some of the insights provided there, I am still confused about how to avoid re-renders.
Complete code is below and online here: https://codesandbox.io/s/504qzw02nl
The issue is that according to devtools, every component sees an "update" (a re-render), even though SectionB is the only component that sees any render changes and even though b is the only part of the state tree that changes. I've tried this with functional components and with PureComponent and see the same render thrashing.
Because nothing is being passed as props (at the component level) I can't see how to detect or prevent this. In this case, I am passing the entire app state into the provider, but I've also tried passing in fragments of the state tree and see the same problem. Clearly, I am doing something very wrong.
import React, { Component, createContext } from 'react';
const defaultState = {
a: { x: 1, y: 2, z: 3 },
b: { x: 4, y: 5, z: 6 },
incrementBX: () => { }
};
let Context = createContext(defaultState);
class App extends Component {
constructor(...args) {
super(...args);
this.state = {
...defaultState,
incrementBX: this.incrementBX.bind(this)
}
}
incrementBX() {
let { b } = this.state;
let newB = { ...b, x: b.x + 1 };
this.setState({ b: newB });
}
render() {
return (
<Context.Provider value={this.state}>
<SectionA />
<SectionB />
<SectionC />
</Context.Provider>
);
}
}
export default App;
class SectionA extends Component {
render() {
return (<Context.Consumer>{
({ a }) => <div>{a.x}</div>
}</Context.Consumer>);
}
}
class SectionB extends Component {
render() {
return (<Context.Consumer>{
({ b }) => <div>{b.x}</div>
}</Context.Consumer>);
}
}
class SectionC extends Component {
render() {
return (<Context.Consumer>{
({ incrementBX }) => <button onClick={incrementBX}>Increment a x</button>
}</Context.Consumer>);
}
}
Edit: I understand that there may be a bug in the way react-devtools detects or displays re-renders. I've expanded on my code above in a way that displays the problem. I now cannot tell if what I am doing is actually causing re-renders or not. Based on what I've read from Dan Abramov, I think I'm using Provider and Consumer correctly, but I cannot definitively tell if that's true. I welcome any insights.
There are some ways to avoid re-renders, also make your state management "redux-like". I will show you how I've been doing, it far from being a redux, because redux offer so many functionalities that aren't so trivial to implement, like the ability to dispatch actions to any reducer from any actions or the combineReducers and so many others.
Create your reducer
export const initialState = {
...
};
export const reducer = (state, action) => {
...
};
Create your ContextProvider component
export const AppContext = React.createContext({someDefaultValue})
export function ContextProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState)
const context = {
someValue: state.someValue,
someOtherValue: state.someOtherValue,
setSomeValue: input => dispatch('something'),
}
return (
<AppContext.Provider value={context}>
{props.children}
</AppContext.Provider>
);
}
Use your ContextProvider at top level of your App, or where you want it
function App(props) {
...
return(
<AppContext>
...
</AppContext>
)
}
Write components as pure functional component
This way they will only re-render when those specific dependencies update with new values
const MyComponent = React.memo(({
somePropFromContext,
setSomePropFromContext,
otherPropFromContext,
someRegularPropNotFromContext,
}) => {
... // regular component logic
return(
... // regular component return
)
});
Have a function to select props from context (like redux map...)
function select(){
const { someValue, otherValue, setSomeValue } = useContext(AppContext);
return {
somePropFromContext: someValue,
setSomePropFromContext: setSomeValue,
otherPropFromContext: otherValue,
}
}
Write a connectToContext HOC
function connectToContext(WrappedComponent, select){
return function(props){
const selectors = select();
return <WrappedComponent {...selectors} {...props}/>
}
}
Put it all together
import connectToContext from ...
import AppContext from ...
const MyComponent = React.memo(...
...
)
function select(){
...
}
export default connectToContext(MyComponent, select)
Usage
<MyComponent someRegularPropNotFromContext={something} />
//inside MyComponent:
...
<button onClick={input => setSomeValueFromContext(input)}>...
...
Demo that I did on other StackOverflow question
Demo on codesandbox
The re-render avoided
MyComponent will re-render only if the specifics props from context updates with a new value, else it will stay there.
The code inside select will run every time any value from context updates, but it does nothing and is cheap.
Other solutions
I suggest check this out Preventing rerenders with React.memo and useContext hook.
I made a proof of concept on how to benefit from React.Context, but avoid re-rendering children that consume the context object. The solution makes use of React.useRef and CustomEvent. Whenever you change count or lang, only the component consuming the specific proprety gets updated.
Check it out below, or try the CodeSandbox
index.tsx
import * as React from 'react'
import {render} from 'react-dom'
import {CountProvider, useDispatch, useState} from './count-context'
function useConsume(prop: 'lang' | 'count') {
const contextState = useState()
const [state, setState] = React.useState(contextState[prop])
const listener = (e: CustomEvent) => {
if (e.detail && prop in e.detail) {
setState(e.detail[prop])
}
}
React.useEffect(() => {
document.addEventListener('update', listener)
return () => {
document.removeEventListener('update', listener)
}
}, [state])
return state
}
function CountDisplay() {
const count = useConsume('count')
console.log('CountDisplay()', count)
return (
<div>
{`The current count is ${count}`}
<br />
</div>
)
}
function LangDisplay() {
const lang = useConsume('lang')
console.log('LangDisplay()', lang)
return <div>{`The lang count is ${lang}`}</div>
}
function Counter() {
const dispatch = useDispatch()
return (
<button onClick={() => dispatch({type: 'increment'})}>
Increment count
</button>
)
}
function ChangeLang() {
const dispatch = useDispatch()
return <button onClick={() => dispatch({type: 'switch'})}>Switch</button>
}
function App() {
return (
<CountProvider>
<CountDisplay />
<LangDisplay />
<Counter />
<ChangeLang />
</CountProvider>
)
}
const rootElement = document.getElementById('root')
render(<App />, rootElement)
count-context.tsx
import * as React from 'react'
type Action = {type: 'increment'} | {type: 'decrement'} | {type: 'switch'}
type Dispatch = (action: Action) => void
type State = {count: number; lang: string}
type CountProviderProps = {children: React.ReactNode}
const CountStateContext = React.createContext<State | undefined>(undefined)
const CountDispatchContext = React.createContext<Dispatch | undefined>(
undefined,
)
function countReducer(state: State, action: Action) {
switch (action.type) {
case 'increment': {
return {...state, count: state.count + 1}
}
case 'switch': {
return {...state, lang: state.lang === 'en' ? 'ro' : 'en'}
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
function CountProvider({children}: CountProviderProps) {
const [state, dispatch] = React.useReducer(countReducer, {
count: 0,
lang: 'en',
})
const stateRef = React.useRef(state)
React.useEffect(() => {
const customEvent = new CustomEvent('update', {
detail: {count: state.count},
})
document.dispatchEvent(customEvent)
}, [state.count])
React.useEffect(() => {
const customEvent = new CustomEvent('update', {
detail: {lang: state.lang},
})
document.dispatchEvent(customEvent)
}, [state.lang])
return (
<CountStateContext.Provider value={stateRef.current}>
<CountDispatchContext.Provider value={dispatch}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
)
}
function useState() {
const context = React.useContext(CountStateContext)
if (context === undefined) {
throw new Error('useCount must be used within a CountProvider')
}
return context
}
function useDispatch() {
const context = React.useContext(CountDispatchContext)
if (context === undefined) {
throw new Error('useDispatch must be used within a AccountProvider')
}
return context
}
export {CountProvider, useState, useDispatch}
To my understanding, the context API is not meant to avoid re-render but is more like Redux. If you wish to avoid re-render, perhaps looks into PureComponent or lifecycle hook shouldComponentUpdate.
Here is a great link to improve performance, you can apply the same to the context API too

Why does this component re-render completely when changing a single item with a unique key?

This is a reoccurring problem for me… Trying to figure out why an update to a single item in a component results in the entire component re-rendering. If I have a CSS fade in transition on the component, it fades in again when changing a child of the component.
I have a list of items, each with a link. Clicking the link adds the item to the cart. I have it set up to put that item in a “loading” state until the cart action is successful.
This used to work perfectly, but now it just re-renders the entire page, making it disappear for a second then reappear. I’m not entirely sure why.
This is the code stripped down to its basic bits:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import autobind from 'class-autobind';
import Responsive from 'components/Responsive';
// Selectors
import { createStructuredSelector } from 'reselect';
import { selectCartLoading, selectCartMap, selectFavorites } from 'containers/App/selectors';
import { selectPackages } from 'store/fonts/selectors';
// Actions
import { addToCart } from 'containers/App/actions';
export class Packages extends React.Component {
constructor() {
super();
autobind(this);
}
state = {
loadingID: 0
}
componentWillReceiveProps(nextProps) {
if (this.props.cartLoading === true && nextProps.cartLoading === false) {
this.setState({ loadingID: 0 });
}
}
onAddToCart(e) {
e.preventDefault();
const { onAddToCart } = this.props;
const id = e.currentTarget.dataset.package;
const packageData = {
type: 'package',
id,
quantity: 1
};
onAddToCart(packageData);
this.setState({ loadingID: id });
}
render() {
const { cartMapping, packages } = this.props;
if (!packages) { return null; }
return (
<Responsive>
<div>
<ul>
{ packages.map((pack) => {
const inCart = !!cartMapping[parseInt(pack.id, 10)];
const isFavorited = !favorites ? false : !!find(favorites.favorites, (favorite) => parseInt(pack.id, 10) === favorite.items.id);
return (
<li key={ pack.id }>
<Icon iconName="heart" onClick={ (e) => this.onAddFavorite(e, pack) } />
<span>{ pack.name }</span>
{ inCart && <span>In Cart</span> }
{ !inCart && <a data-package={ pack.id } href="/" onClick={ this.onAddToCart }>Add to Cart</a> }
</li>
);
})}
</ul>
</div>
</Responsive>
);
}
}
Packages.propTypes = {
cartLoading: PropTypes.bool,
cartMapping: PropTypes.object,
onAddToCart: PropTypes.func.isRequired,
packages: PropTypes.array
};
Packages.defaultProps = {
cartLoading: null,
cartMapping: null,
packages: null
};
const mapStateToProps = createStructuredSelector({
cartLoading: selectCartLoading(),
cartMapping: selectCartMap(),
packages: selectPackages()
});
function mapDispatchToProps(dispatch) {
return {
onAddToCart: (data) => dispatch(addToCart(data)),
dispatch
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Packages);
So why does clicking on <a data-package={ pack.id } href="/" onClick={ this.onAddToCart }>Add to Cart</a> result in a complete component re-render?
In your onAddToCart function you are setting the state of the component which will by default trigger a re-render of the component. If you need to set the state but not cause a re-render you can add a shouldComponentUpdate() function and check the changes before issuing a re-render to the component.
Find out more about shouldComponentUpdate() and the rest of the component lifecycle here

Categories