skip to Main Content

enter image description here

As you can see on the image, after fetching data and displaying it on the screen, orange button appears (in the center) for 1 second and then dissappears.

Code for component:

const Home: FC = () => {
    const { pizzas, loading, error, count } = useAppSelector(
        (state) => state.pizzas
    )
    const { categoryID, searchValue, currentPage, sortNameObj } =
        useAppSelector((state) => state.filter)
    const dispatch = useAppDispatch()

    const handleChangeCategory = useCallback((index: number) => {
        dispatch(setCategoryID(index))
        dispatch(setCurrentPage(1))
    }, [])

    const handleChangePage = (page: number) => {
        dispatch(setCurrentPage(page))
    }

    const pizzaList = pizzas?.map((item) => {
        const pizzaImg = pizzaImagesMiddle[item.title]

        return <PizzaCard key={item.id} item={item} pizzaImg={pizzaImg} />
    })

    const skeletons = [...new Array(4)].map((_, index) => (
        <PizzaSkeleton key={index} />
    ))

    const loadedPizzas = loading ? skeletons : pizzaList

    useEffect(() => {
        dispatch(fetchPizzas())
    }, [categoryID, searchValue, sortNameObj, currentPage])

    if (error) {
        return <EmptyResult title='Произошла ошибка' />
    }

    if (!loading && (!pizzas || pizzas?.length === 0)) {
        return <EmptyResult title='Пиццы не найдены' />
    }

    return (
        <div className={styles.home__container}>
            <div className={styles.content__header}>
                <Categories
                    handleChangeCategory={handleChangeCategory}
                    value={categoryID}
                />
                <Sort sortNameObj={sortNameObj} />
            </div>
            <h2>Все пиццы</h2>
            <section className={styles.content__list}>{loadedPizzas}</section>
            <Pagination
                handleChangePage={handleChangePage}
                currentPage={currentPage}
                itemsLength={count}
            />
        </div>
    )
}

It is because cheking pizzas length in condition if (!loading && (!pizzas || pizzas?.length === 0)). Without check of empty length if (!loading && !pizzas) everything goes well. But I need to check if array is empty or not.

In default, pizzas length is empty (so I have empty array before fetching data)

Pizza slice:

const initialState: PizzasState = {
    pizzas: [],
    loading: false,
    error: null,
    count: 0
}

const pizzasSlice = createSlice({
    name: 'pizzas',
    initialState,
    reducers: {},
    extraReducers: (builder) => {
        builder.addCase(fetchPizzas.pending, (state) => {
            state.loading = true;
            state.pizzas = [];
            state.error = null;
            state.count = 0
        });
        builder.addCase(fetchPizzas.fulfilled, (state, action) => {
            state.pizzas = action.payload.items;
            state.error = null;
            state.count = action.payload.count;
            state.loading = false
        });
        builder.addCase(fetchPizzas.rejected, (state, action) => {
            state.pizzas = [];
            state.count = 0;
            if (action.payload) {
                state.error = action.payload.message
            } else {
                state.error = action.error.message
            };
            state.loading = false
        })
    }
})

Question: How to properly avoid flashing <EmptyResult/> for 1 second?

2

Answers


  1. you can either place a setTimeout(() => dispatch(fetchPizzas()), 1000) on your useEffect or, if any of those functions are async, you can use a State to conditionally render the component or not. Where you are returning the component that you want to delay, make something like: return shouldRenderPizza ? (your actual pizza component) :

    now, on your useEffect make something like dispatch(fetchPizzas().then(result => setTimeout(() => setShouldRenderPizza(true), 1000)))

    Login or Signup to reply.
  2. Issue

    The EmptyResult component is currently displayed when loading is true and the pizzas state is falsey or empty. The pizzas state is initially empty and is also set to [] when the fetchPizzas action is pending.

    Solution

    If you only want to display EmptyResult once data has been loaded then select a different value from [] – "fetched data and empty" and [....] – "fetched data and populated" to differentiate the states and loading conditions. Using undefined or null are valid choices here to indicate that data hasn’t been fetched/loaded yet, and is easy to check in the UI.

    const initialState: PizzasState = {
      pizzas: undefined, // <-- initially undefined
      loading: false,
      error: null,
      count: 0,
    };
    
    const pizzasSlice = createSlice({
      name: 'pizzas',
      initialState,
      extraReducers: (builder) => {
        builder.addCase(fetchPizzas.pending, (state) => {
          state.loading = true;
          // state.pizzas = []; // <-- Don't update yet
          state.error = null;
          state.count = 0
        });
        builder.addCase(fetchPizzas.fulfilled, (state, action) => {
          state.pizzas = action.payload.items; // <-- update to "loaded" value
          state.error = null;
          state.count = action.payload.count;
          state.loading = false;
        });
        builder.addCase(fetchPizzas.rejected, (state, action) => {
          state.pizzas = []; // <-- update to "loaded" value
          state.count = 0;
          state.error = action.payload
            ? action.payload.message
            : action.error.message;
          state.loading = false;
        });
      },
    });
    

    Update the UI to check for undefined/null loaded data.

    const skeletons = [...new Array(4)].map((_, index) => (
      <PizzaSkeleton key={index} />
    ));
    
    const Home: FC = () => {
      const { pizzas, loading, error, count } =
        useAppSelector((state) => state.pizzas);
      const { categoryID, searchValue, currentPage, sortNameObj } =
        useAppSelector((state) => state.filter);
      const dispatch = useAppDispatch();
    
      const handleChangeCategory = useCallback((index: number) => {
        dispatch(setCategoryID(index));
        dispatch(setCurrentPage(1));
      }, []);
    
      useEffect(() => {
        dispatch(fetchPizzas())
      }, [categoryID, searchValue, sortNameObj, currentPage])
    
      const handleChangePage = (page: number) => {
        dispatch(setCurrentPage(page));
      };
    
      if (error) {
        return <EmptyResult title='Произошла ошибка' />;
      }
    
      // Check if pizzas is a defined array and empty
      if (!loading && (Array.isArray(pizzas) && !pizzas.length)) {
        return <EmptyResult title='Пиццы не найдены' />;
      }
    
      return (
        <div className={styles.home__container}>
          <div className={styles.content__header}>
            <Categories
              handleChangeCategory={handleChangeCategory}
              value={categoryID}
            />
            <Sort sortNameObj={sortNameObj} />
          </div>
          <h2>Все пиццы</h2>
          <section className={styles.content__list}>
            {loading
              ? skeletons
              : pizzas?.map((item) => (
                <PizzaCard
                  key={item.id}
                  item={item}
                  pizzaImg={pizzaImagesMiddle[item.title]}
                />
              ))
            }
          </section>
          <Pagination
            handleChangePage={handleChangePage}
            currentPage={currentPage}
            itemsLength={count}
          />
        </div>
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search