Search code examples
reactjstypescriptreact-hooksstyled-components

Creating a state as an array of booleans is giving me an empty array, why?


I am trying to create a component that will display the products from my database with a checkbox in each of them. Then I should be able to delete all checked items from the database with one press of a button in the header component.

This is the work in progress:

import React, { useState, useEffect } from "react";
import { Container, ItemDescription, ProductContainer } from "./style";
const API = "http://localhost:4000/select";

interface DATA {
  sku: string,
  name: string, 
  price: string,
  type: string,
  size: number,
  weight: number,
  height: number,
  width: number,
  length: number,
}

interface IProps {
  del: boolean
}

const ProductDisplay = ({del}:IProps) => {
  const [productList, setProductList] = useState<Array<DATA>>([]);
  var checked = new Array(productList.length).fill(false); // this gives me what I want
  const [checkedItems, setCheckedItems] = useState(checked); // here it gives me an empty undefined array
  const [deleteList, setDeleteList] = useState<Array<DATA>>([]);
  
  useEffect(() => {
    const fetchData = async (data: any) => {
      try {
        const response = await fetch(data);
        const results = await response.json();
        setProductList(results);
      } catch (e) {
        console.log(e);
      }
    };
    fetchData(API);
  }, []);

  const handleOnChange = (position:number) => {
    const updatedCheckedItems = checkedItems.map((item, index) =>
      index === position ? !item : item
    );

    setCheckedItems(updatedCheckedItems);

      const skuList = updatedCheckedItems.reduce(
      (currentState, index) => {
        if (currentState === true) {
          return productList[index].sku;
        }
        return null;
      }
    );

    setDeleteList(skuList);

    console.log(checkedItems);
   };

  return (
    <div>
        {productList.length === 0 && <h4>Fetching Checkboxes ...</h4>}
        {productList.length > 0 &&
            <Container>
                {productList.map((item: any, index) => (
                    <ProductContainer>              
                        <input type="checkbox"
                            value={item.sku}
                            id={`custom-checkbox-${index}`}
                            checked={checkedItems[index]}
                            onChange={() => handleOnChange(index)}
                        />
                        <ItemDescription>
                            <span className="sku" >{item.sku}</span>
                            <span className="name" >{item.name}</span>
                            <span className="price" >{item.price} $</span>
                            {item.size && <span className="size" >Size: {item.size}MB</span>}
                            {item.weight && <span className="weight" >Weight: {item.weight}KG</span>}
                            {item.height && <span className="dimention" >Dimention: {item.height} x {item.width} x {item.length}</span>}
                        </ItemDescription>
                    </ProductContainer>
                ))}
            </Container>  
        }
     );
};

export default ProductDisplay;

The product display is working fine, the checked variable shows me the array with 11 false booleans that I want, but the state for checkeItems gives me an empty array. Because of that I haven't been able to test if the rest of the code allows me to produce a list of skus from the checked items for deletion.

I have tried creating the new array as the starting state with the same result:

const [checkedItems, setCheckedItems] = useState(
new Array(productList.length).fill(false)
);

I'm quite new to this and appreciate any help, thanks!


Solution

  • You pass two different parameters to useState(checked) in two render, which only uses the first one (the empty array) as the initValue.

    You should use useEffect. Just like:

    function App() => {
      const [productList, setProductList] = useState<Array<DATA>>([]);
      const [checkedItems, setCheckedItems] = useState<Array<Boolean>>([]);
    
      useEffect(() => {
        const fetchData = async (data: any) => {
          try {
            const response = await fetch(data);
            const results = await response.json();
            setProductList(results);
            //
            const checked = new Array(results.length).fill(false);
            setCheckedItems(checked)
          } catch (e) {
            console.log(e);
          }
        };
        fetchData(API);
      }, []);
      
      // ......
    }
    

    After react18, setProductList and setCheckedItems in useEffect will only cause one re-render. See Automatic batching for fewer renders in React 18