React useEffect does infinite loop - javascript

My useEffect is getting data from web api. I want to render all posts on home page and trigger again my useEffect when someone create new post. The problem is when I put dependancy on useEffect its start doing endless requests. When I pass empty array as dependancy , when someone create new post it doesnt render on home page until I refresh the page. I read a lot in internet about that problem but still I dont know how to do it. Thanks
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const jwt = localStorage.getItem("jwt");
fetch('https://localhost:44366/api/Posts/getAllPosts',
{
method: "GET",
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + jwt
},
})
.then(r => r.json()).then(result => setPosts(result));
}, [posts]);
return (
<div >
<Router>
<Header />
<main className="App">
{
posts.map(post => (
<Post keyToAppend={post.CreatedOn} username={post.User.FirstName} title={post.Title} content={post.Content} images={post.ImageUrls} avatarImage={post.User.MainImageUrl} />
))
}
</main>
</Router>
<Footer />
</div>
);
}
Post component :
const Post = ({ keyToAppend, username, title, content, images, avatarImage }) => {
return (
<div className="post" key={keyToAppend}>
<div className="post__header">
<Avatar
className="post__avatar"
alt="avatar"
src={avatarImage}
/>
<h3 className="username">{username}</h3>
</div>
<h1 className="title">{title}</h1>
<p className="content">{content}</p>
<p>
{typeof images != "undefined" ? <ImageSlider slides={images} /> : ""}
</p>
</div>
)
}
export default Post;

Remove posts from the dependency array, so it's just []. That will run the effect once, when the component loads.
useEffect(() => {
const jwt = localStorage.getItem("jwt");
fetch('https://localhost:44366/api/Posts/getAllPosts',
{
method: "GET",
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + jwt
},
})
.then(r => r.json()).then(result => setPosts(result));
}, []);
// ^^−−−−− remove `posts` here
The reason it runs endlessly with your current code is that your effect callback changes the posts state member, which triggers the effect again (because posts is in the dependency array).
You only need to include things in the dependency array that you read in the effect callback. You never read posts in the effect callback.
Side note: That code is falling prey to the fetch API footgun I describe here. You need to check r.ok before calling r.json (and handle errors):
useEffect(() => {
const jwt = localStorage.getItem("jwt");
fetch("https://localhost:44366/api/Posts/getAllPosts", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + jwt
},
})
.then(r => {
if (!r.ok) { // <=================
throw new Error(`HTTP error ${r.status}`);
}
return r.json();
})
.then(result => setPosts(result))
.catch(error => {
// ...handle/report error here...
});
}, []);

Related

React: why at a time only single onclick component works while others fail?

Just started my React learning curve, please feel free to ask for clarifications if I am not able to explain. The question may look long but it's just the code for better understanding.
This is my app.js which uses the router, the problem arises for profile path.
function App() {
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/">
<h2>Welcome to jatin</h2>
<AnonymousUserApi />
<LoginButton />
<SignupButton />
</Route>
<Route exact path="/profile">
<Profile />
<LoginUserApi />
<History />
<LogoutButton />
</Route>
</Switch>
</div>
</Router>
);
}
Once user is authenticated using auth0, user is able to see but the problem is if i click component first then history component wont work and if i click history component first, loginuserAPi component wont work.
In both the components, I am calling different backend APIs for both the component which sends back data( there is no problem with them).
Also:- These components can be clicked anytime by the user, it's not like they will be used once.
Update :- after clicking the first component, when I try to click another component it always shows undefined.
I am seeing cors/preflight issue too, for the second component even though cors is enabled in express
my loginuserAPi component
import React, { useState } from 'react';
import { useAuth0 } from '#auth0/auth0-react';
var jwt = require('jsonwebtoken');
const LoginUserApi = () => {
const [orgURL, setorgURL] = useState('');
const [responseData, setresponseData] = useState('');
const { getAccessTokenSilently, user, isAuthenticated } = useAuth0();
let token;
const callSecureApi = async () => {
try {
token = await getAccessTokenSilently();
let decodedToken = jwt.decode(token, { complete: true });
console.log(token);
const response = await fetch(
'https://my api endpoint',
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
// sending subclaim as userId to backend Axios request!
body: JSON.stringify({
longURL: orgURL,
userId: decodedToken.payload.sub,
}),
}
);
const respData = await response.json();
console.log(respData);
setresponseData(respData.convertedURL);
console.log('jatin', responseData);
} catch (error) {
alert(error.error);
}
};
const submit = (e) => {
e.preventDefault();
console.log('clicked');
if (!orgURL) {
alert('URL cannot be empty');
}
};
return (
<div className="container">
<form onSubmit={submit}>
<div className="mb-3">
<label htmlFor="title">Enter your URL</label>
<input
type="text"
value={orgURL}
onChange={(e) => {
setorgURL(e.target.value);
}}
className="form-control"
id="title"
aria-describedby="emailHelp"
placeholder="Enter Your URL"
/>
</div>
<button
type="submit"
onClick={callSecureApi}
className="btn btm-sm btn-success"
>
Trimify
</button>
</form>
{orgURL && (
<div className="mt-5">
<h6 className="muted">Result</h6>
<div className="container-fluid">
<div className="row">
<p>{responseData}</p>
</div>
</div>
</div>
)}
</div>
);
};
export default LoginUserApi;
my history component.
import React, { useState } from 'react';
import { useAuth0 } from '#auth0/auth0-react';
var jwt = require('jsonwebtoken');
const History = () => {
const [historyOfUser, setHistory] = useState([]);
const { getAccessTokenSilently, user, isAuthenticated } = useAuth0();
let token;
const callHistoryApi = async () => {
try {
token = await getAccessTokenSilently();
let decodedToken = jwt.decode(token, { complete: true });
console.log(token);
const response = await fetch(
'https://history-enpoint-api',
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ userId: decodedToken.payload.sub }),
}
);
const respData = await response.json();
console.log(respData);
setHistory(respData.URLS);
console.log('jatin', historyOfUser);
} catch (error) {
alert(error.error);
}
};
return (
<>
<button onClick={callHistoryApi} className="btn btm-sm btn-success">
History
</button>
<div>
{historyOfUser.map((history) => <div key={history.originalURL}>{history.converted}</div> )}
</div>
</>
);
};
export default History;
In short how do multiple components fetching data from the backend can coexist?
when you have react as frontend which is calling api in the backend, such scenarious will come if api is caching the response.
if the api is protected API WHICH NEEDS SOME KIND OF EXCLUSIVE PERMISSION TO SPECIFIC RESOURCE THEN IF API IS CACHING IT THE RESPONSE FOR another resource it will cause error

Need help to display data from external API

I fetched some data from an API, Im trying to display the data but I'm doing something wrong. Can someone help? I have attached a photos of the fetched data on the console and my code[![data api
import React, {useState, useEffect} from 'react'
import './Track.css';
export default function Track() {
const [carbon] = useState([])
useEffect( () => {
const headers = {
'Accept':'application/json'
};
fetch('https://api.carbonintensity.org.uk/intensity',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
})
return (
<div>
<p>Track</p>
<div>
<p>{carbon.forecast}</p>
</div>
</div>
)
}
]1]1
Change to
import React, { useState, useEffect } from 'react'
import './Track.css';
export default function Track() {
const [carbon, setCarbon] = useState([])
useEffect(() => {
const headers = {
'Accept': 'application/json'
};
fetch('https://api.carbonintensity.org.uk/intensity',
{
method: 'GET',
headers: headers
})
.then(function (res) {
setCarbon(res.data)
}).then(function (body) {
console.log(body);
});
})
return (
<div>
<div>
{carbon.map((obj, i) => (
<li key={i}>
<ul>{obj.from}</ul>
</li>
))}
</div>
</div>
)
}
I recommend to you study https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map
you forgot some little thing:
first you forgot the setCarbon in the useStae hook you will need it to pass the response from the fetch.
You forgot to set the state in the fecth.
You will need to add a condition to render only when the state (carbon) is set.
you need to add an empty dependency to useEffect
import React, { useState, useEffect } from "react";
export default function Track() {
const [carbon, setCarbon] = useState([]);
useEffect(() => {
const headers = {
Accept: "application/json"
};
fetch("https://api.carbonintensity.org.uk/intensity", {
method: "GET",
headers: headers
})
.then((res) => {
return res.json();
})
.then((body) => {
console.log(body.data);
setCarbon(body.data);
});
}, []);
return (
<div>
<p>Track</p>
{carbon.length > 0 && (
<div>
{carbon.map((c, i) => (
<p key={i}>
<div>from: {c.from} </div>
<div>to: {c.to}</div>
<div>forecast: {c.intensity.forecast}</div>
<div>actual: {c.intensity.actual}</div>
<div>index: {c.intensity.index}</div>
</p>
))}
</div>
)}
</div>
);
}
Here you go,
Remember, state is like a place to store data for your component.
When you use fetch, you are getting data and now you need to save it to your state.
If you use state inside of your JSX, you can get the information to display.
Check out the console log, to look at the data structure that is returned from the fetch. This is what is set to the state "data". It can be called whatever you want. You can iterate through it, and dynamically display the data in JSX if you wanted, but I just hardcoded it for you so it's easier to understand.
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://api.carbonintensity.org.uk/intensity", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
.then((res) => res.json())
.then((data) => setData(data))
.catch((e) => console.error(e));
}, []);
console.log("data:", data);
return (
<div>
<p>Track</p>
<div>
<p>From: {data.data["0"].from}</p>
<p>To: {data.data["0"].to}</p>
<div>Intensity:</div>
<p>forecast: {data.data["0"].intensity.forecast}</p>
<p>forecast: {data.data["0"].intensity.actual}</p>
<p>forecast: {data.data["0"].intensity.index}</p>
</div>
</div>
);

React not re-rendering when setState following API calls

I have been struggling to understand what is going wrong with this simple todo list front end React app, which should interact with an express API.
my React code is:
import React, {Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
todos:[],
currentItem: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleADDButton = this.handleADDButton.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.updateTodo = this.updateTodo.bind(this);
}
componentDidMount(){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/list')
.then(res => res.json())
.then((todosList) =>
{this.setState({'todos': todosList});
});
}
handleChange(event){
this.setState({currentItem: event.target.value});
}
handleADDButton(event){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/post', {
method: 'POST',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
});
}
deleteItem(x){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + x, {
method: 'DELETE',
headers:{'Content-type': 'application/json'}
})
}
updateTodo(y){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + y, {
method: 'PUT',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
})
}
render() {
return(
<div>
<h1> Todo List </h1>
<ul>
{this.state.todos.map((todo) => <li> {todo.title}
<button type="button" onClick={() => this.deleteItem(todo.key)} >x</button>
<button type="button" onClick={() => this.updateTodo(todo.key)}>update</button> </li>)}
</ul>
<input type="text" value={this.state.currentItem} onChange={this.handleChange} />
<button type="submit" onClick={this.handleADDButton}>ADD</button>
</div>
)
}
}
export default App
The calls do update the API, and if I manually refresh the page, the React app picks up on the new data coming through from the API. However, when clicking the buttons it doesn't re-render by itself.
Say for example I click the ADD Button. It sends an OPTIONS to which I get back a 200 code, a POST which also comes back with a 200 and only sometimes, a GET with a 200. There is no pattern in when it performs the last GET call and also there is no pattern in when it re-renders following a button click. To obtain the latest data I always have to refresh.
Don't know what to make of this and have been stuck for days.
I think there is no state update on button actions
try to add a state updates for the actions same as componentDidMount
For ex:
handleADDButton(event){
event.preventDefault();
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/post', {
method: 'POST',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
}).then(res => res.json())
.then((data) => {
this.setState((prevState) {
const todos = [...prevState.todos, data.todo];
return {
todos: todos
}
})
});
}
In this case you have to return the new todo which will catch in data.todo
And for delete action
deleteItem(x){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + x, {
method: 'DELETE',
headers:{'Content-type': 'application/json'}
}).then(res => res.json())
.then((data) => {
this.setState((prevState) {
const newTodos = prevState.todos.filter(t => t.key !== x);
return {
todos: newTodos
};
})
});
}
These codes are not tested.
actually you don't have any state update in your code. you have to use "setState" when fetching data from API. I recommended learning arrow function and Hooks and use somethings like AXIOS to manage API calls.

React Functional Component - useState / useCallback - value changes back / reverts to initial value on submit

I have a functional component where I am submitting a text value entered by the user.
import React, { useState, useEffect, useCallback } from 'react'
// other imports
function Settings (props) {
const [primaryColor, setPrimaryColor] = useState('#E02E26');
useEffect(() => {
fetch(`//URL`, {...})
.then(res => res.json())
.then(
(result) => {
setPrimaryColor(result.primaryColor);
})
},[]);
const handlePrimaryColorChange = useCallback((newValue) => {
setPrimaryColor(newValue);
}, []);
const handlePCChange = useCallback((newValue) => {
setPrimaryColor(newValue.hex)
}, []);
const handleSubmit = useCallback((_event) => {
fetch(`//URL`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
primaryColor: primaryColor
})})
.then(res => res.json())
.then((result) => {
console.log('response recieved from post api');
})
}, []);
return (
<div>
<Page title="Customise UI">
<Form onSubmit={handleSubmit}>
<TextField type="text" onChange={handlePrimaryColorChange} value={primaryColor} />
<SketchPicker disableAlpha={true} color={primaryColor} onChangeComplete={handlePCChange}/>
<Button primary submit>Save Settings</Button>
</Form>
</Page>
</div>
)
Settings.getInitialProps = async (context) => {
return context.query;
}
The data is correctly loaded by 'useEffect' and 'primaryColor' is set and the correct values are displayed on TextField and SketchPicker components.
When I change values in either TextField and SketchPicker then the value gets updated on-screen in the other component correctly.
Now, when I click on Submit, the value that is received on the backend or if I print it just before fetch is '#E02E26' (the initial value in useState). The fetch request is successful.
What is going wrong here? I want to send the current primaryColor value in the fetch body.
Try adding primaryColor to the array:
const handleSubmit = useCallback((_event) => {
fetch(`//URL`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
primaryColor: primaryColor
})})
.then(res => res.json())
.then((result) => {
console.log('response recieved from post api');
})
}, [primaryColor]);

componentDidMount not rendering

I have an Header component that suppose to render his child components by a the condition of if the user is logged. It recognize the condition by the session-storage. I tried to control the rendering by
componentDidMount:
renderUserHeader = () => {
if (sessionStorage.getItem('user-auth')) {
var tokenToSend = { token: sessionStorage.getItem('user-auth') }
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: regJSONED
})
.then(response => {
if (!response.ok) {
throw new Error('HTTP error ' + response.status)
}
return response.text()
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name, JsonedUserName.admin)
})
if (!this.state.admin) {
return <UserHeader name={this.state.userName} />
} else if (this.state.admin) {
return <AdminHeader name={this.state.userName} />
}
} else if (!sessionStorage.getItem('user-auth')) {
return (
<Link to='/login'>
{' '}
<LoginLink />{' '}
</Link>
)
}
}
componentDidMount() {
this.renderUserHeader()
}
As you can see the renderUserHeader is being the component did mount but it is not working.
I have tried calling renderUserHeader inside the render and it worked but it keeps bugging and I have to refresh the page everytime.
render() {
return (
<header>
<Logo />
{this.renderUserHeader()}
</header>
)
}
Can someone tell me why componentDidMount doesn't not work?
componentDidMount is used for side effects like fetching data and updating component state only. If you return some component (eg <Link />) from componentDidMount it won't be rendered.
And you should never do any side effects inside render.
Instead, you should fetch and update state in the componentDidMount and based on the state render corresponding components.
componentDidMount not rendering
as said in above answer:componentDidMount is used for side effects like fetching data and updating component state only.
now
how to make it work with your code
in order to make it work,your componentDidMount should be like
componentDidMount(){
if(sessionStorage.getItem('user-auth')){
var tokenToSend = {token: sessionStorage.getItem('user-auth')}
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers:{
"Content-Type": "application/json"
},
body: regJSONED,
}).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.text();
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name,JsonedUserName.admin )
})
}
and your renderUserHeader should be like
renderUserHeader = () =>{
if(!sessionStorage.getItem('user-auth')){
return <Link to="/login"> <LoginLink /> </Link>
}
if(!this.state.admin){
return <UserHeader name ={this.state.userName}/>
}
else if(this.state.admin){
return <AdminHeader name ={this.state.userName}/>
}
}
and you can call it from render method.
you can use conditional rendering with ternary operator.
{this.state.isLoggedIn ? (some JSX if true) : (some JSX if false or just NULL)}
in the componentDidMount you can set the isLoggedIn property in the state, so every time the component loads - (render method will run) - the condition will be checked again

Categories

Resources