so what I am trying to achieve here is storing a whole component in an array in a parent component which renders a specific component in the array using its index for example :
export const Test = () => {
const [components, setComponents] = useState([
<Order key={1} />,
<Order key={2} />,
<Order key={3} />,
]);
const [index, setIndex] = useState(0);
return (
<div>
<button onClick={() => setIndex((old) => (old + 1) % components.length)}>
change
</button>
{`page ` + index}
{components[index]}
</div>
);
};
const Order = () => {
const [someState, setSomeState] = useState(1);
return (
<div>
<button onClick={() => setSomeState((old) => old + 1)}>
{someState}
</button>
</div>
);
};
when I change the state of one item then cycle through the items then return to the item which I changed its state i found that it is not updated
what I figured out is that the component in the array (in the Test component) doesn't get updated and I couldn't figure out how to update it
what I don't want to do is storing the state of the order item in the parent and pass it as props (because it will be a pain to make it work)
const App = ({ flag }) => {
if (flag) <Order />
return null
}
I'm giving you an example so i can explain what might happen in your case. If the flag becomes false from a true, the App turns blank. But what happen to the Order? It's unmounted, why? Since when React compares between the previous scene and the current scene, it notice there's no such Order any more. So what you think about the memory of component of Order (which is called a fiber)?
I guess the answer is, the memory goes to be deleted and will be collected for future use.
Now back to your case, you are using an id to switch to different component. But in theory it should behave very similar to my example for each component.
NOTE: the take away is that if you want to use an array, that's fine, but all components has to be rendered at ALL time, you can hide them, but you can't unmount any of them.
what I don't want to do is storing the state of the order item in the
parent and pass it as props (because it will be a pain to make it
work)
Your problem is that when you render a Test component and then increase index, then you render another Test component with a different key, so reacts reconciliation algorithm unmounts the old one and you lose the state.
You have two options:
lift state of each Test component up, then when one gets unmounted, you will remount it with the old state, because state will be stored in parent, it will not be lost
another option is to render all components and only show those which you want using CSS display property, this way none of them gets unmounted and you retain state. Here is example:
const Order = () => {
const [someState, setSomeState] = React.useState(1);
return (
<div>
<button onClick={() => setSomeState((old) => old + 1)}>
{someState}
</button>
</div>
);
};
let components = [<Order />, <Order />, <Order />];
const Test = () => {
const [index, setIndex] = React.useState(0);
return (
<div>
<button onClick={() => setIndex((old) => (old + 1) % components.length)}>
change
</button>
{`page ` + index}
{[0, 1, 2].map((x) => (
<div key={x} style={{ display: index === x ? "block" : "none" }}>
{components[x]}
</div>
))}
</div>
);
};
ReactDOM.render(
<Test />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
PS I have removed components from state, I can't find official info now, but IMHO it is not good idea to store components in state.
Related
This is a very common performance problem while using the Context API. Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown.
If I have a the wrapper as this:
<CounterProvider>
<SayHello />
<ShowResult />
<IncrementCounter />
<DecrementCounter />
</CounterProvider>
And the value props as:
<CounterContext.Provider value={{increment, decrement, counter, hello }} >
{children}
</CounterContext.Provider>
Everytime I increment the count value from the IncrementCounter component, the entire set of wrapped components re-renders as it is how the Context API is supposed to work.
I did a bit of research and came across these solutions:
Split the Context into N number of Context according to the use-case : This solution works as expected.
Wrap the value provider using React.Memo: I saw a lot of articles suggesting to the React.Memo API as follows:
<CounterContext.Provider
value={useMemo(
() => ({ increment, decrement, counter, hello }),
[increment, decrement, counter, hello]
)}
>
{children}
</CounterContext.Provider>
This however doesn't work as expected. I still can see all the components getting re-rendered. What I'm doing wrong while using the Memo API? Dan Abramov does recommend to go by this approach in an open React issue
If anyone can help me out on this one. Thanks for reading.
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The above statement is true if a context is used like in the below example where components are directly nested in the provider. All of them re-render when count changes, no matter wether they are called useContext(counterContext) or not.
const counterContext = React.createContext();
const CounterContextProvider = () => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
<ComponentOne/>
<ComponentTwo />
</counterContext.Provider>
);
};
const ComponentOne = () => {
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider/>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The statement is false if you are consuming nested components with children. This time when count changes CounterContextProvider renders, but since it's rendering because its state has changed and not because of its parent rendering, and because a component cannot mutate its props, React won't render children. That's it if it was a normal component.
But since there is a context involved here, React will find all components that contain useContext(counterContext) and render them.
const counterContext = React.createContext();
const CounterContextProvider = ({ children }) => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
{children}
</counterContext.Provider>
);
};
const ComponentOne = () => {
const { count } = React.useContext(counterContext);
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider>
<ComponentOne />
<ComponentTwo />
</CounterContextProvider>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In the above example only ComponentOne renders when count changes, which is normal cause he is consuming it. Every component that calls useContext(counterContext) renders if one value of the context changes.
Even with useMemo wrapping the context object as you did, that's the behavior you get as soon as one variable in its dependency array changes.
I'm a bit surprised I'm having trouble finding this online, but I can't seem to find an example of how to do this in a React functional component. I have a React component that I would like to render when I click a button. Right now the function fires and I can see my console.log firing, however the component isn't rendering. My first guess was that it won't render because React doesn't know to update the view, however I added boolean via useState and it still won't render. What am I doing wrong?
Below is the relevant code. How can I get the component in addSection to render?
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
return additionalSection && (
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
span className="button--small">{form.button}</span>
</LinkButton>
</FormAdd>
);
You should change your state (or a prop in your useEffect dependency array in case you had one) in order to force a rerender. In this case:
setAdditionalSection(prevState=>!prevState);
A state change like the one you are calling, will trigger a re-render.
But all html to be rendered must be included in the functional components return statement.
The elements you want to render can be conditionally rendered like this:
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
<span className="button--small">{form.button}</span>
</LinkButton>
{additionalSection &&
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
}
</FormAdd>
);
Am trying to render a new component onclick a button in react js. Am using functional components and I can't handle it. Eg: am in the UserManagement component and on a button click I need to render another component named employee management.
You can conditionally render your component.
Example :
EmployeeManagement.js
const EmployeeManagement = () => {
....
return (
<div>
EmployeeManagement
</div>
);
}
UserManagement.js
const UserManagement = () => {
const [hasRender, setRender] = useState(false);
const onShow = React.useCallback(() => setRender(true), []);
return (
<>
<button onClick={onShow}>Show Employee Management</button>
{hasRender && <EmployeeManagement />}
</>
)
}
One way to do this would be to add a local state in UserManagement,
that holds a boolean value indication whether the component should be hidden or shown.
Then you will have something like:
function UserManagement() {
const [compIsShown, setCompIsShown] = useState(false);
return (
// Whatever else you're rendering.
<button onClick={() => setCompIsShown(true)}>...</button>
{compIsShown && <OtherComp />}
)
}
What will happen is that compIsShown will initialize as false,
so this condition compIsShown && <OtherComp /> will prevent it from rendering.
Then, when you click the button, the state will set, causing a re-render, except now the condition will be true, so <OtherComp> will be rendered.
There are other ways to go about this.
Depends mostly on the use-case.
use a visible state & toggle it in onClick:
const [visible, setVisible] = useState(false)
onClick = () => {setVisible(true)}
then render it like this:
{visible && <EmployeeManagement onClick={onClick} />}
I recently start learning React. I have problem when use dispatch in UseEffect, which is inside the child in the loop. I can't publish all project, but below piece of code:
On the home page online store products are displayed. In a separate component there is a small card that is displayed using the map loop:
<Row className='mt-20'>
{products.map(product => (
<ProductItem key={product._id} product={product} history={history} />
))}
</Row>
Child component code:
const ProductItem = ({ product, history }) => {
const dispatch = useDispatch()
const reviewList = useSelector(state => state.reviewList)
const { reviews } = reviewList
useEffect(() => {
let clean = false
if (!clean) dispatch(listReviewsDetailsByProductId(product._id))
return () => (clean = true)
}, [dispatch, product])
const productRate =
reviews.reduce((acc, item) => item.rating + acc, 0) / reviews.length || 0
return (
<Col xl='3' sm='6'>
<Card>
<Card.Body>
<div
className={'product-img position-relative ' + styles.wrapperImage}
>
<Link to={'/product/' + product.slug}>
{product.images[0] && (
<img
src={product.images[0].path}
alt=''
className='mx-auto d-block img-fluid'
/>
)}
</Link>
</div>
<div className='mt-4 text-center'>
<h5 className='mb-3 text-truncate'>
<Link to={'/product/' + product.slug} className='text-dark'>
{product.name}{' '}
</Link>
</h5>
{reviews && (
<div className='mb-3'>
<StarRatingsCustom rating={productRate} size='14' />
</div>
)}
<ProductPrice price={product.price} newPrice={product.newPrice} />
</div>
</Card.Body>
</Card>
</Col>
)
}
I use dispatch action to get reviews for a specific product from the server and then calculate the rating and display it. Unfortunately, it works every other time, the rating appears, then disappears. I would be grateful for your help!
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SingleProduct (created by Context.Consumer)
in Route (at Root.jsx:96)
The problem is the clean variable which is not a part of your component's state. It exists only inside the scope of the useEffect callback and gets recreated every time that the effect runs.
What is the purpose of clean and when should we set it to true? The cleanup function of a useEffect hook only runs when the component unmounts so that would not be the right place to set a boolean flag like this.
In order to dispatch once per product, we can eliminate it and just rely on the dependencies array. I'm using product_.id instead of product so that it re-runs only if the id changes, but other property changes won't trigger it.
useEffect(() => {
dispatch(listReviewsDetailsByProductId(product._id))
}, [dispatch, product._id])
If this clean state serves some purpose, then it needs to be created with useState so that the same value persists across re-renders. You need to figure out where you would be calling setClean. This code would call the dispatch only once per component even if the product prop changed to a new product, which is probably not what you want.
const [clean, setClean] = useState(false);
useEffect(() => {
if (!clean) {
dispatch(listReviewsDetailsByProductId(product._id))
setClean(true);
}
}, [dispatch, product._id, clean, setClean])
Let's say I have an app which looks like this:
<>
<Component />
<button>Add New Component</button>
</>
How can I make it so every time the button is clicked, a new <Component /> is being appended? It's not about conditional rendering when we show a component or hide it, It's about a possibility to add unlimited amount of new components. Do you have any ideas?
The general workflow is that you store component data (or just identifiers) in an array in state. You then map over the array to render your Component list. The button adds a new identifier/data set to the array.
const App = () => {
const [list, setList] = useState([0]);
const addComponent = () => {
setList([...list, list.length]);
};
return (
<>
{list.map(id => <Component key={id} />)}
<button onClick={addComponent}>Add New Component</button>
</>
)
};
This is a very simple example. In reality you would want to assign unique ids for the keys and probably package it with some more data as an object, but you get the idea.