Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement conditional rendering while the children component calling useState() in react hooks?

Tags:

Recently I am trying to replace class components implementation in my project with React Hooks and I have some troubles in implementing conditional rendering of children component.

I have a parent component contains header, footer and a conditional rendering children component which is rendering different children component depends on the state of parent component and its state is controlled by another useEffect like the coded stated below.

However, one of my children component 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 component 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 component.

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 component:

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
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
like image 704
Horace Tang Avatar asked May 17 '19 04:05

Horace Tang


1 Answers

It's perfectly fine to render child components 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 compiles into). Directly calling child components 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>
);
like image 122
Nicholas Tower Avatar answered Sep 29 '22 03:09

Nicholas Tower