State updates by itself in React - javascript

I want to build a small quiz app in React using jservice.io
However, I can't find a solution for this problem: after I fetch random question in updateQuestion(), somehow, data fetches again (or changes in some other way) when I call checkAnswer(), even though it's not needed and not called.
The code:
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
useEffect(() => {
updateQuestion()
}, [])
async function updateQuestion () {
await fetch(`http://jservice.io/api/random`)
.then(data => data = data.json())
.then(function (response) {
setQuestion(response[0].question)
setAnswer(response[0].answer)
console.log("fetched")
} )
const title = document.querySelector(".question_title")
title.innerText = question
console.log(`${question} - when update question`)
console.log(`${answer} - when update question`)
}
function checkAnswer () {
console.log(`${question} - when check answer`)
console.log(`${answer} - when check answer`)
}
}

It is hard to say without seeing how you are calling checkAnswer or a valid code example. However, I've tried to reproduce what I presume is a comparable example that works as I believe you expect it should:
import React, { useState, useEffect } from "react";
function App() {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState("");
function updateQuestion() {
// Fake fetch
new Promise(resolve => {
const data = [
{
answer: "to winnow",
question: "To sift through material \u0026 toss out the junk"
}
];
resolve(data[0]);
}).then(response => {
setQuestion(response.question);
setAnswer(response.answer);
});
console.log(`${question} - when update question`);
console.log(`${answer} - when update question`);
}
function checkAnswer() {
console.log(`${question} - when check answer`);
console.log(`${answer} - when check answer`);
}
useEffect(() => {
console.log("calling useEffect");
updateQuestion();
});
return (
<div>
<h1>{question}</h1>
<p>{answer}</p>
<button onClick={() => checkAnswer()}>Click Me!</button>
</div>
);
}
export default App;
If you need more information, we're going to need more code. :)
Hope that helps!

You triggered it yourself because useEffect is triggered at least twice:
you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
https://reactjs.org/docs/hooks-effect.html
So it triggers once at componentDidMount, then once again at componentDidUpdate

Related

How to use data of an Async function inside a functional component to render HTML in React

I've been trying to use the data I get from an Async function inside of another function I use to display HTML on a react project. I have made several attempts but nothing seems to work for me. Hope any of you could help me. Please correct me if I did anything wrong.
I've tried it with a useEffect as well:
import React, { useState, useEffect } from 'react';
import { getGenres } from './api/functions';
const ParentThatFetches = () => {
const [data, updateData] = useState();
useEffect(() => {
const getData = async () => {
const genres = await getGenres('tv');
updateData(genres);
}
getData();
}, []);
return data && <Screen data={data} />
}
const Screen = ({data}) => {
console.log({data}); //logs 'data: undefined' to the console
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
</div>
);
}
export default Screen;
The Error I get from this is: {data: undefined}.
The getGenres function that makes the HTTP Request:
const apiKey = 'key';
const baseUrl = 'https://api.themoviedb.org/3';
export const getGenres = async (type) => {
const requestEndpoint = `/genre/${type}/list`;
const requestParams = `?api_key=${apiKey}`;
const urlToFetch = baseUrl + requestEndpoint + requestParams;
try {
const response = await fetch(urlToFetch);
if(response.ok) {
const jsonResponse = await response.json();
const genres = jsonResponse.genres;
return genres;
}
} catch(e) {
console.log(e);
}
}
I want to use the data inside my HTML, so the H1 for example.
Once again, haven't been doing this for a long time so correct me if I'm wrong.
There are a few conceptual misunderstandings that I want to tackle in your code.
In modern React, your components should typically render some type of jsx, which is how React renders html. In your first example, you are using App to return your genres to your Screen component, which you don't need to do.
If your goal is to fetch some genres and then ultimately print them out onto the screen, you only need one component. Inside that component, you will useEffect to call an asynchronous function that will then await the api data and set it to a react state. That state will then be what you can iterate through.
When genres is first rendered by react on line 6, it will be undefined. Then, once the api data is retrieved, React will update the value of genre to be your array of genres which will cause the component to be re-rendered.
{genres && genres.map((genre) ... on line 20 checks to see if genres is defined, and only if it is, will it map (like looping) through the genres. At first, since genres is undefined, nothing will print and no errors will be thrown. After the genres are set in our useEffect hook, genres will now be an array and we can therefore loop through them.
Here is a working example of your code.
import React, { useState, useEffect } from "react";
import { getGenres } from "./api/functions";
function App() {
const [genres, setGenres] = useState();
useEffect(() => {
async function apiCall() {
const apiResponse = await getGenres("tv");
console.log(apiResponse);
setGenres(apiResponse);
}
apiCall();
}, []);
return (
<div>
<h1 className="text-3xl font-bold underline">H1</h1>
{genres && genres.map((genre) => <div key={genre}>{genre}</div>)}
</div>
);
}
export default App;
You should use a combination or useEffect and useState
You should use useEffect to launch the async get, without launching it each rerendering. If a async function is used to get some data from outside the component, this is called 'side effect' so use useEffect.
You should use useState to react to changes on theses side effects, re-rendering the component to get the data in the dom.
In the next example, Im using a dummy async function getGenres which returns an array of genres.
Here is an example and a WORKING EXAMPLE :
const {useState, useEffect} = React;
async function getGenres() {
var promise = new Promise(function(resolve, reject) {
window.setTimeout(function() {
resolve( ['genre1', 'genre2']);
});
});
return promise;
}
const Screen = () => {
const [genres, setGenres] = useState([])
useEffect(
() => {
getGenres().then(
res => setGenres(res)
)
}, [getGenres]
)
return (
<ul>
{
genres.map(
i => <li>{i}</li>
)
}
</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Screen/>)

React render component only for few seconds

In my existing react component, I need to render another react component for a specific time period.
As soon as the parent component mounts/or data loads, the new-component (or child component) should be visible after 1-2 seconds and then after another few seconds, the new-component should be hidden. This needs to be done only if there is no data available.
This is what currently I've tried to achieve:
import React, { useState, useEffect } from "react";
function App() {
const [showComponent, setShowComponent] = useState(false);
const sampleData = [];
useEffect(() => {
if (sampleData.length === 0) {
setTimeout(() => {
setShowComponent(true);
}, 1000);
}
}, [sampleData]);
useEffect(() => {
setTimeout(() => {
setShowComponent(false);
}, 4000);
}, []);
const componentTwo = () => {
return <h2>found error</h2>;
};
return <>First component mounted{showComponent && componentTwo()}</>;
}
export default App;
The current implementation is not working as expected. The new-component renders in a blink fashion.
Here is the working snippet attached:
Any help to resolve this is appreciated!
Every time App renders, you create a brand new sampleData array. It may be an empty array each time, but it's a different empty array. Since it's different, the useEffect needs to rerun every time, which means that after every render, you set a timeout to go off in 1 second and show the component.
If this is just a mock array that will never change, then move it outside of App so it's only created once:
const sampleData = [];
function App() {
// ...
}
Or, you can turn it into a state value:
function App() {
const [showComponent, setShowComponent] = useState(false);
const [sampleData, setSampleData] = useState([]);
// ...
}
I have modified the code to work, hope this how you are expecting it to work.
import React, { useState, useEffect } from "react";
const sampleData = [];
// this has to be out side or passed as a prop
/*
reason: when the component render (caued when calling setShowComponent)
a new reference is created for "sampleData", this cause the useEffect run every time the component re-renders,
resulting "<h2>found error</h2>" to flicker.
*/
function App() {
const [showComponent, setShowComponent] = useState(false);
useEffect(() => {
if (sampleData.length === 0) {
const toRef = setTimeout(() => {
setShowComponent(true);
clearTimeout(toRef);
// it is good practice to clear the timeout (but I am not sure why)
}, 1000);
}
}, [sampleData]);
useEffect(() => {
if (showComponent) {
const toRef = setTimeout(() => {
setShowComponent(false);
clearTimeout(toRef);
}, 4000);
}
}, [showComponent]);
const componentTwo = () => {
return <h2>found error</h2>;
};
return <>First component mounted{showComponent && componentTwo()}</>;
}
export default App;
You can try this for conditional rendering.
import { useEffect, useState } from "react";
import "./styles.css";
const LoadingComponent = () => <div>Loading...</div>;
export default function App() {
const [isLoading, setLoading] = useState(true);
const [isError, setIsError] = useState(false);
const onLoadEffect = () => {
setTimeout(() => {
setLoading(false);
}, 2000);
setTimeout(() => {
setIsError(true);
}, 10000);
};
useEffect(onLoadEffect, []);
if (isLoading) {
return <LoadingComponent />;
}
return (
<div className="App">
{isError ? (
<div style={{ color: "red" }}>Something went wrong</div>
) : (
<div>Data that you want to display</div>
)}
</div>
);
}
I needed to do imperatively control rendering an animation component and make it disappear a few seconds later. I ended up writing a very simple custom hook for this. Here's a link to a sandbox.
NOTE: this is not a full solution for the OP's exact use case. It simply abstracts a few key parts of the general problem:
Imperatively control a conditional render
Make the conditional "expire" after duration number of milliseconds.

Browser freezing when using React Hook

When I enter into a router that refers to this Component, my browser just crashes. I've made some console tests and notices that when the response.data.message is up, it continually re-renders the page. Can someone help me?
import React from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
return (
<div>
{fetchUsername()}
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
On every render, fetchUsername() is called and results in updating statue and stories, which results in another rerender, and thus leads to an infinite loop of rerendering (since every render triggers a state update).
A better practice for handling functions with side-effects like fetching data is to put the fetchUsername in useEffect.
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {})
.then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
});
}, []); // An empty denpendency array allows you to fetch the data once on mount
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
You can't call function fetchUsername() inside return statement. It
will go in infinite loop.
You can't directly set the state of two variable as setStatue(response.data.statue) & setStories(response.data.message) respectively
use the useEffect hooks and set the status inside that hooks with conditional rendering to avoid looping.
call the fetchUsername() just before the return statement.
I looked at your code carefully. As you can see in this code, the fetchUsername() function is executed when the component is first rendered.
When you call setState() in the fetchUsername function, your component's state variable is updated. The problem is that when the state variable is updated, the component is rendered again. Then the fetchUsername function will be called again, right?
Try the useEffect Hook.
The example code is attached.
eg code
How about trying to change the way you call the fetchUsername() function with the useEffect hook instead?
import React, { useEffect } from 'react'
import "./UsernameStory.css";
import Axios from "axios";
const UsernameStory = ({match}) => {
const [statue , setStatue] = React.useState("");
const [stories , setStories] = React.useState([]);
useEffect(() => {
const fetchUsername = () => {
const urls = match.params.username;
Axios.post("http://localhost:8080/"+urls, {
}).then((response) => {
if(response.data.statue)
{
setStatue(response.data.statue);
}
if(response.data.message){
setStories(response.data.message);
}
})
}
fetchUsername();
// clean up here
return () => {
// something you wanna do when this component being unmounted
// console.log('unmounted')
}
}, [])
return (
<div>
<p>{statue}</p>
<ul>
{stories.map((story , key) => (<li key={key}>{story.username}</li>))}
</ul>
</div>
)
}
export default UsernameStory
But, sometimes your code just doesn't need a cleanup in some scenarios, you could read more about this here: Using the Effect Hook - React Documentation.

How to load dynamically data from localStorage in React JS?

I'm trying to display data stored in localstorage dynamically, meaning when data is added to localstorage it should be also displayed on the same page at the same time. I have the following code:
const [peopleInfo, getPeopleInfo] = useState(
JSON.parse(localStorage.getItem("peopleInfo"))
);
const [objects, getObjectsList] = useState(
JSON.parse(localStorage.getItem("objects"))
);
const setPeopleInfo = async () => {
const people = await JSON.parse(localStorage.getItem("peopleInfo"));
getPeopleInfo(people);
};
const getObjects = async () => {
const object = await JSON.parse(localStorage.getItem("objects"));
getObjectsList(object);
};
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
setPeopleInfo();
getObjects();
}
return () => (isSubscribed = false);
});
When useEffect doesn't include getObject() like this:
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
setPeopleInfo();
}
return () => (isSubscribed = false);
});
code works fine, peopleInfo gets updated every time when localStorage changes, but when I include getObjects() and other similar functions to get all the necessary data, app crashes without showing errors. I don't know the reason why. When I use useEffect with empty [], data doesn't get updated on localStorage change unless I refresh the page (this is not what I want). Can anyone please suggest what to do?
According to your code useEffect call, every time when your state changes, if you are using [] as your dependency useEffect call only one time but I realize when you call getObjects() function in useEffect() it changes the state every time so your state changes infinity times
import React,{useState,useEffect} from "react";
import "./style.css";
export default function App() {
const [objects, getObjectsList] = useState([{ name: "object1" }]);
useEffect(() => {
getObjectsList([{ name: "object1" }])
console.log("loop executed");
});
return <div />;
}
so I just change some code
import React, { useState, useEffect} from "react";
import "./style.css";
export default function App() {
const [objects, getObjectsList] = useState([{ name: "object1" }]);
useEffect(() => {
setInterval(() => {
const localStorageValue = [{ name: "object2" }]; //JSON.parse(localStorage.getItem("objects"));
if (JSON.stringify(localStorageValue) !== JSON.stringify(objects)) {
getObjectsList(localStorageValue);
}
}, [1000]);
}, []);
return <div />;
}
in this code, setInterval check every time if your local storage changes it update your state

Why does my timer component behave strangely when passing a dependency to useEffect?

I have the following timer using useEffect and pass a function dependency to it:
const Timer = () => {
const [count, setCount] = useState(0);
const setId = () => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}
useEffect(() => {
setId();
}, [setId])
}
However the timer behaves strangely: the first several seconds is normal, then it starts showing the count randomly. What caused the problem? What's the correct way of doing it?
Check this snippet, A good doc by Dan
import React, { useCallback, useState, useEffect, useRef } from "react";
import "./styles.css";
export default function App() {
let [count, setCount] = useState(0);
useInterval(() => {
setCount(count + 1);
}, 1000);
return <div className="App">{count}</div>;
}
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Working codesandbox
Update
As keith suggested don't pass the function to array deps as shown follow.
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <div>{count}</div>;
}
clear the interval on unmount
useEffect(() => {
let id;
const setId = () => {
id = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
};
setId();
return () => clearInterval(id);
}, []);
#DILEEP THOMAS update answer for something like this is how I would do it.
But #thinkvantagedu comment
still not too sure why it is okay to pass []
Maybe needs more explaining, unfortunately too much for comments so I'm posting this.
To answer this, we need to take a step back, and first ask, what is the point of the dependency array?. The simple answer is anything inside the useEffect that depends on something that if changed from say either useState or Props that would require different handling, it would want putting in the array.
For example, if say you had a component that simply displayed a userProfile, but getting this info was async, so requires a useEffect. It might look something like this ->
<UserProfile userId={userId}/>
Now the problem here is if we passed [] as the dependency, the useEffect would not re-fire for the the new userId, so the props might say userId = 2, but the data we currently have stored in state is for userId = 1,.. So for something like this [props.userId] would make total sense.
So back to the OP's component <App/> what is there here that would change?, well it's not the props, as none are passed. So what about count you might ask, well again ask yourself the question does the state of count warrant a new instance of a setInterval been destroyed / created?. And of course the answer here is no, and as such passing [] makes total sense here.
Hope that make sense,.

Categories

Resources