const fetcher = (url: string) => fetch(url).then((r) => r.json());
const { data, error } = useSWR(
";,
fetcher,
);
is there any way to add data in a useState hook like this
const fetcher = (url: string) => fetch(url).then((r) => r.json());
const { data, error } = useSWR(
";,
fetcher,
);
const [memes,setMemes]=useState(data);
cause I want to concat the data at some point for inifnite scrolling
const fetcher = (url: string) => fetch(url).then((r) => r.json());
const { data, error } = useSWR(
"https://some./api",
fetcher,
);
is there any way to add data in a useState hook like this
const fetcher = (url: string) => fetch(url).then((r) => r.json());
const { data, error } = useSWR(
"https://meme-api.herokuapp./gimme/5",
fetcher,
);
const [memes,setMemes]=useState(data);
cause I want to concat the data at some point for inifnite scrolling
Share Improve this question asked Sep 27, 2021 at 11:09 Jatin HemnaniJatin Hemnani 3212 silver badges8 bronze badges 6-
Side note: Your
fetcher
function is falling prey to a footgun in thefetch
API I describe here. Before callingjson
, you probably want to check whether the HTTP request was successful (fetch
doesn't reject on HTTP errors, only network errors). So:const fetcher = (url: string) => fetch(url).then((r) => { if (!r.ok) { throw new Error(`HTTP error ${r.status}`); } return r.json(); });
– T.J. Crowder Commented Sep 27, 2021 at 11:18 -
1
FWIW,
useSWR
doesn't immediately seem to make sense with something likehttps://meme-api.herokuapp./gimme/5
that always provides a new set of responses when you send it a request. – T.J. Crowder Commented Sep 27, 2021 at 11:23 - You can but only when your ponent mounts, because you need to wait for the api response in useSWR(), add the data as dependency so whenever it changes, it'll rerender. – kmp Commented Sep 27, 2021 at 11:27
- @T.J.Crowder what if I want to contact the data every new request – Jatin Hemnani Commented Sep 27, 2021 at 13:52
- @JatinHemnani - What does "contact the data every new request" mean? – T.J. Crowder Commented Sep 27, 2021 at 13:55
2 Answers
Reset to default 5The fastest solution to transfer data from one variable to another is to use an useEffect
hook. When data
changes, update memes
.
useEffect(() => { setMemes(data); }, [data])
Infinite scroling
A better solution would be to use SWR provided solutions for infinite scrolling. You have different options documented here.
Plain fetch
In this case, you can also consider using directly the fetch function and appending data to the memes list directly:
const [ memes, setMemes ] = useState([]);
async function fetchAnotherPage() {
const data = (await fetch('https://meme-api.herokuapp./gimme/5')).json();
setMemes(value => [...value, ...data.memes]);
}
useEffect(() => fetchAnotherPage(), []);
Since https://meme-api.herokuapp./gimme/5
always returns new data for each call, useSWR
isn't a good fit for this and, moreover, the fact it retrieves from cache and gives that to your code and then revalidates and (possibly) calls your code to update, without telling you whether it's the first result or an update, makes it very hard to do what you're describing.
Instead, I'd just use fetch
directly and not try to do the SWR thing; see ments:
// Start with no memes
const [memes,setMemes] = useState([]);
// Use a ref to track an `AbortController` so we can:
// A) avoid overlapping fetches, and
// B) abort the current `fetch` operation (if any) on unmount
const fetchControllerRef = useRef(null);
// A function to fetch memes
const fetchMoreMemes = () => {
if (!fetchControllerRef.current) {
fetchControllerRef.current = new AbortController();
fetch("https://meme-api.herokuapp./gimme/5", {signal: fetchControllerRef.current.signal})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
.then(newMemes => {
setMemes(memes => memes.concat(newMemes.memes));
})
.catch(error => {
// ...handle/report error...
})
.finally(() => {
fetchControllerRef.current = null;
});
}
};
// Fetch the first batch of memes
useEffect(() => {
fetchMoreMemes();
return () => {
// Cancel the current `fetch` (if any) when the ponent is unmounted
fetchControllerRef.current?.abort();
};
}, []);
When you want to fetch more memes, call fetchMoreMemes
.
Live Example:
const {useState, useEffect, useRef} = React;
const Example = () => {
// Start with no memes
const [memes,setMemes] = useState([]);
// Use a ref to track an `AbortController` so we can:
// A) avoid overlapping fetches, and
// B) abort the current `fetch` operation (if any) on unmount
const fetchControllerRef = useRef(null);
// A function to fetch memes
const fetchMoreMemes = () => {
if (!fetchControllerRef.current) {
fetchControllerRef.current = new AbortController();
fetch("https://meme-api.herokuapp./gimme/5", {signal: fetchControllerRef.current.signal})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
.then(newMemes => {
// I'm filtering out NSFW ones here on SO
setMemes(memes => memes.concat(newMemes.memes.filter(({nsfw}) => !nsfw)));
})
.catch(error => {
// ...handle/report error...
})
.finally(() => {
fetchControllerRef.current = null;
});
}
};
// Fetch the first batch of memes
useEffect(() => {
fetchMoreMemes();
return () => {
// Cancel the current `fetch` (if any) when the ponent is unmounted
fetchControllerRef.current && fetchControllerRef.current.abort();
};
}, []);
const message = memes.length === 1 ? "1 meme:" : `${memes.length} memes:`;
return <div>
<div>{message} <input type="button" value="More" onClick={fetchMoreMemes}/></div>
<ul>
{/* `index` as key is ONLY valid because our array only grows */}
{memes.map(({postLink}, index) => <li key={index}>{postLink}</li>)}
</ul>
</div>
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare./ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>