Recently I am trying to replace class ponents implementation in my project with React Hooks and I have some troubles in implementing conditional rendering of children ponent.
I have a parent ponent contains header, footer and a conditional rendering children ponent which is rendering different children ponent depends on the state of parent ponent and its state is controlled by another useEffect like the coded stated below.
However, one of my children ponent contains a simple counter which is implemented by useState() like the example in official React Hooks tutorial. As rules of hooks stated that we can only call hook at the top level, my app is crashed while this children is rendered.
I guess one of the solution is to put the children's useState() to parent ponent or use Redux-like implementation? But it is a bit awkward because the counter is just a simple logic and not necessary to be put out of the ponent.
So I am finding another way to solve this problem. Of course please let me know if my concept is wrong at the beginning.
My parent ponent:
const StorePage = (props) => {
const { children } = props;
const [detectedTagIds, setDetectedTagIds] = useState([]);
const [detectedProducts, setDetectedProducts] = useState([]);
const fetchProductByTagIds = (tagIds) => productController.getProducts({ tagId: tagIds })
.then(res => res.json())
.then(json => setDetectedProducts(json.result))
// monitor detected tags
useEffect(() => {
ws.addEventListener('message', (event) => {
const json = JSON.parse(event.data)
const { tagId } = json;
if (!_.includes(detectedTagIds, tagId)) {
setDetectedTagIds(_.concat(detectedTagIds, tagId));
}
});
}, []);
// fetch while detected tags are changed
useDeepCompareEffect(() => {
fetchProductByTagIds(detectedTagIds)
}, [detectedTagIds]);
return (
<div className="StorePage">
{Header({ detectedProducts })}
<div className="StorePage-content">
{
detectedTagIds.length === 0 ?
LandingPage() :
( detectedProducts.length === 1 ? ProductInfoPage({ detectedProduct: detectedProducts[0] }) : null )
}
</div>
{Footer({ detectedProducts })}
</div>
);
};
export default StorePage;
Here is the error message I have got, I think this is triggered by the change of detectedProducts:
Previous render Next render
------------------------------------------------------
1. useState useState
2. useState useState
3. useEffect useEffect
4. useRef useRef
5. useEffect useEffect
6. useState useState
7. useState useState
8. useState useState
9. useRef useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Recently I am trying to replace class ponents implementation in my project with React Hooks and I have some troubles in implementing conditional rendering of children ponent.
I have a parent ponent contains header, footer and a conditional rendering children ponent which is rendering different children ponent depends on the state of parent ponent and its state is controlled by another useEffect like the coded stated below.
However, one of my children ponent contains a simple counter which is implemented by useState() like the example in official React Hooks tutorial. As rules of hooks stated that we can only call hook at the top level, my app is crashed while this children is rendered.
I guess one of the solution is to put the children's useState() to parent ponent or use Redux-like implementation? But it is a bit awkward because the counter is just a simple logic and not necessary to be put out of the ponent.
So I am finding another way to solve this problem. Of course please let me know if my concept is wrong at the beginning.
My parent ponent:
const StorePage = (props) => {
const { children } = props;
const [detectedTagIds, setDetectedTagIds] = useState([]);
const [detectedProducts, setDetectedProducts] = useState([]);
const fetchProductByTagIds = (tagIds) => productController.getProducts({ tagId: tagIds })
.then(res => res.json())
.then(json => setDetectedProducts(json.result))
// monitor detected tags
useEffect(() => {
ws.addEventListener('message', (event) => {
const json = JSON.parse(event.data)
const { tagId } = json;
if (!_.includes(detectedTagIds, tagId)) {
setDetectedTagIds(_.concat(detectedTagIds, tagId));
}
});
}, []);
// fetch while detected tags are changed
useDeepCompareEffect(() => {
fetchProductByTagIds(detectedTagIds)
}, [detectedTagIds]);
return (
<div className="StorePage">
{Header({ detectedProducts })}
<div className="StorePage-content">
{
detectedTagIds.length === 0 ?
LandingPage() :
( detectedProducts.length === 1 ? ProductInfoPage({ detectedProduct: detectedProducts[0] }) : null )
}
</div>
{Footer({ detectedProducts })}
</div>
);
};
export default StorePage;
Here is the error message I have got, I think this is triggered by the change of detectedProducts:
Previous render Next render
------------------------------------------------------
1. useState useState
2. useState useState
3. useEffect useEffect
4. useRef useRef
5. useEffect useEffect
6. useState useState
7. useState useState
8. useState useState
9. useRef useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Share
Improve this question
asked May 17, 2019 at 4:23
Horace TangHorace Tang
532 silver badges4 bronze badges
1 Answer
Reset to default 5It's perfectly fine to render child ponents conditionally even if those children make use of hooks, but you'll need to do so using the normal react way: either by writing jsx tags, or by manually calling React.createElement (which is what jsx piles into). Directly calling child ponents as functions will cause the problems you are seeing.
return (
<div className="StorePage">
<Header detectedProducts={detectedProducts} />
<div className="StorePage-content">
{detectedTagIds.length === 0 ? (
<LandingPage/>
) : detectedProducts.length == 1 ? (
<ProductInfoPage detectedProducts={detectedProducts[0]} />
) : (
null
)}
</div>
<Footer detectedProducts={detectedProducts}/>
</div>
);