Search code examples
javascriptreactjsslideruse-effectuse-state

setState inside useEffect not working on First Render in React JS


I'm building a ReactJS Component that uses React Awesome Slider. What I'm trying to create is a slider with a description div under it, which changes the text then I change the Slide.

Now, I found a way to make it work but I have a problem with the setState of an object, here is the code.

SLIDER:

const AutoplaySlider = withAutoplay(AwesomeSlider);

const StaticSlider = ({slider}) => {
    var images = "";
    var length=0;
   
    const [current, setCurrent] = useState(0);
    const [title, setTitle] = useState([]);
    const [description, setDescription] = useState([]);
   
    switch (slider) {

        case 'portfolio_sviluppo_software':

            images = portfolio_description.sviluppo_software;
            length= portfolio_description.sviluppo_software.length;
          
            break;
        case 'portfolio_domotica':

            images = portfolio_description.domotica;
            length= portfolio_description.domotica.length;
            
            break;
        case 'portfolio_digital_signage':

            images = portfolio_description.digital_signage;
            length= portfolio_description.digital_signage.length;
           
            break;
        case 'portfolio_ricerca_e_sviluppo':

            images = portfolio_description.ricerca_e_sviluppo;
            length= portfolio_description.ricerca_e_sviluppo.length;
           
            break;
    }
   
    useEffect(
        () => {
            setTitle(
                images.map(
                    (slide) => (slide.title)
                )
            );
            setDescription(
                images.map(
                    (desc) => (desc.data)
                )
            );
            }, [images]
            
    );
   
    return(
        <div>
            <AutoplaySlider
                play={true}
                cancelOnInteraction={true}
                interval={0}
                onTransitionStart={slide => setCurrent(slide.nextIndex)}
                className="sliderHome"
                >
                {images.map((image, index) => {
                        
                    let src = "/image/slider/portfolio/"+image.image;
                    //console.log(src);
                    return (
                        <div key={index} data-src={src}>
                        </div>
                    );
                
                })}
            </AutoplaySlider>
            <GalleryCaption selected={current} title={title} description={description} area={slider}/>
        </div>
)
};

export default StaticSlider;

DESCRIPTION GENERATOR


const GalleryCaption = ({ selected = 0, title = [], description= [], area = 0 }) => {
  const formattedIndex = selected + 1;
  var title = title[selected];
  var data = description[selected];
  return (
    <div className="containerDivDescriptionPortflio">
      <div className="DivDescriptionPortflio">
          <p id ={"description_portfolio_"+area} className="paragDescriptionPortflio" >
            <h4>{title}</h4>
            <hr></hr>
            {
                data.map((val) => (
                 <div className="rowDescriptionPortfolio">
                   <div className="divIndexPortfolio" dangerouslySetInnerHTML={{ __html: val.index }} >

                   </div>
                   <div className="divTextPortfolio" dangerouslySetInnerHTML={{ __html: val.text }} >
                     
                   </div>
                 </div>
            ))}
          </p>
      </div>
  </div>

  );
};

export default GalleryCaption;

OBJECT EXAMPLE

{
            "title":"text",
            "data":[
                {
                    "index":"text",
                    "text": "text"
                },
                {
                    "index":"text",
                    "text": "text"
                }
            ],
            "image": "folder/image.jpg"
        },

(This is an element of an array of this kind of object) Now the main problem is that if inside the use effect I only call the setTitle function all works as it should, but if I use also the setDescription all just stop working. I didn't get a specific error, but I get a white screen.

ERROR THAT I GET

Warning: Each child in a list should have a unique "key" prop.

Check the render method of `PortfolioArea`. See https://reactjs.org/link/warning-keys for more information.
    at div
    at PortfolioArea (http://localhost:3000/static/js/bundle.js:1618:5)
    at http://localhost:3000/static/js/bundle.js:4509:78
    at Routes (http://localhost:3000/static/js/bundle.js:177110:5)
    at Router (http://localhost:3000/static/js/bundle.js:177043:15)
    at BrowserRouter (http://localhost:3000/static/js/bundle.js:176523:5)
    at App
    at AppProvider (http://localhost:3000/static/js/bundle.js:3289:5)

Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://reactjs.org/link/unsafe-component-lifecycles for details.

* Move data fetching code or side effects to componentDidUpdate.
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state

Please update the following components: AwesomeSlider
The above error occurred in the <GalleryCaption> component:
    
        at GalleryCaption (http://localhost:3000/static/js/bundle.js:973:5)
        at div
        at StaticSlider (http://localhost:3000/static/js/bundle.js:3049:5)
        at div
        at PortfolioArea (http://localhost:3000/static/js/bundle.js:1618:5)
        at http://localhost:3000/static/js/bundle.js:4510:78
        at Routes (http://localhost:3000/static/js/bundle.js:177111:5)
        at Router (http://localhost:3000/static/js/bundle.js:177044:15)
        at BrowserRouter (http://localhost:3000/static/js/bundle.js:176524:5)
        at App
        at AppProvider (http://localhost:3000/static/js/bundle.js:3290:5)

Consider adding an error boundary to your tree to customize error handling behavior. Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.`

I've tried to change the useEffect second parameters to null and also to use a unique state for every parameter, but the problem seems to be that every time I try to set a state with an object inside the useEffect, on the first render I always get a null value inside that state.

Any tips?


Solution

  • I think that in StaticSlider, since images and length are calculated based on slider prop, I suggest using useMemo() to calculate them per slider change, instead of reassigning the values to the var variables, which is not how it should be done in React and invites bugs to come.

    const StaticSlider = ({slider}) => {
        const images = useMemo(
          () => {
            // calculate and return images value
            // with `switch`
            switch (slider) {
              // ...
              default:
                return [];
            }
          },
          [slider]
        );
    
        const length = useMemo(
          () => {
            // calculate and return length value
            // with `switch`
            switch (slider) {
              // ...
              default:
                return 0;
            }
          },
          [slider]
        );
    

    Please note that your current switch block does not have a default case, you should consider returning a default case with initial values for images and length.

    Also note that the initial assignment of images is images = "" which would make it a string, but inside setDescription() you are calling images.map() which is an array method, so it won't work at the initial render of the component when images is an empty string. I think this is what causes the bug.

    images = [] or default: return [] for images inside switch statement should be better.

    Lastly I think you can consider using a condition check inside useEffect() to only setState on title and description when images is already populated (has length).

        useEffect(
            () => {
              if (images.length) {
                setTitle(
                    images.map(
                        (slide) => (slide.title)
                    )
                );
    
                setDescription(
                    images.map(
                        (desc) => (desc.data)
                    )
                );
              }
            },
            [images]   
        );