React and useState(): multiple re-renders if initial state is a string - reactjs

So I have a functional component that displays an iframe. The src-property for the iframe is set in useState().
Here is the code:
import React, { useEffect, useState } from "react";
export default function ComponentWithIframe() {
const [iframeUrl, setIframeUrl] = useState(undefined);
useEffect(() => {
// some axios-requests
... setIframeUrl(responseFromAxios);
});
return (
<div className='Iframe'>
<iframe
id="my_iframe"
src={iframeUrl}
width="100%" height='800px'
title="I am an Iframe"/>
</div>
);
}
Now here's the strange thing: If useState() is initialized with a string, the component does multiple re-renders on mount (3 times). For example:
const [iframeUrl, setIframeUrl] = useState("There is no url defined");
But when I initialize useState() with undefined, then the component is rendered only once and everything works fine.
I found this solution more by accident and don't understand why it is working like this. Research didn't bring any explanation. Neither the official React Docs nor in-depth explanation sites like this or this. Even here on StackOverflow, I couldn't find anything.
Does anyone know what causes this behavior?

I think the re-render occurs because there is no dependency array.
It should more like this,
useEffect(() => {
// some axios-requests
... setIframeUrl(responseFromAxios);
}, []) // <- This Array
Refer: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Related

useState is not changing it's value on useEffect hook [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 months ago.
I am trying to animate a header on a personal site I'm working on.
It's a simple fade in fade out effect as the user scrolls through the page.
To get this effect what i'm doing is using an Intersection Observer to check wether or not a certain element is in view or not.
When that element is in view, i'd like to change the state elementInView to reflect that or not using a boolean value.
when I console.log the isIntersecting value of the element that the observer is listening to, I can see that it changes True or False based on whether the element is in view or not.
so... since that is working, i'm using that value to set state of elementInView.
BUT!!! the state is not changing when the element is in view, even though it should. I'm not sure what went wrong but I cannot figure it out for the life of me.
here's the code:
import React from 'react';
import { useEffect, useRef, useState } from 'react';
import { Link, useRouteMatch, useNavigate } from 'react-router-dom';
import BoulderImage from "../data/nat_pic_boulders_and_tree.jpeg";
import FenceImage from "../data/nat_pic_fence.jpeg";
import GrassField from '../data/nat_pic_grass.jpeg';
export default function HomePage() {
const navigate = useNavigate();
const firstRef = useRef();
const secondRef = useRef();
const [elementInView, setElementInView] = useState();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
setElementInView(entry.isInteresecting);
console.log(elementInView);
})
observer.observe(firstRef.current);
observer.observe(secondRef.current);
}, [elementInView])
return (
<div>
<h1 className="main-intro-header">Welcome Home</h1>
<div className='nav-links'>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</div>
<div className="stop-point-one" ref={firstRef}>stop point</div>
<div className="stop-point-two" ref={secondRef}>stop point</div>
</div>
)}
the list at the end of the useEffect hook. i've tried putting elementInView in there and taking it out and re-running it. no luck.
I think that the thing you are missing is that after calling setElementInView it does not change elementInView on the current render. Only in the next render for that component elementInView will get the new state. And that way you are getting the wrong output to the console.
The way to solve it is to add another useEffect hook with elementInView as a dependency and do you logic there.
You can watch a great video of Jack Herrington regarding this:
https://www.youtube.com/watch?v=RAJD4KpX8LA
Henrys comment links to a better explanation than I can give. Summed up, since setState is performed in an async nature, the value will not be what you set it to immediately after the setState line is ran. If you move your console.log line out of your observer declaration, into the body of the useEffect, it should work as you expect. I put together a little sandbox and spaced some elements apart to put them off screen to show this as well.
https://codesandbox.io/s/observer-test-m5qh88?file=/src/App.js
If you scroll the elements on and off the page with the console open, you'll see it update each time they move off and back onto the screen.
I figured it out!
I saw a simpler way to set the state of these elements as they appear on the screen.
i updated this piece of code:
const [elementInView, setElementInView] = useState();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
setElementInView(entry.isInteresecting);
console.log(elementInView);
})
observer.observe(firstRef.current);
observer.observe(secondRef.current);
}, [elementInView])
to this:
import { useInView } from 'react-intersection-observer';
const { ref: firstRef, inView: firstBreakIsVisibile} = useInView();
const { ref: secondRef, inView: secondBreakIsVisibile} = useInView();
I used npm to install the react-intersection-observer dependency.

Does setState function of useState hook trigger re-render of whole component or just the returned JSX part of the code?

I am a beginner in React and I am learning it from a udemy course. Initially I thought the whole code inside the functional component gets re rendered/re run after state update. But then I saw this implementation of countdown before redirect and got confused.
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
const LoadingToRedirect = () => {
const [count, setCount] = useState(5);
let history = useHistory();
useEffect(() => {
const interval = setInterval(() => {
setCount((currentCount) => --currentCount);
}, 1000);
// redirect once count is equal to 0
count === 0 && history.push("/");
// cleanup
return () => clearInterval(interval);
}, [count]);
return (
<div className="container p-5 text-center">
<p>Redirecting you in {count} seconds</p>
</div>
);
};
export default LoadingToRedirect;
So why is setInterval needed here if setCount triggers a re-render of the whole code? Can't we do the same thing with setTimeout? So I tried that and it worked. Surely there is a reason he did it that way? Am I missing something?
Of course React re-renders the whole component but it also depends on some conditions. For example if you look at your code you have passed count variable as a dependency to useEffect hook, it means if the value of count changes React will render the effect inside of the useEffect hook.
Yes, you can achieve the same using setTimeout;setInterval is
pointless because it totally depends on count variable you passed as a
dependency.
if you remove count as a dependency then you can easily see it will
not redirect you the required page.

React - issue with useEffect() and axios.get

I am struggling to understand why my code is not working. I am using the useEffect() hook to make a call to an API, and then I am using setState() to update my component state. In my JSX, I am mapping my info array to render the data.
Here's my code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
function App() {
const [info, setInfo] = useState();
console.log(info);
useEffect(() => {
const getUsers = async () => {
const res = await axios('https://api.mocki.io/v1/b043df5a');
console.log(res.data);
setInfo(res.data);
};
getUsers();
}, []);
return (
<div>
<input type='text' placeholder='Search users' />
<input type='text' placeholder='Search users' />
{info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
</div>
);
}
export default App;
However, I get this error: 'TypeError: Cannot read property 'map' of undefined'. My best guess is that my JSX is rendered before my state is populated with API info.
The code does work when I write the following JSX, which allows me to check if 'info' is true:
{info && info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
Is this a normal behavior? Why is useEffect not populating my state before my page is rendered?
I would appreciate your help, as I am struggling to find the solution to this specific issue.
Thanks!
Just do this:
const [info, setInfo] = useState([]);
The issue is that you have no intial value and therefore it automatically defaults to undefined. Now you are trying to call .map on a value that is undefined therefore it throws an error. With an empty array as the initial value however, .map will loop over an empty array on the first render (before the useEffect) and it won't throw any error.
that's because useEffect hook will run after the dom render phase finished and one more thing that can cause the delay of getting data is the fact that you're calling an asynchronous function which usually get some time to finished.
so what are the possible options here:
just use empty array [] as default value
check the length of the state like info.length && whatever...
sometimes you can use the useLayoutEffect which is kinda a synchronous operation. but in your case which is an api calls solution 1 and 2 is the answer
You are trying to iterate over undefined it's normal cause you need to check first before data will come to your state. You can achieve this in two ways:
info && info.map
But your initial state must be falsy value like:
useState(null)
Or better way to set initial state to empty array and map will run when data will come and you will get no error.
So basicly useEffect function is equivalent of componentDidMount. What I'm trying to say your component renders and then it executes function passed in useEffect. To avoid this eighter use check that you introduced as a fix yourself or pass default value to useState method. I would suggest your option but with condition if info exists show it and if it's not then show some kind of loading indicator.
A use Effect with a [] as second can be interpreted as a 'componentDidMount'
To give a very simple answer, your code is 'executed' the first time without the useEffect. So indeed, 'info' will not exist. At this point your 'info' variable is not yet defined. Then, when your component 'is mounted', it will execute the code in your useEffect. Only then info will be filled in.
I would recommend to go through this documentation to fully understand this: https://reactjs.org/docs/state-and-lifecycle.html

Do I have to worry about useState causing a re-render?

NB: I've asked this on wordpress.stackexchange, but it's not getting any response there, so trying here.
I'm not sure if this is WordPress specific, WordPress's overloaded React specific, or just React, but I'm creating a new block plugin for WordPress, and if I use useState in its edit function, the page is re-rendered, even if I never call the setter function.
import { useState } from '#wordpress/element';
export default function MyEdit( props ) {
const {
attributes: {
anAttribute
},
setAttributes,
} = props;
const [ isValidating, setIsValidating ] = useState( false );
const post_id = wp.data.select("core/editor").getCurrentPostId();
console.log('Post ID is ', post_id);
const MyPlaceholder = () => {
return(
<div>this is a test</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
If I comment out const [ isValidating, setIsValidating ] = useState( false ); then that console.log happens once. If I leave it in, it happens twice; even if I never check the value of isValidating, never mind calling setIsValidating. I don't want to over-optimize things, but, equally, if I use this block n times on a page, the page is getting rendered 2n times. It's only on the admin side of things, because it's in the edit, so maybe not a big deal, but ... it doesn't seem right. Is this expected behavior for useState? Am I doing something wrong? Do I have to worry about it (from a speed perspective, from a potential race conditions as everything is re-rendered multiple times)?
In your example code, the console.log statement is being immediately evaluated each time and triggering the redraw/re-rendering of your block. Once console.log is removed, only the state changes will trigger re-rendering.
As the Gutenberg Editor is based on Redux, if the state changes, any components that rely on that state are re-rendered. When a block is selected in the Editor, the selected block is rendered synchronously while all other blocks in the Editor are rendered asynchronously. The WordPress Gutenberg developers are aware of re-rendering being a performance concern and have taken steps to reduce re-rendering.
When requesting data from wp.data, useEffect() should be used to safely await asynchronous data:
import { useState, useEffect } from '#wordpress/element';
export default function MyEdit(props) {
...
const [curPostId, setCurPostId] = useState(false);
useEffect(() => {
async function getMyPostId() {
const post_id = await wp.data.select("core/editor").getCurrentPostId();
setCurPostId(post_id);
}
getMyPostId();
}, []); // Run once
const MyPlaceholder = () => {
return (
<div>Current Post Id: {curPostId}</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
As mentioned in the question, useState() is used in core blocks for setting and updating state. The state hook was introducted in React 16.8, its a fairly recent change and you may come across older Gutenberg code example that set state via the class constructor and don't use hooks.
Yes, you have to worry about always put an array of dependencies, so that, it won't re-render, As per your query, let's say are planning to edit a field here is the sample code
const [edit, setEdit]= useState(props);
useEffect(() => {
// logic here
},[edit])
that [edit] will check if there is any changes , and according to that it will update the DOM, if you don't put any [](array of dependencies) it will always go an infinite loop,
I guess this is expected behavior. If I add a similar console.log to native core blocks that use useState, I get the same effect. It seems that WordPress operates with use strict, and according to this answer, React double-invokes a number of things when in strict mode.

How do I console log the value of initialized useState in react?

Initializing values
const initialValues = {
bag: 32,
donor: "",
};
const [values, setValues] = useState(initialValues);
console.log(values.bag)
I don't know more about hooks but when I console.log the value is showing infinite looping value. Am I doing something wrong?
As you are using the functional components, you could set your state or put console.log into the useEffect() hook. Also, you could call your own function in this functional component to do the same. I am writing out both the scenarios in the below code snippet.
import React,{useEffect,useState} from 'react';
export default function IntroToReact(){
const initialValues = {bag: 32,donor:""};
const [values, setValues] = useState(initialValues);
//Effect is called once during init
useEffect(()=>{
console.log(values.bag);
},[]);
//Effect is called everytime the values change
useEffect(()=>{
console.log(values.bag);
},[values]);
// Display the values
function onChangeValues(){
console.log(values.bag)
}
return (
<div>
{onChangeValues()}
</div>
);
}`
For further understanding, this concept you could refer to the official website of the react:- https://reactjs.org/docs/hooks-effect.html
I hope it will help you.
Thank you :)
Hooks does not work inside classes. It works with function based component. It should always be used at the top level of the React Function.
I have run your code it's working fine. But you need to keep in mind some points using hooks first what syntax and approach you are following. Like your code needed to run in function-based component. I am adding a small snippet which is running fine.
import React, {useState} from 'react';
function Home(){
const initialValues = {
bag: 90,
donor: "",
};
const [values, setValues] = useState(initialValues);
console.log(values.bag)
return(
<div>
<h1>Welcome To Home</h1>
</div>
);}export default Home;
Set the indentation properly and call this component into your main component(if it is App.js or App.jsx and index.js or index.jsx)
Follow this official documentation of react.js. I am showing something in this documentation which will help you to know more about hooks.
enter link description here

Resources