In my app, I want to pass the global store of my application to the child components via context. For the sake of an example, I have created 2 child components namely Child1 and Child2, and passing increment and decrement counter functions along with the corresponding counter values to them. Child1 is responsible for incrementing counter1 and child2 for decrementing counter2. When I am invoking the increment/decrement function in the components, the other component is uselessly getting re-rendered along with the parent. How can I prevent this from happening?
Please find the above use-case here
Below is the code for the same,
App.js
import React, { useState, useCallback } from 'react';
import './style.css';
import { UserContext } from './Context.js';
import Child1 from './Child1';
import Child2 from './Child2';
export default function App() {
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const IncrementCounter = useCallback(
() => setCounter1((prevState) => prevState + 1),
[setCounter1]
);
const DecrementCounter = useCallback(
() => setCounter2((prevState) => prevState - 1),
[setCounter2]
);
const store = {
child1: {
counter1,
IncrementCounter,
},
child2: {
counter2,
DecrementCounter,
},
};
console.log('App re-rendering');
return (
<UserContext.Provider value={store}>
<Child1 />
<Child2 />
</UserContext.Provider>
);
}
Child1.js
import React, { useContext } from 'react';
import { UserContext } from './Context.js';
const Child1 = () => {
const store = useContext(UserContext);
const { child1 } = store;
console.log('Child1 Re-rendering');
return (
<div>
<p>{`Counter1 value : ${child1.counter1}`}</p>
<button onClick={child1.IncrementCounter}>Increment</button>
</div>
);
};
export default React.memo(Child1);
Child2.js
import React, { useContext } from 'react';
import { UserContext } from './Context.js';
const Child2 = () => {
const store = useContext(UserContext);
const { child2 } = store;
console.log('Child2 Re-rendering');
return (
<div>
<p>{`Counter2 value : ${child2.counter2}`}</p>
<button onClick={child2.DecrementCounter}>Decrement</button>
</div>
);
};
export default React.memo(Child2);
Context.js
import React from 'react';
const UserContext = React.createContext();
export { UserContext };
Related
I was having a parent component named Cart.jsx in react and the child component named Card.jsx.
The Parent component looks like this.
Cart.jsx
import React, { useState, useEffect, useContext } from "react";
import Card from "../../Components/Card";
function Cart() {
const cart = [**Array of Objects**]
const [total,setTotal] = useState([]);
return (
<div className="cart__Items">
<Card item={cart[0]} />;
<Card item={cart[1]} />;
<Card item={cart[2]} />;
</div>
)
}
export default Cart;
And the Card Component looks as follows
Card.jsx
import React, { useState } from "react";
function Card() {
const [price,setPrice] = useState(0);
// in-between implemented some function to calculate price value.
return (
<div>
// rendering code
</div>
)
}
export default Card;
Now the problem is, how do I get the price data of each child component and store them in the total array of the parent component.
Here the parent has 3 Cardcomponents, I need to get the price data from each component and store them in the Cart component
Here is the code. I hope this might help
import React, { useState, useEffect, useContext } from "react";
import Card from "../../Components/Card";
function Cart() {
const cart = [**Array of Objects**]
const [total,setTotal] = useState(0);
return (
<div className="cart__Items">
{cart.map(crt =><Card item={crt} total={total} setTotal={setTotal} />}
</div>
)
}
export default Cart;
import React, { useState } from "react";
function Card(props) {
const [price,setPrice] = useState(0);
const {setTotal, total} = props
useEffect(()=>{
setTotal(total+price)
},[])
// in-between implemented some function to calculate price value.
return (
<div>
// rendering code
</div>
)
}
export default Card;
import React, { useState, useEffect, useContext } from "react";
import Card from "../../Components/Card";
function Cart() {
const cart = [**Array of Objects**]
const [total,setTotal] = useState([]);
return (
<div className="cart__Items">
{cart.map((item) => (<Card item={item} setTotal={setTotal} />))}
</div>
)
}
export default Cart;```
Now you have access to the setTotal function inside each card Item from which you can update the parent state of "total".
If you feel like the individual prices are to be calculated by the child you should use an event handler prop :
Cart.jsx
import React, { useState, useEffect, useContext } from "react";
import Card from "../../Components/Card";
function Cart() {
const cart = [**Array of Objects**]
const [total,setTotal] = useState(0);
const handlePriceCalculated = price => {
setTotal(total + price);
}
return (
<div className="cart__Items">
<Card item={cart[0]} onPriceCalculated={handlePriceCalculated} />
<Card item={cart[1]} onPriceCalculated={handlePriceCalculated} />
<Card item={cart[2]} onPriceCalculated={handlePriceCalculated} />
</div>
)
}
export default Cart;
Card.jsx
import React, { useState } from "react";
function Card({
onPriceCalculated
}) {
const [price,setPrice] = useState(0);
// in-between implemented some function to calculate price value.
...
setPrice(calculatedValue)
onPriceCalculated(calculatedValue)
...
return (
<div>
// rendering code
</div>
)
}
export default Card;
Giving the responsability to the child to set the total is a bad practise and will result in your components not to be reusable as they would be too hardly coupled.
Consider the code :
APP.JS
import React, { useState, useMemo } from 'react';
import Counter from './components/Counter';
import './App.css';
function App() {
const [countA, setCountA] = useState(0);
const incrementA = () => {
setCountA(countA + 1);
};
// const memoCounter = useMemo(() => {
// return <Counter />;
// }, []);
return (
<div className='App'>
<h1>Incrementing CountA from APP.JS : {countA}</h1>
<p>
<button onClick={incrementA}>Increment A</button>
</p>
{/* {memoCounter} */}
<Counter />
</div>
);
}
export default App;
Counter.js :
import React, { useEffect } from 'react';
let renderCount = 1;
const Counter = () => {
useEffect(() => {
renderCount++;
});
return (
<div>
<h1>Rendering Counter component : {renderCount}</h1>
</div>
);
};
export default Counter;
When the user hits the button and increments , React renders Counter component all over again , even though I don't pass anything to it.
However when I put useMemo it doesn't.
Why ?
By default when a parent component renders (App), it renders all its children (Counter).
To overload the default behaviour, use React API like React.memo:
const Counter = () => {...}
export default React.memo(Counter);
I have been playing with React Context API but not sure how I am supposed to type-check using the PropTypes package, given the value is passed down to the child component (Module1) via useContext() rather than via traditional props.
I have tried to implement PropTypes in both the parent (App) and child (module1) components but it is returning an error. Thanks.
import './App.css';
import Module1 from './Module1.js';
import PropTypes from 'prop-types';
export const MyContext = React.createContext();
const App = () => {
const [age, setAge] = useState(22);
const increaseAge = () => {
return setAge( age -1)
};
return (
<MyContext.Provider value={
{
age: age,
increaseAge: increaseAge
}
}>
<Module1/>
</MyContext.Provider>
);
}
App.PropTypes ={
age: PropTypes.number
}
export default App;
==========================================================================================
import React, { useContext } from 'react';
import {MyContext} from './App.js'
import PropTypes from 'prop-types';
const Module1 = () => {
const myAge = useContext(MyContext);
return (
<div>
Your age is: {myAge.age}
<button onClick={myAge.increaseAge}> increase age </button>
</div>
)
}
Module1.PropTypes = {
MyAge: PropTypes.object
}
export default Module1;
My component is not rerendering after the store is changing.
I make sure that the store is actually changing by dropping him to the console with
store.subscribe() and console.log(store.getState()) but still the component is not rerendering again.
I will appreciate your help.
configureStore.js
import { createStore, combineReducers } from 'redux';
import home from '../reducers/home';
import favorites from '../reducers/favorites';
export default () => {
const store = createStore(combineReducers({
home,
favorites
}))
return store;
}
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/store/configureStore';
import AppRouter from './router/AppRouter';
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(jsx, document.querySelector('#root'));
home.js (reducer)
const homeDefaultState = {
name: 'someName'
}
export default (state = homeDefaultState, action) => {
switch (action.type) {
case 'CHANGE_NAME':
return {
...state,
name: 'otherName'
}
default:
return state;
}
}
home.js (action)
export const changeName = () => ({
type: 'CHANGE_NAME'
})
Home.js (component)
import React from 'react';
import configureStore from '../../redux/store/configureStore';
import { changeName } from '../../redux/actions/home';
import { connect } from 'react-redux';
const store = configureStore();
const handleName = () => {
store.dispatch(changeName())
}
const Home = (props) => (
<div className="home">
<button onClick={handleName}>
change name
</button>
{props.home.name}
</div>
);
const mapStateToProps = (state) => ({
home: state.home
});
export default connect(mapStateToProps)(Home);
In your Home component you initialize store for second time. And bound action to this second store
const store = configureStore();
const handleName = () => {
store.dispatch(changeName())
}
At the same time with connect() you access store declared in App.jsx
Read from first but update second. Just remove second store and use mapDispatchToProps(second parameter passed to connect()) instead:
const mapStateToProps = (state) => ({
home: state.home
});
export default connect(mapStateToProps, { handleName: changeName })(Home);
I need to use dispatch Context API methods in _app.js.
The main limitation is that I use React hooks along with Context API, since _app.js is a Class, I can't use hooks within it.
My code:
// store.js
import React, { createContext, useContext, useReducer } from "react";
import mainReducer from "../store/reducers";
const AppStateContext = createContext();
const AppDispatchContext = createContext();
const initialState = {
filters: {
diet: {
selected: []
}
}
};
const useAppState = () => useContext(AppStateContext);
const useAppDispatch = () => useContext(AppDispatchContext);
const useApp = () => [useAppState(), useAppDispatch()];
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(mainReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
};
export { AppProvider, useAppState, useAppDispatch, useApp };
// _app.js
import App from "next/app";
import React from "react";
import { AppProvider } from "../store";
class MyApp extends App {
componentDidMount() {
/***********************************/
// HERE I WOULD LIKE TO USE DISPATCH
/***********************************/
}
render() {
const { Component, router, pageProps } = this.props;
return (
<AppProvider>
<Component {...pageProps} />
</AppProvider>
);
}
}
export default MyApp;
If you really want to use hooks, then just put a wrapper around _app.js like this:
import React from 'react'
import App from 'next/app'
function MyComponent({ children }) {
// You can use hooks here
return <>{children}</>
}
class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<MyComponent>
<Component {...pageProps} />
</MyComponent>
)
}
}
export default MyApp