how to change state by setState in React hooks - reactjs

App.js
<Route path="/detail/:id" >
<PostDetail />
</Route>
PostDetail.js
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios'
const PostDetail = () => {
const { id } = useParams();
let [comments, setComments] = useState([]);
useEffect(async () => {
await axios
.all([
axios.get(`https://dummyapi.io/data/v1/post/${id}`, {
headers: { "app-id": process.env.REACT_APP_API_KEY }
}),
axios.get(`https://dummyapi.io/data/v1/post/${id}/comment`, {
headers: { "app-id": process.env.REACT_APP_API_KEY }
})
])
.then(
axios.spread((detail, comment) => {
console.log("before: ", comments)
console.log("data:", comment.data.data)
setComments([...comment.data.data])
console.log("after: ", comments)
})
)
.catch((detail_err, comment_err) => {
console.error(detail_err);
console.error(comment_err);
});
}, []);
return (
<div>
detail page:
</div>
);
};
export default PostDetail;
and got some data with axiosin useEffect hook, and used setComments() with the data(comment.data.data). but it doesn't set the axios data for some reason. What's wrong with it? If you help me out, it would be a huge help

The setter method (setComments) is asynchronous. Therefore you cannot expect to get the updated value to log right after it.
setComments([...comment.data.data])
console.log("after: ", comments)
You should move the log to the component's top level.
const PostDetail = () => {
const { id } = useParams();
let [comments, setComments] = useState([]);
useEffect(async () => {
await axios
.all([
axios.get(`https://dummyapi.io/data/v1/post/${id}`, {
headers: { "app-id": process.env.REACT_APP_API_KEY }
}),
axios.get(`https://dummyapi.io/data/v1/post/${id}/comment`, {
headers: { "app-id": process.env.REACT_APP_API_KEY }
})
])
.then(
axios.spread((detail, comment) => {
console.log("before: ", comments)
console.log("data:", comment.data.data)
setComments([...comment.data.data])
})
)
.catch((detail_err, comment_err) => {
console.error(detail_err);
console.error(comment_err);
});
}, []);
// move the log to here
console.log("after: ", comments)
return (
<div>
detail page:
</div>
);
};
export default PostDetail;
If you want to do some other work when comments gets changed, add a useEffect hook with comments as a dependancy.
useEffect(() => {
console.log(comments);
}, [comments]);

Related

React Hook useEffect has a missing dependency: 'data'. Either include it or remove the dependency array

I have a filter function which is filtering data with state data, dataCopy and searchValue. Issue is if i don't include the data state than react gives warning and if i do include it it cause infinite loop cause the data array is changing within the useEffect. How can i make so that i don't get that warning.
Filter function
import React, { useEffect, useState } from 'react'
import Header from '../Components/Header/Header'
import Home from '../Components/Home/Home'
import "./Layout.css"
import Spinner from '../Components/Spinner/Spinner'
function Layout() {
// state for data, copy of data and spinner
const [data, setData] = useState([])
const [dataCopy, setDataCopy] = useState([])
// state for search input in Header.js (define in parent )
const [searchValue, setSearchValue] = useState("")
// changing search value
const changeSearchValue = (value) => {
setSearchValue(value)
}
// useEffect for search functionality
useEffect(() => {
const handleSearch = () => {
if (searchValue !== "") {
const searchFilter = data.filter(item =>
!isNaN(searchValue) ? item.expected_annually_bill_amount.toString().includes(searchValue) :
item.dmo_content.Ausgrid.toString().toLowerCase().includes(searchValue.toLowerCase()))
setData(searchFilter)
} else {
setData(dataCopy)
}
}
handleSearch()
}, [searchValue, dataCopy])
// useEffect for getting data from api
useEffect(() => {
// making post request to get the token
axios.post(`${process.env.REACT_APP_BASE_URL}`, { data: "" },
{
headers:
{
'Api-key': `${process.env.REACT_APP_API_KEY}`,
},
})
// after getting to token returning it for callback
.then((response) => {
return response.data.data.token
})
// using the token to call another api for the needed data
.then((tokenIs) => {
axios.post(`${process.env.REACT_APP_DATA_URL}`,
{ "session_id": `${process.env.REACT_APP_SESSION_ID}` },
{
headers:
{
'Api-key': `${process.env.REACT_APP_API_KEY}`,
'Auth-token': tokenIs,
},
})
.then((response) => {
setData(response.data.data.electricity)
setDataCopy(response.data.data.electricity)
setSpinner(false)
})
})
// catiching any error if happens
.catch((err) => {
setSpinner(false)
alert(err)
})
}, [])
return (<>
<div className='layout'>
<Header
changeSearchValue={changeSearchValue}
searchValue={searchValue}
/>
<Home data={data} />
</div>
)
}
export default Layout
Here you can eliminate data dependency by:
useEffect(() => {
const handleSearch = () => {
if (searchValue !== "") {
setData(data => data.filter(item =>
!isNaN(searchValue) ? item.expected_annually_bill_amount.toString().includes(searchValue) :
item.dmo_content.Ausgrid.toString().toLowerCase().includes(searchValue.toLowerCase())))
} else {
setData(dataCopy)
}
}
handleSearch()
}, [searchValue, dataCopy])
You can add the following comment above the dependency array for suppressing the warning
// eslint-disable-next-line react-hooks/exhaustive-deps

Why is my set data with useState not updating the state?

setAcessKeys is not updating the state immediately even though the data is available at that point which I know through the console.log(data) or by passing the array manually. I realized that without useEffect, it renders multiple times and the console.log(accessKeys) returns data from the third run going.
import { useState, useEffect } from "react";
import axios from "axios";
import AccessKey from "./AccessKey";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";
import "./AccessKey.module.css";
const AccessKeys = () => {
const [accessKeys, setAccessKeys] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const getAccessKeys = async () => {
try {
let token = localStorage.getItem("auth");
const response = await axios.get(
"http://localhost:5000/api/keys/user",
{
headers: {
authorization: `Bearer ${token}`,
},
}
);
const data = response.data;
console.log(data); // [{...}, {...}]
setAccessKeys((prevKeys) => [...prevKeys, ...data]);
console.log(accessKeys); // []
} catch (error) {
navigate("/");
toast.error(error.response.data.message);
}
};
getAccessKeys();
}, [navigate, accessKeys]);
return (
<>
{accessKeys.length > 0 ? (
<main>
{accessKeys.map((accessKey) => (
<AccessKey key={accessKey._id} acesskey={accessKey} />
))}
</main>
) : (
<h4>You do not have any Access Keys at the moment</h4>
)}
</>
);
};
export default AccessKeys;

How can I add sorting using Redux?

How can I add sorting for API returning a JSON array? I'm new to Redux. I have installed the redux. Could someone tell me what's the best method to follow?
Thanks for your help.
import React, { useState, useEffect } from "react";
import Post from "../../Components/Post/Post";
import axios from "axios";
const HomePage = () => {
const [posts, setPosts] = useState("");
let config = { Authorization: "............." };
const url = "..........................";
useEffect(() => {
AllPosts();
}, []);
const AllPosts = () => {
axios
.get(`${url}`, { headers: config })
.then((response) => {
const allPosts = response.data.articles;
console.log(response);
setPosts(allPosts);
})
.catch((error) => console.error(`Error: ${error}`));
};
return (
<div>
<Post className="Posts" posts={posts} />
</div>
);
};
export default HomePage;
You don't have redux here. Do you need it?
If you want to sort result and save sorted results to state:
...
.then((response) => {
const allPosts = response.data.articles;
// sort the result here
const sortedPosts = allPosts.sort((a,b) =>
// comparer
);
setPosts(sortedPosts);
})
...

React TypeScript: How to call an api and set state each time the route changes

useEffect fires perfectly each time I change the route, When I call the API from within useEffect, and then try to set state with the result I get the error Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I have tried invoking getPrice with a self-invoking function and nothing changes, I still get the same error.
Should I be using Suspense ??
import React, { useContext, useEffect, useState } from 'react';
const Calc: React.FC = () => {
interface StateInterface {
priceUsd: number;
}
const [price, setPrice] = useState<StateInterface>({
priceUsd: 0,
});
useEffect(() => {
const getPrice = async () => {
const response = await fetch('http://localhost:9999/price', {
body: JSON.stringify({
jwtToken: localStorage.getItem('jwtToken'),
}),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
});
if (response.status !== 400) {
const content = await response.json();
const priceUsd = content.price[0].priceUsd;
setPrice({ priceUsd });
}
};
getPrice();
}, []);
return (
<div>Calc</div>
)
}
export { Calc };
This calc component gets loaded in the router like this
import React, { useReducer } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { globalContext, setGlobalContext } from './components/shell/context';
import { Layout } from './components/shell/layout';
import { defaultState, globalReducer } from './components/shell/reducer';
import { Calc } from './routes/calc';
import { NotFound } from './routes/not-found';
export function Router(): JSX.Element {
const [global, setGlobal] = useReducer(globalReducer, defaultState);
return (
<setGlobalContext.Provider value={{ setGlobal }}>
<globalContext.Provider value={{ global }}>
<BrowserRouter>
<Route
render={({ location }) => (
<Layout location={location}>
<Switch location={location}>
<Route exact path='/' component={Calc} />
<Route component={NotFound} />
</Switch>
</Layout>
)}
/>
</BrowserRouter>
</globalContext.Provider>
</setGlobalContext.Provider>
);
}
I can't tell any obvious problem from the code you share. But the error says that, when setPrice({ priceUsd }) is called, the <Calc /> component is already unmounted.
So the problem is elsewhere, its parent component un-renders the <Calc /> before completion of fetch logic.
I propose a method to verify, see (+/-) sign for diff:
import React, { useContext, useEffect, useState } from 'react';
const Calc: React.FC = () => {
interface StateInterface {
priceUsd: number;
}
const [price, setPrice] = useState<StateInterface>({
priceUsd: 0,
});
useEffect(() => {
const getPrice = async () => {
const response = await fetch('http://localhost:9999/price', {
body: JSON.stringify({
jwtToken: localStorage.getItem('jwtToken'),
}),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
});
if (response.status !== 400) {
const content = await response.json();
const priceUsd = content.price[0].priceUsd;
- setPrice({ priceUsd });
+ console.log('calling setPrice()', priceUsd);
}
};
getPrice();
+ return () => { console.log('I got cleaned-up') }
}, []);
return (
<div>Calc</div>
)
}
export { Calc };
If my theory is correct, we expect to see "I got cleaned-up" printed in console first before "calling setPrice()"
I had the same problem, the thing what you need to do is write the function outside the useEffect and call that function, some thing like this.
const getPrice = async () => {
const response = await fetch('http://localhost:9999/price', {
body: JSON.stringify({
jwtToken: localStorage.getItem('jwtToken'),
}),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
});
if (response.status !== 400) {
const content = response.json();
const priceUsd = content.price[0].priceUsd;
setPrice({ priceUsd });
}
};
useEffect(() => {
getPrice();
}, []);

Using react useEffect hook

I'm using React useEffect hook for getting data and display the loading indicator but my loading is not working.
Heres the useEffect Hook code:
useEffect(() => {
fetchEvents();
}, []);
fetchEvents function code:
const fetchEvents = () => {
setLoading(true);
const requestBody = {
query: `
query {
events {
_id
title
description
price
date
creator {
_id
email
}
}
}
`
};
fetch("http://localhost:5000/graphql", {
headers: {
"Content-Type": "application/json"
},
method: "POST",
body: JSON.stringify(requestBody)
})
.then(res => {
if (res.status !== 200 && res.status !== 201) {
throw new Error("Failed");
}
return res.json();
})
.then(resData => {
const events = resData.data.events;
setEvents(events);
setLoading(false);
})
.catch(err => {
console.log(err);
setLoading(false);
});
};
You should give more info but here an example for you:
import React, { useState, useEffect } from 'react';
import { Spinner } from 'react-bootstrap';
const MyComponent = () => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
useEffect(() => {
setIsLoading(true);
fetch('/data/endpoint')
.then((res) => res.json)
.then((response) => {
setData([...response]);
setIsLoading(false);
});
}, []);
return isLoading ? (
<Spinner />
) : (
<ol>
data.map(items => <li>{items.label}</li>);
</ol>
);
};
The first parameter of useEffect (the function) is only called if one object of the second parameter (the list) is modified. If the list is empty, it never happens.
You could remove the second parameter to apply the function fetchEvents at each update or you could use any constant to run fetchEvents only once.

Resources