How to avoid unexpected rendering while using React Context? - javascript

I have two functional component under my provider,
SubApp1 and SubApp2 and here when I am increasing counter1 in SubApp1 the SubApp2 also is rendering, even when it is not need to be re-rendered.
And when I am increasing counter2 in SubApp2 the SubApp1 also is rendering.
I know this happens regally, but How can avoid this situation ?
App.js:
import React, {useContext, useState, memo} from "react";
import "./styles.css";
export const MainContext = React.createContext();
export const MainProvider = ({children})=> {
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
return (
<MainContext.Provider value={{
counter1, setCounter1,
counter2, setCounter2,
}}>
{children}
</MainContext.Provider>
);
}
export const SubApp1 = memo(()=> {
const {counter1, setCounter1} = useContext(MainContext);
console.log('Counter 1: ', counter1);
return (
<div className="App">
<button onClick={()=> {
setCounter1(counter1+1);
}}>
Increase Count 1
</button>
</div>
);
});
export const SubApp2 = memo(()=> {
const {counter2, setCounter2} = useContext(MainContext);
console.log('counter2: ', counter2);
return (
<div className="App">
<button onClick={()=> {
setCounter2(counter2+1);
}}>
Increase Count 2
</button>
</div>
);
});
export default function App ({navigation}){
console.log('App Is rendering...');
return (
<div className="App">
<button onClick={()=> {
navigation.navigate('SubApp1');
}}>
navigate to SubApp1
</button>
<button onClick={()=> {
navigation.navigate('SubApp2');
}}>
navigate to SubApp2
</button>
</div>
);
}
index.js:
import React from "react";
import ReactDOM from "react-dom";
import App, {MainProvider} from "./App";
const MainApp = ()=> (
<MainProvider>
<App />
</MainProvider>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<MainApp />, rootElement);

You should pass the counter to the SubApps as props. Then memo will take care that only the component with changing props will be rerendered.
Something like this:
export const Wrapper1 = ()=> {
const {counter1, setCounter1} = useContext(MainContext);
return (
<SubApp1 {...{counter1, setCounter1}} />
);
};
export const SubApp1 = memo(({counter1, setCounter1})=> {
console.log('Counter 1: ', counter1);
return (
<div className="App">
<button onClick={()=> {
setCounter1(counter1+1);
}}>
Increase Count 1
</button>
</div>
);
});
export const SubApp2 = memo(({counter2, setCounter2})=> {
console.log('counter2: ', counter2);
return (
<div className="App">
<button onClick={()=> {
setCounter2(counter2+1);
}}>
Increase Count 2
</button>
</div>
);
});
export default function App (){
const {counter2, setCounter2} = useContext(MainContext);
console.log('App Is rendering...');
return (
<div className="App">
<Wrapper1/>
<SubApp2 {...{counter2, setCounter2}} />
</div>
);
}
Codesandbox link is not right...

I follow the tip of Peter Ambruzs, but i have a problem if i pass counter1 as a param. The component keep rerendering.
But, if i pass just setCounter1 function, its works fine.
Below, my example using typescript.
const Campaigns = (): JSX.Element => {
const { setAlert } = useContext(AlertContext);
return <InnerCampaign {...{ setAlert }} />;
};
const InnerCampaign = memo(
({ setAlert }: any): JSX.Element => {...},)
export default Campaigns;

Related

useState replaces the current element

I have 2 buttons. Add1 and Add2
The Add2 button in the Test does not work. What am I doing wrong. I'm not very familiar with React.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react-lite";
function App() {
const [component, setComponent] = useState([]);
useEffect(() => {});
const Test = observer(() => {
return (
<div>
<p>
Test -
<button onClick={() => setComponent([...component, Test])}>
Add 2
</button>
</p>
</div>
);
});
return (
<div>
{component.map((Input, index) => (
<Input key={index} />
))}
<button onClick={() => setComponent([...component, Test])}>Add 1</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
codesandbox: https://codesandbox.io/s/react-hooks-useeffect-forked-ml77pz
You should not create component inside another component if you do not keep its reference!
Move it outside of App and add prop setComponent
const Test = observer(({setComponent}) => {
return (
<div>
<p>
Test -
<button onClick={() => setComponent(component => [...component, Test])}>
Add 2
</button>
</p>
</div>
);
});
function App() {
...
}
Then when you render, pass 'setComponent' to it:
<Input key={index} setComponent={setComponent} />
You have not defined the base case for this recursive render
const Test = observer(() => {
return (
<div>
<p>
Test -
<button onClick={() => setComponent([...component, Test])}>
Add 2
</button>
</p>
</div>
);
});

React component's internal state is not updating when it is inside a context

I have a simple React component named Child which increments its count when clicked. Also, an outside state is saved which get updated when the Child component is incremented.
const Child = ({ onChange }) => {
const [count, setCount] = useState(0);
return (
<div>
<h1
onClick={() => {
setCount(prev => prev + 1);
onChange(1);
}}
>
Child count = {count}
</h1>
</div>
);
};
const App = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<h1> Parent Count = {count} </h1>
<Child onChange={(i) => setCount(count + i)} />
</div>
);
}
Above code works as expected. However, when I use the Context API to manage the outside state, the Child component's internal count remains at 0.
const App = () => {
const [count, setCount] = useState(0);
const AppContext = React.createContext();
return (
<div className="App">
<AppContext.Provider value={{ state: count, setState: setCount }}>
<>
<AppContext.Consumer>
{({ state }) => <h1> Parent Count = {state} </h1>}
</AppContext.Consumer>
<AppContext.Consumer>
{({ state, setState }) => (
<Child onChange={(i) => setState(state + i)} />
)}
</AppContext.Consumer>
</>
</AppContext.Provider>
</div>
);
};
Sample CodeSandbox.
Why the internal state of the element inside AppContext.Provider always reset to its initial state?
First, the creation of a context should be outside of a Component, which means this line:
const AppContext = React.createContext()
should be outside of App. Second, a context setup can be simpler, as below (I separated each component in its own file, so it resembles to common use cases), and working forked CodeSandbox here.
App:
import { useState, createContext } from "react";
import Child from "./Child";
export const AppContext = createContext();
const App = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<h1> Parent Count = {count} </h1>
<AppContext.Provider value={{ state: count, setState: setCount }}>
<Child />
</AppContext.Provider>
</div>
);
};
export default App;
Child:
import { useContext, useState } from "react";
import { AppContext } from "./App";
const Child = () => {
const { state, setState } = useContext(AppContext);
const [count, setCount] = useState(0);
return (
<div>
<h1
onClick={() => {
setCount((prev) => prev + 1);
setState(state + 1);
}}
>
Child count = {count}
</h1>
</div>
);
};
export default Child;

get data from child components to parents components in react js

I want to get value from child components to parent components.
Here is my code.
//this is child component
import { React } from "react";
const Tab = () => {
const changeTab = (index) => {
console.log(index);
};
return (
<>
<div className="flex gap-10">
<button
onClick={() => changeTab(1)}
className="bg-gray-700 p-2 text-white"
>
btn1
</button>
</div>
</>
);
};
export default Tab;
//this is parent component
import React from "react";
import Nav1 from "./Components/Navbar/Nav1";
import Tab from "./Tab";
const App = () => {
return (
<>
<Tab />
<div>
<Nav1 />
</div>
</>
);
};
export default App;
I want to log the value of the index in a parent component that was coming from a child.
define changeTab in parent component and pass it through props to Tab.
parent:
import React from "react";
import Nav1 from "./Components/Navbar/Nav1";
import Tab from "./Tab";
const App = () => {
const changeTab = (index) => {
console.log(index);
};
return (
<>
<Tab changeTab={changeTab}/>
<div>
<Nav1 />
</div>
</>
);
};
export default App;
and child component:
import { React } from "react";
const Tab = ({changeTab}) => {
const onChangeTab = (index) => {
changeTab(index);
// other stuff
};
return (
<>
<div className="flex gap-10">
<button
onClick={() => onChangeTab(1)}
className="bg-gray-700 p-2 text-white"
>
btn1
</button>
</div>
</>
);
};
export default Tab;
You can use context API:
https://reactjs.org/docs/context.html
Or you can transfer changeTab function to parent component and pas it as prop
Your problem is common(with most people). And the solution is to lift the state up(React docs).
Which basically means, you'll have to maintain the state in the parent component and pass the value and method to the child component.
// App.jsx
import React from "react";
import Nav1 from "./Components/Navbar/Nav1";
import Tab from "./Tab";
const App = () => {
const changeTab = (index) => {
console.log(index);
};
return (
<>
<Tab handleTabChange={changeTab}/>
<div>
<Nav1 />
</div>
</>
);
};
export default App;
import { React } from "react";
const Tab = (props) => {
const changeTab = (index) => {
props.handleTabChange(index);
};
return (
<>
<div className="flex gap-10">
<button
onClick={() => changeTab(1)}
className="bg-gray-700 p-2 text-white"
>
btn1
</button>
</div>
</>
);
};
export default Tab;

React: Having an error when I try to take components from my app and make new files from them

It reads:
"Error: App(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null."
I have seen other thread with about this particular issue with JS React but the solutions didn't work for me. I was making a shopping cart app with React and it was working fine when everything was on one JSX page but when I start making files for every every compartment for example, "Products, "Cart". . I am aware others have had this problem but I believe there are different ways you can get this error message.
Index.js:
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
App.js
const PAGE_PRODUCTS = 'products';
const PAGE_CART = 'cart';
function App() {
const [cart, setCart] = useState([]);
const [page, setPage] = useState(PAGE_PRODUCTS);
const addToCart = (product) =>{
console.log('we are in fart i mean cart');
setCart([...cart, {...product}]);
const removeFromCart = (productToRemove) =>{
setCart(
cart.filter(product => product !== productToRemove ));
};
const navigateTo = (nextPage) => {setPage(nextPage);
};
const renderCart = () => (
<>
<h1>Cart</h1>
<div className="products">
{cart.map ((product, idx) => (
<div className="product" key={idx}>
<h3>{product.name}</h3>
<h4>{product.cost}</h4>
<img src={product.image} alt={product.name}/>
<button onClick = {() => removeFromCart(product)}>Remove</button>
</div>
))}
</div>
</>
);
return (
<div className="App">
<header>
<button onClick={() => navigateTo(PAGE_CART)}>Go to Cart ({cart.length})</button>
<button onClick={() => navigateTo(PAGE_PRODUCTS)}>View Products </button>
</header>
{page === PAGE_PRODUCTS && (
<Products addToCart={addToCart} />
)}
{page === PAGE_CART && renderCart()}
</div>
);
};
}
export default App;
Products.jsx
import React, { useState } from 'react';
export default function Products({ addToCart }){
const [products] = useState([
{
name: 'TWA FUCK 12 T-SHIRT',
cost: '$19.99',
image: 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/ba6f3a5b-075f-4fae-9efd-dd797e00931a/ddya15n-0c2ea56a-4735-470c-bee9-41dd09f9dfb9.png/v1/fill/w_250,h_250,strp/blue_lives_splatter_by_0r4lf1x4t10n_ddya15n-250t.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD0xMjgwIiwicGF0aCI6IlwvZlwvYmE2ZjNhNWItMDc1Zi00ZmFlLTllZmQtZGQ3OTdlMDA5MzFhXC9kZHlhMTVuLTBjMmVhNTZhLTQ3MzUtNDcwYy1iZWU5LTQxZGQwOWY5ZGZiOS5wbmciLCJ3aWR0aCI6Ijw9MTI4MCJ9XV0sImF1ZCI6WyJ1cm46c2VydmljZTppbWFnZS5vcGVyYXRpb25zIl19.bhFc4MR_BfROHMtp2C6Nl2GaQ1PeJS2piOioT3tyRgc'
},
{
name: 'TWA THE WORLD IS YOURS T-SHIRT',
cost: '$19.99',
image: 'https://ih1.redbubble.net/image.974235379.7506/poster,504x498,f8f8f8-pad,600x600,f8f8f8.jpg'
}
]);
return (
<>
<h1>Products</h1>
<div className="products">
{products.map ((product, idx) => (
<div className="product" key={idx}>
<h3>{product.name}</h3>
<h4>{product.cost}</h4>
<img src={product.image} alt={product.name}/>
<button onClick = {() => addToCart(product)}>Add To Cart</button>
</div>
))}
</div>
</>
);
}

Why doesn't this React component re-render when its prop changes?

I've created a cut-down version of a re-render issue which I'm having with an application I am working on.
In reality DisplayElement1 and DisplayElement2 are two complex components.
DisplayElement2 here is iterating through a simple array of numbers (supplied via its prop numbers) and displaying them.
Problem : When the array behind the numbers prop gets updated in the main component App (in this case by clicking on the Add Number to Array button I would expect DisplayElement2 to re-render with the updated array but it doesn't, why not ??
If I click Show Display 1 and then click back on Show Display 2 the updated array renders.
App.js
import React, { useState, useMemo } from "react";
import "./styles.css";
import DisplayComponent1 from "./DisplayComponent1";
import DisplayComponent2 from "./DisplayComponent2";
import { Button } from "#material-ui/core";
export default function App() {
const [numbersToDisplay, setNumbersToDisplay] = useState([1, 2, 3]);
////////////////////////////////////////////////
const component1 = useMemo(() => {
return <DisplayComponent1 />;
}, []);
const component2 = useMemo(() => {
return (
<DisplayComponent2
style={{ background: "red" }}
numbers={numbersToDisplay}
/>
);
}, [numbersToDisplay]);
////////////////////////////////////////////////
const [currentDisplayComponent, setCurrentDisplayComponent] = useState(
component2
);
return (
<div className="App">
<Button
variant="contained"
color="secondary"
onClick={() => setCurrentDisplayComponent(component1)}
>
Show Display 1
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setCurrentDisplayComponent(component2)}
>
Show Display 2
</Button>
<Button
variant="contained"
style={{ marginLeft: 50 }}
onClick={() => {
let tempArray = Array.from(numbersToDisplay);
tempArray.push(4);
setNumbersToDisplay(tempArray);
}}
>
Add number to array
</Button>
{currentDisplayComponent}
</div>
);
}
DisplayElement1.js and DisplayElement2.js
import React from "react";
import {Paper} from "#material-ui/core";
export default function DisplayComponent1(props) {
return (
<Paper>
<p>This is DisplayComponent1</p>
</Paper>
);
}
import React from "react";
import { Paper } from "#material-ui/core";
export default function DisplayComponent2(props) {
return (
<Paper>
<p>This is DisplayComponent2</p>
{props.numbers.map((currNumber, currIndex) => {
return <div key={currIndex}>{currNumber}</div>;
})}
</Paper>
);
}
The reason your component doens't re-render with updated props is because you have a previous instance of your component stored in the currentDisplayComponent state which is what you use to render
A hacky workaround with your current code would be to make use of useEffect and update the component instance that is active
However the best solution in this scenarios is to take out the component instances outside of the state and render these based on a selected component string state.
To prevent unnecessary updates you can make use of React.memo
export default React.memo(function DisplayComponent2(props) {
return (
<Paper>
<p>This is DisplayComponent2</p>
{props.numbers.map((currNumber, index) => {
return <div key={index}>{currNumber}</div>;
})}
</Paper>
);
});
App.js
export default function App() {
const [numbersToDisplay, setNumbersToDisplay] = useState([1, 2, 3]);
const [currentDisplayComponent, setCurrentDisplayComponent] = useState(
"component1"
);
const getCurrentComponent = currentDisplayComponent => {
switch (currentDisplayComponent) {
case "component1":
return <DisplayComponent1 />;
case "component2":
return (
<DisplayComponent2
style={{ background: "red" }}
numbers={numbersToDisplay}
/>
);
default:
return null;
}
};
return (
<div className="App">
<Button
variant="contained"
color="secondary"
onClick={() => setCurrentDisplayComponent("component1")}
>
Show Display 1
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setCurrentDisplayComponent("component2")}
>
Show Display 2
</Button>
<Button
variant="contained"
style={{ marginLeft: 50 }}
onClick={() => {
let tempArray = Array.from(numbersToDisplay);
tempArray.push(4);
setNumbersToDisplay(tempArray);
}}
>
Add number to array
</Button>
{getCurrentComponent(currentDisplayComponent)}
</div>
);
}
Working demo
Consider something like this for your App.js, where the display states are enumerated and we've removed the useMemo
import React, { useState } from "react";
import "./styles.css";
import DisplayComponent1 from "./DisplayComponent1";
import DisplayComponent2 from "./DisplayComponent2";
import { Button } from "#material-ui/core";
const DisplayStatEnum = {COMPONENT1: 0, COMPONENT2: 1};
export default function App() {
const [numbersToDisplay, setNumbersToDisplay] = useState([1, 2, 3]);
////////////////////////////////////////////////
const component1 = <DisplayComponent1 />;
const component2 = <DisplayComponent2
style={{ background: "red" }}
numbers={numbersToDisplay}
/>;
////////////////////////////////////////////////
const [currentDisplayComponent, setCurrentDisplayComponent]
= useState(DisplayStatEnum.COMPONENT2);
const componentSelected =
currentDisplayComponent === DisplayStatEnum.COMPONENT1
? component1
: component2;
return (
<div className="App">
<div>
<Button
variant="contained"
color="secondary"
onClick={() => setCurrentDisplayComponent(DisplayStatEnum.COMPONENT1)}
>
Show Display 1
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setCurrentDisplayComponent(DisplayStatEnum.COMPONENT2)}
>
Show Display 2
</Button>
</div>
<Button
variant="contained"
style={{ marginLeft: 50 }}
onClick={() => {
let tempArray = Array.from(numbersToDisplay);
tempArray.push(4);
setNumbersToDisplay(tempArray);
}}
>
Add number to array
</Button>
{componentSelected}
</div>
);
}

Categories

Resources