Home > Enterprise >  Handle React render / flickering whilst awaiting useEffect async operaion
Handle React render / flickering whilst awaiting useEffect async operaion

Time:07-11

I have the below code which works really well, but I can't see to handle the fact that 'NOTHING' is displayed with a flicker as the state changes / there is re-rendering before the data is shown.

How can I wait until the state is updated and only show the spinning loader until all the hooks have executed / the final state is recognized final render have taken place?

A note that item.something is originally undefined before useEffect hence the need to check before returning.

Does there need to be an initial state set (so that item.something !== undefined, and if so, how would I do that?

Is it possibly something like const [item, setItem] = useState('Somehting here')?

import { useEffect, useState } from 'react';
import { useParams, useSearchParams, useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { getItem } from '../features/itemSlice';

import { toast } from 'react-toastify';

import Spinner from '../components/Spinner';


function Items() {
  const [searchParams, setSearchParams] = useSearchParams();
  const itemObject = searchParams.get(`item`);
  

  const { item, isLoading, isError, message } = useSelector((state) => state.task);

  const dispatch = useDispatch();

 
  useEffect(() => {
    if (isError) {
      toast.error(message);
    }

    dispatch(getItem(itemObject));
    // eslint-disable-next-line
  }, [isError, message, itemObject]);

  if (isLoading) {
    return <Spinner />;
  }

  if (isError) {
    return <h3>Error</h3>;
  }

  return (
    <>
      <div>
        {!item.something ? ( //How to refactor here(?) so that nothing is rendered if no data and await result of useEffect hook
          <p>NOTHING</p>
        ) : (
          item.something.map((element, index) => {
            return element.body ? <p key={index}>{element.somethingElse}</p> : <p key={index}>Truly Nothing This Time</p>;
          })
        )}
      </div>
    </>
  );
}

export default Items;

CodePudding user response:

Should be able to accomplish this by adding another condition in your spinner return that check that something is available.

import { useEffect, useState } from 'react';
import { useParams, useSearchParams, useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { getItem } from '../features/itemSlice';

import { toast } from 'react-toastify';

import Spinner from '../components/Spinner';


function Items() {
  const [searchParams, setSearchParams] = useSearchParams();
  const itemObject = searchParams.get(`item`);
  

  const { item, isLoading, isError, message } = useSelector((state) => state.task);

  const dispatch = useDispatch();

 
  useEffect(() => {
    if (isError) {
      toast.error(message);
    }

    dispatch(getItem(itemObject));
    // eslint-disable-next-line
  }, [isError, message, itemObject]);

  // move this to the top because in error state `item.something` will likely be falsy
  if (isError) {
    return <h3>Error</h3>;
  }

  if (isLoading || !item.something) {  // renders spinner when `item.something` is undefined (or any falsy value)
    return <Spinner />;
  }

  return (
    <>
      <div>
        {item.something.map((element, index) => {
            return element.body ? <p key={index}>{element.somethingElse}</p> : <p key={index}>Truly Nothing This Time</p>;
          })
        )}
      </div>
    </>
  );
}
  • Related