I am getting this UseEffct error that causes my window to render an empty page
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."
my Component Code
import React, { useEffect, useState } from "react";
import { instance } from "../Data/axios";
const Row = ({ title, fetchURL }) => {
const [movies, setMovies] = useState([]);
// A snippet of code that runs on a certain condition
useEffect(() => {
async function fetchData() {
const data = await instance.get(fetchURL);
setMovies(data.data.results);
}
fetchData();
}, [fetchURL]);
const base_url = "https://image.tmdb.org/t/p/original/";
console.log(movies);
return (
<div className="rows">
<h3>{title}</h3>
<div className="rows_poster">
{movies.map((movie) => {
<div key={movie.id}>
<img src={`${base_url}${movie.poster_path}`} />
</div>;
})}
</div>
</div>
);
};
export default Row;
This has worked for me:
useEffect(() => {
let mounted = true;
async function fetchData(){
const data = await instance.get(fetchURL);
if (mounted) {
setMovies(data.data.results);
}
};
fetchData();
return ()=> {mounted = false}
}, []);
Related
Can someone explain what am I'm doing wrong?
I have a react functional component, where I use useEffect hook to fetch some data from server and put that data to state value. Right after fetching data, at the same useHook I need to use that state value, but the value is clear for some reason. Take a look at my example, console has an empty string, but on the browser I can see that value.
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
console.log(value);
};
fetchData();
}, [value]);
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
Link to codesandbox.
The useEffect hook will run after your component renders, and it will be re-run whenever one of the dependencies passed in the second argument's array changes.
In your effect, you are doing console.log(value) but in the dependency array you didn't pass value as a dependency. Thus, the effect only runs on mount (when value is still "") and never again.
By adding value to the dependency array, the effect will run on mount but also whenever value changes (which in a normal scenario you usually don't want to do, but that depends)
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
console.log(value);
};
fetchData();
}, [value]);
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
Not sure exactly what you need to do, but if you need to do something with the returned value from your endpoint you should either do it with the endpoint returned value (instead of the one in the state) or handle the state value outside the hook
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
// handle the returned value here
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
};
fetchData();
}, []);
// Or handle the value stored in the state once is set
if(value) {
// do something
}
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
when I try to get some data from my backend API using axios, and set the state after I've gotten the result for some reason the state is not updated and when I try to use the state it will only show me an empty array. but what's so interesting is that when I console.log(res.data) it will show me my array of lists with no problem, so I guess the problem is with the setCategories() state function. What am I doing wrong?
const Home = (props) => {
const [categories, setCategories] = useState([]);
useEffect(() => {
getCats();
}, []);
const getCats = async () => {
const data = await axios.get(`${myUrl}/allItems`, {
withCredentials: true,
});
const cats = await data.data;
console.log(cats); //this one works perfectly
setCategories(cats);
console.log(categories) //this one doesn'nt work which means the setState didn't work
};
return (
<>
<div className="card-div mt-5">
{categories.map((cat) => {
<li>{cat.name}</li>;
})}
</div>
</>
);
};
the state is set asynchronously, so the data is not updated instantly. that's why you are not getting the output on console.log(categories) right after setCategories(cats);
here is a small example of asynchronous behaviour of useState state update:
Link to working example: stackblitz
import React, { useEffect, useState } from "react";
import "./style.css";
import axios from "axios";
const url = "https://jsonplaceholder.typicode.com/users";
export default function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get(url).then(result => {
console.log("1. when data is fetched sucessfully: ", result.data);
setUsers(result.data);
console.log("2. Just after setting state: ", users);
});
}, []);
// secons useEffect for logging out upadated todos useState
useEffect(() => {
console.log("todos upadated: ", users);
}, [users]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
{users.map(user => (
<p>{user.name}</p>
))}
</div>
);
}
Here is what is happening in the above example:
You can see the flow of data fetching and async update of state.
The useState function is asynchronous, so you will never get the new state in the same function, the best way is to use it in another function or useEffect.
Example:
useEffect(() => {
console.log(categories);
}, [categories]);
The data is not displaying in the child component Store. In the Storecomponent, the console.log in the useEffect() hook returns undefined. I suspect the reason being that the fetchAPI function in the parent component is only called after myContext.Provider is rendered, thus the value of myContext.Provider is undefined.
How can I pass the data(hook state) I fetched from the API in Stores(parent) down to Store(child) in this case?
export const myContext = createContext()
const Stores = () =>{
const [data, setData ] = useState([])
const fetchAPI = async() => {
var res = await fetch('https://fortnite-api.theapinetwork.com/store/get')
var result = await res.json()
var final= result.map(item => item)
setData(final)
}
useEffect(() =>{
fetchAPI().then(console.log(data))
}, [data])
return(
<div>
<myContext.Provider value={data} >
{data.map(item => {
return(
<div>
<ul>
<Link to={`stores/${item.itemId}`}><li>{item.item.name}</li></Link>
</ul>
</div>
)
})}
</myContext.Provider>
</div>
)
};
const Store = () => {
const specific = useContext(myContext)
useEffect(
() => {
console.log(specific)
}
)
return(
<>
{specific.map( item => {
return(
<div>
<h2> Description: {item.name}</h2>
</div>
)
})}
</>
)
}
The way you use fetch api isn't correct I think. fetch returns a promise, so you need to return the promise in your fetchAPI method if you want to uses .then().
.then() allow you to grab the promise and work with it, here is your code changed to work:
import React, { Component, useState, useEffect, createContext } from "react";
import { render } from "react-dom";
const myContext = createContext()
const App = () =>{
const [data, setData ] = useState([]);
const fetchAPI = () => {
// It return a promise
return fetch('https://fortnite-api.theapinetwork.com/store/get');
}
useEffect(() =>{
fetchAPI().then(data =>
// try to call .json() method. this method returns a promise
data.json().then(json => {
// if .json() succeed, then do your stuff
console.log(json);
setData(json.data.map(item => item));
})
)
// if .json() fails, promise is rejected with your error
.then(err => console.log('err', err));
}, []); // empty array to prevent looping (rule : do not update a state you are passing in this deps array)
return(
<div>
<myContext.Provider value={data} >
{data.map(item => {
return(
<div>
<ul>
<li><pre>{JSON.stringify(item)}</pre></li>
</ul>
</div>
)
})}
</myContext.Provider>
</div>
)
};
render(<App />, document.getElementById("root"));
Here is the repro on Stackblitz.
The useEffect below renders, fetches data, and displays it once (using an empty array for 2nd parameter in useEffect).
I need it to rerun useEffect everytime the user changes data to the database (when user uses axios.post).
What i've tried
using [tickets], but that just causes the useEffect to run infinitly
also using [tickets.length] and [tickets, setTickets]
trying to use props as parameter but didnt find anything useful
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const TicketContext = createContext();
export const TicketProvider = (props) => {
console.log(props);
const [tickets, setTickets] = useState([]);
useEffect(() => {
getTickets();
console.log("1", { tickets });
}, []);
const getTickets = async () => {
const response = await axios.get("http://localhost:4000/tickets/");
setTickets(response.data);
};
return <TicketContext.Provider value={[tickets, setTickets]}>{props.children}
</TicketContext.Provider>;
};
import React from "react";
import { useState, useEffect, useContext } from "react";
import Ticket from "../Ticket";
import { TicketContext } from "../contexts/TicketContext";
import AddBacklog from "../addData/AddBacklog";
const TicketDisplay = (props) => {
const [tickets, setTickets] = useContext(TicketContext);
return (
<div className="display">
<p>Antony Blyakher</p>
<p>Number of Tickets: {tickets.length}</p>
<div className="backlog">
<h1>Backlog</h1>
{tickets.map((currentTicket, i) => (
<div className="ticketBlock">
<Ticket ticket={currentTicket} key={i} />
</div>
))}
</div>
</div>
);
const AddBacklog = (props) => {
const [tickets, setTickets] = useState("");
...
axios.post("http://localhost:4000/tickets/add", newTicket).then((res) => console.log(res.data));
setTickets((currentTickets) => [...currentTickets, { name: name, status: "backlog", id: uuid() }]);
};
You'll need to watch for tickets and return if it has data to not cause infinite loop:
useEffect(() => {
if (tickets.length) return // so, we call just once
getTickets();
console.log("1", { tickets });
}, [tickets]);
const fetchData = () => {
axios.get("http://localhost:7000/api/getData/").then((response) => {
console.log(response.data);
if (response.data.success) {
SetIsLoading(false);
}
setDataSource(response.data.data);
});
};
useEffect(() => {
fetchData();
if (fetchData.length) fetchData();
}, [fetchData]);
by this you can fetch the data in real-time as any change in data occurs.
I need to get data from server on changes in search input but I don't want to send a new request on every new character there so I'm trying use debounce from use-debounce package https://github.com/xnimorz/use-debounce. But my code below causes only endless requests before even any changes in search input happens.
App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import moment from "moment";
import { useDebounce } from "use-debounce";
import { Layout } from "./../Layout";
import { List } from "./../List";
import { Loader } from "./../Loader";
import { Header } from "./../Header";
import { Search } from "./../Search";
import { Licenses } from "./../Licenses";
import { Pagination } from "./../Pagination";
import "./App.css";
const PER_PAGE = 20;
export const App = () => {
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
const [nameSearch, setNameSearch] = useState("");
const [license, setLicense] = useState({});
const [currentPage, setCurrentPage] = useState(1);
const [total, setTotal] = useState(0);
const debouncedNameSearch = useDebounce(nameSearch, 2000);
const fetchData = async () => {
setHasError(false);
setIsLoading(true);
try {
const prevMonth = moment()
.subtract(30, "days")
.format("YYYY-MM-DD");
const licenseKey = (license && license.key) || "";
const url = `https://api.github.com/search/repositories?q=${nameSearch}+in:name+language:javascript+created:${prevMonth}${
licenseKey ? `+license:${licenseKey}` : ""
}&sort=stars&order=desc&page=${currentPage}&per_page=${PER_PAGE}`;
const response = await axios(url);
setData(response.data.items);
setTotal(response.data.total_count);
} catch (error) {
setHasError(true);
setData([]);
}
setIsLoading(false);
};
useEffect(() => {
fetchData();
}, [license, nameSearch, currentPage]);
return (
<Layout>
<Header>
<Search
handleNameSearchChange={setNameSearch}
nameSearch={nameSearch}
/>
<Licenses license={license} handleLicenseChange={setLicense} />
</Header>
<main>
{hasError && <div>Error...</div>}
{isLoading && <Loader />}
{data && !isLoading && !hasError && (
<>
<List data={data} />
<Pagination
currentPage={currentPage}
total={total}
itemsPerPage={PER_PAGE}
handlePageChange={setCurrentPage}
/>
</>
)}
</main>
</Layout>
);
};
Search.js
import React from "react";
import PropTypes from "prop-types";
export const Search = ({ handleNameSearchChange, nameSearch }) => (
<div className="flex-grow-1 mx-lg-3 mb-4 mb-lg-0">
<input
type="text"
name="search"
placeholder="Enter name..."
onChange={e => handleNameSearchChange(e.target.value)}
className="form-control"
value={nameSearch}
/>
</div>
);
Search.propTypes = {
nameSearch: PropTypes.string,
handleNameSearchChange: PropTypes.func
};
How to make debounce work properly?
You never refer to debouncedNameSearch.
I think the issue is with your useEffect:
useEffect(() => {
fetchData();
}, [license, nameSearch, currentPage]);
The first issue is that it will fire every time nameSearch changes, so you should change it to use debouncedNameSearch:
useEffect(() => {
fetchData();
}, [license, debouncedNameSearch, currentPage]);
You are also firing the request on initial render when debouncedNameSearch is an empty string, so you could wrap the call to fetchData in a conditional to prevent the request firing when debouncedNameSearch === "":
useEffect(() => {
if(debouncedNameSearch) {
fetchData();
}
}, [license, debouncedNameSearch, currentPage]);
Also, your request is using nameSearch when it should be using debouncedNameSearch:
const url = `https://api.github.com/search/repositories?q=${nameSearch}...
Change to:
const url = `https://api.github.com/search/repositories?q=${debouncedNameSearch}...
And it's recommended that any function that is declared inside a component and called inside a useEffect should either be declared inside the useEffect, or set as a dependency of that useEffect:
Read the docs: is it safe to omit functions from the list of dependencies?
So you can either do something like this:
useEffect(() => {
// Declare fetchData inside useEffect
const fetchData = async () => {...};
if (debouncedNameSearch) {
// Call it inside useEffect too
fetchData();
}
}, [
// Don't forget to add the function's dependencies
license,
debouncedNameSearch,
currentPage,
setHasError,
setIsLoading,
setData,
setTotal
]);
Or you can make the function itself a dependency of the useEffect, but you should wrap the function in a useCallback to make sure its state dependencies are up to date (as per the documentation linked above):
const fetchData = useCallback(
async () => {
// Function defined here
},
[ // function dependencies
setHasError,
setIsLoading,
license,
debouncedNameSearch,
currentPage,
setData,
setTotal,
setHasError
]
);
useEffect(() => {
if(debouncedNameSearch) {
fetchData();
}
}, [license, debouncedNameSearch, currentPage, fetchData]); // Add as dependency