change in hook state not updating value in template literals - reactjs

I am new to hooks and is coming after learning react with classes, so a bit lost. in the below code I am changing setDog to Husky which should then tell the API call to search and fetch me pic of a husky. But its not happening despite the change in dog. Can anyone see where I am going wrong?
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function ApiCalls() {
const [ data, setData ] = useState();
const [ dog, setDog ] = useState('labrador');
useEffect(() => {
const fetchData = async () => {
const result = await axios(`https://dog.ceo/api/breed/${dog}/images`);
setData(result.data.message[0]);
};
fetchData();
}, []);
const Husky = () => {
setDog('husky');
};
return (
<div>
<img alt={''} src={data} />
<button onClick={Husky}>Retrieve Husky</button>
</div>
);
}

Your useEffect sensivitylist is [], so this useEffect just run on component mount that the dog variable is labrador. So after you change dog on button click nothings new will be fetched from server. Change your code as follow:
useEffect(() => {
const fetchData = async () => {
const result = await axios(`https://dog.ceo/api/breed/${dog}/images`);
setData(result.data.message[0]);
};
fetchData();
}, [dog]);

useEffect only run once because the dependency array is [] empty. So when you change dog it wont trigger. To fix this add dog to useEffect dependency
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function ApiCalls() {
const [ data, setData ] = useState();
const [ dog, setDog ] = useState('labrador');
useEffect(() => {
const fetchData = async () => {
const result = await axios(`https://dog.ceo/api/breed/${dog}/images`);
setData(result.data.message[0]);
};
fetchData();
}, [dog]);
const Husky = () => {
setDog('husky');
};
return (
<div>
<img alt={''} src={data} />
<button onClick={Husky}>Retrieve Husky</button>
</div>
);
}

Do this
const fetchData = async (input) => {
const result = await axios(`https://dog.ceo/api/breed/${input}/images`);
setData(result.data.message[0]);
};
useEffect(() => fetchData(dog), []);

Related

How to get useEffect to not re-render infinitely

I am trying to render this CardsContainerCopy component after making an AJAX call with Redux-thunk.
If I leave the dependencies array in useEffect empty, the component doesn't render at all.
If I add cartItems to the dependencies array, the components will render but the fetchItems function keeps being called infinitely.
Code:
import React, { useEffect, useState } from "react";
import SingleCard from "./SingleCard";
import { createServer } from "miragejs";
import axios from "axios";
import itemsData from "../../config/ItemsData";
import { useDispatch, useSelector } from "react-redux";
import { selectCartItems } from "./shopSlice";
let server = createServer();
server.get("/api/food", itemsData);
const fetchItems = async (dispatch) => {
const itemsData = await axios.get("/api/food");
dispatch({ type: "shop/fetchedItems", payload: itemsData.data });
};
const CardsContainerCopy = () => {
const [items, setItems] = useState([]);
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
useEffect(() => {
dispatch(fetchItems);
setItems(cartItems);
}, [cartItems]);
return (
<>
{items?.map((item, i) => {
return <SingleCard props={item} key={i} />;
})}
</>
);
};
export default CardsContainerCopy;
Your useEffect function does create an infinite loop, as you're listening to cartItems changes which triggers dispatch again. To avoid infinite re render you can do something like this:
const CardsContainerCopy = () => {
const [items, setItems] = useState([]);
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
useEffect(()=>{
dispatch(fetchItems);
}, [])
useEffect(() => {
setItems(cartItems);
}, [cartItems]);
return (
<>
{items?.map((item, i) => {
return <SingleCard props={item} key={i} />;
})}
</>
);
};

How does react useEffect work with useState hook?

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;

How do I properly set up an API call using useEffect?

Here is my entire component. In the console the correct data is showing up at "data" but when I try to run map on it it says "map is not a function." The 16 items in the console are the correct beaches.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export default function Beaches() {
const [data, setData] = useState({beaches: []})
// const [hasError, setErrors] = useState(false)
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios('http://localhost:3000/beaches');
setData(result.data);}
fetchBeaches();
}, [])
console.log(data)
return (
<ul>
{data.beaches.map(beach => (
<button>{beach.name}</button>
))}
</ul>
)
}
Because you're not setting the beaches data in state correctly.
Replace useEffect code with this:
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios('http://localhost:3000/beaches');
setData({beaches: result.data});
}
fetchBeaches();
}, [])
furthermore, you can improve the state structure of beaches data:
import React, { useState, useEffect } from "react";
import axios from "axios";
export default function Beaches() {
const [beaches, setBeaches] = useState([]);
// const [hasError, setErrors] = useState(false)
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios("http://localhost:3000/beaches");
setBeaches(result.data);
};
fetchBeaches();
}, []);
return (
<ul>
{beaches.map((beach) => (
<button>{beach.name}</button>
))}
</ul>
);
}

Correct way to use useEffect() to update when data changes

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.

Using debounce with React hooks causes endless requests

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

Resources