How to conditionally fetch within useEffect - javascript

I was able to fetch a new quote every time I re-render, but now I want to add a functionality where I have the option to replay the same quote and not fetch a new one if I press the replay quote button, however I still want to call the useEffect. The solution to this sounds super straight forward, however, I do not know how to approach it with useEffect in play.
Also, when using react is it better to use ref hooks, instead of document.getElementByID ?
Code SandBox Link
import "./styles.css";
import { useEffect, useState } from "react";
async function newQuote() {
const response = await fetch("https://api.quotable.io/random");
const data = await response.json();
return data.content;
}
async function genQuote() {
document.getElementById("quoteDisplay").innerText = await newQuote();
}
export default function App() {
const [quote, fecthNewQuote] = useState(0);
useEffect(() => {
genQuote();
document.getElementById(
"newQuoteCounter"
).innerText = `New Quote Counter: ${quote}`;
document.getElementById("quoteReplayedCounter").innertext = ``;
}, [quote]);
console.log();
return (
<div className="App">
<div id="quoteDisplay"></div>
<div id="newQuoteCounter">New Quote Counter: 0</div>
<div id="quoteReplayedCounter"> Quote Replayed Counter: 0 </div>
<button id="newQuoteButton" onClick={() => fecthNewQuote((c) => c + 1)}>
Fetch New Quote
</button>
<button id="keepSameQuote">Keep Same Quote</button>
</div>
);
}

You can refactor like this with async function inside useEffect and you do not need to use these kind of document.getElementById("quoteDisplay").innerText js solutions. You can do it with react states.
Here is the working example https://codesandbox.io/embed/distracted-shamir-sfinbd?fontsize=14&hidenavigation=1&theme=dark
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [quote, fecthNewQuote] = useState(0);
const [display, setDisplay] = useState("");
useEffect(() => {
async function newQuote() {
const response = await fetch("https://api.quotable.io/random");
const data = await response.json();
setDisplay(data.content);
}
newQuote();
}, [quote]);
console.log();
return (
<div className="App">
<div id="quoteDisplay">{display}</div>
<div id="newQuoteCounter">New Quote Counter: {quote}</div>
<div id="quoteReplayedCounter"> Quote Replayed Counter: 0 </div>
<button id="newQuoteButton" onClick={() => fecthNewQuote(quote + 1)}>
Fetch New Quote
</button>
<button id="keepSameQuote">Keep Same Quote</button>
</div>
);
}

Related

Error on fetch data, function instead of value

Hi guys I am trying to fetch data using get, and I want the data to be displayed after I click on the button, as a normal crud
I am new in programming if there is someone that can help me. I APPRECIATE THANKS
everything in my backend is ok, I try in postman and console.log is everything good. My problem is only in this part thanks
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
useEffect(()=> {
const todosUsers = async () => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}
todosUsers()
},[])
return (
<>
<button onClick=
{users.map((users) => (
<h1>{users.username}</h1>
))}></button>
</>
)
}
export default Usuarios;
one solution would be to keep a seperate variable to see if button is clicked as S.Singh mentioned
const [users, setUsers] = useState([]);
const [clicked, setClicked] = useState(false);
and in your component you can set the clicked variable to true on click
return (
<>
<button onClick={() => setClicked(true)}></button> //setting clicked true onclick
{clicked && users.map((users) => ( //only renders when click is true
<h1>{users.username}</h1>
))}
</>
)
if you want to hide and show alternatively on click just change the line to onClick={() => setClicked(!clicked)}
codesandbox demo
Move
{users.map((users) => (
<h1>{users.username}</h1>
))
}
from onClick definition and place it in an a markup where you want it to render. Define onClick hendler, which will be setting data from an API to a state
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchOnClick = async () => {
await axios.get("/users");
console.log(res)
setUsers(res.data);
}
return (
<>
<button onClick={fetchOnClick}>
Fetch
</button>
<div>
{users.map((users) => (
<h1>{users.username}</h1>
))}
</div>
</>
)
}
OR
If you want to fetch the data inside useEffect hook, like you did in your example
import React, { useState, useEffect } from "react";
function Usuarios() {
const [users, setUsers] = useState([]);
const [isContainerShowed, setIsContainerShowed] = useState(false)
useEffect(() => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}, [])
const displayContainer = () => setIsContainerShowed(true);
return (
<>
<button onClick={displayContainer}>
Fetch
</button>
<div style={{display: isContainerShowed ? "block" : "none"}}>
{users.map(users => <h1>{users.username}</h1>)}
</div>
</>
)
}
use Effect hook runs every time the component is rendered but as I understand, you want to display users when the button is clicked, so you can fetch users once the button is clicked and display them using the code below
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchUsers= async () => {
const res= await axios.get("/users");
setUsers(res.data);
}
return (
<>
<button onClick={() => fetchUsers()}></button>
{
users.length && users.map(user => <h2>{user.username}</h2>)
}
</>
)
}

Load More with paginated api

I'm making a Blog Web Page and the API I'm using to build it, it's already paginated so it returns 10 objects in every request I make.
But the client wants the page to have a "load more" button, in each time the user click on it, it will keep the already loaded data and load more 10 objects.
So far, I've made the button call more 10 new objects, everytime I clicked on it but I also need to keep the already loaded data.
This is my file so far:
MainPage.js
import React, { useState, useEffect} from 'react';
const MainPage = () => {
const [blogs, setBlogs] = useState('');
const [count, setCount] = useState(1);
useEffect(() => {
fetch("https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page="+count)
.then((response) => response.json())
.then((json) => {
console.log(json)
setBlogs(json)
})
}, [count])
const clickHandler = () => {
console.log(count);
return setCount( count+1)
}
return (
<div>
<p>All the recent posts</p>
{ blogs && blogs.map((blog) => {
return (
<div key={blog.id}>
<img width="100px" src={blog._embedded["wp:featuredmedia"][0].source_url}/>
<p>{blog.title["rendered"]}</p>
</div>
)
})
}
<button onClick={clickHandler}>LoadMore</button>
</div>
)
}
export default MainPage;
The idea is pretty simple. Just concatenate arrays using the Spread syntax as follows.
var first =[1, 2, 3];
var second = [2, 3, 4, 5];
var third = [...first, ...second];
So, do this thing when you're clicking the load more button.
Here I've come up with handling the whole thing:
Firstly, I will call a function inside the useEffect hook to load some blog posts initially. Secondly I've declared an extra state to show Loading and Load More text on the button.
Here is the full code snippet:
import React, { useState, useEffect } from "react";
const MainPage = () => {
const [loading, setLoading] = useState(false);
const [blogs, setBlogs] = useState([]);
const [count, setCount] = useState(1);
useEffect(() => {
const getBlogList = () => {
setLoading(true);
fetch(
"https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page=" +
count
)
.then((response) => response.json())
.then((json) => {
setBlogs([...blogs, ...json]);
setLoading(false);
});
};
getBlogList();
}, [count]);
return (
<div>
<p>All the recent posts</p>
{blogs &&
blogs.map((blog) => {
return (
<div key={blog.id}>
<img
width="100px"
src={blog._embedded["wp:featuredmedia"][0].source_url}
/>
<p>{blog.title["rendered"]}</p>
</div>
);
})}
{
<button onClick={() => setCount(count + 1)}>
{loading ? "Loading..." : "Load More"}
</button>
}
</div>
);
};
export default MainPage;
According to React documentation:
If the new state is computed using the previous state, you can pass a function to setState.
So you could append newly loaded blog posts to the existing ones in useEffect like this:
setBlogs((prevBlogs) => [...prevBlogs, ...json])
I would also set the initial state to an empty array rather than an empty string for consistency:
const [blogs, setBlogs] = useState([]);

How can I return text with useState?

I am waiting for the data (when the user submit) i fetch the data then return the Temperature with useState() but i wanted to return a header with it like Temperature:12°C.
Something else is that i wanna round the temperature to 2 decimal places but i don't know how to do so
here is my code:
import axios from 'axios';
import React from 'react'
import {useState, useEffect} from 'react'
import './style.css'
import rain from './use1.png'
function App() {
const [name,setName] = useState('Cairo')
const [res, setRes] = useState(null)
const [pic, setPic] = useState(null)
const [temp, setTemp] = useState('')
const getApi = e => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${name}&appid=my_key`)
.then(response=> {
console.log(response.data);
setRes(response.data.name)
setTemp(response.data.main.feels_like-273.15+'°C');
Math.round(temp)
setPic(`https://openweathermap.org/img/w/${response.data.weather[0].icon}.png`)
})}
const handleChange = e => {
setName(e.target.value);
};
return (
<div>
<img className="wallpaper" src={rain}></img>
<div className="content">
<input placeholder="Search.." className="input" onChange={handleChange}></input>
</i>
</div>
<div className="content2">
<h1 className="name">{res}</h1>
<img src={pic}></img>
<h1 className="temp">{temp}</h1>
</div>
</div>
);
}
export default App;
Add a useEffect hook so your component re-renders after your temp state changes.
useEffect(() => {}, [temp]);
In order to round to two decimal places...well, usually I don't like telling people this but that's an extremely easy thing to find out from just using Google or another search engine.
JavaScript math, round to two decimal places
You could do it like:
`Temperature ${(response.data.main.feels_like-273.15).toFixed(2)} °C`
You can do like this
const [temp, setTemp] = useState('')
setTemp(response.data.main.feels_like-273.15+'°C');
return (
<div>
<h1 className="temp"> Temperature {temp?.toFix(2)}</h1>
</div>
)
Could you please provide the full component's code, or at least where is response coming from? Anyway, If the response is being passed from a parent component, then I see no reason to use useState at all.
But if you are fetching the data in the same component, then you need to fetch the data asynchronously:
function component(){
const [temp, setTemp] = useState('Loading temperature...')
useEffect(() => {
fetchTemp().then((response) => {
setTemp(response.data.main.feels_like-273.15+'°C');
})
}, [])
return (
<div>
<h1 className="temp">{temp}</h1>
</div>
)
}
About rounding the number, you can use toFixed(decimals) method.
Example:
let number = 900.1234
console.log(number.toFixed(2))

react await async calling too soon giving undefined then the right result

I'm using async and await, however the result it calling two sets of times, the first set returns the empty default from the useEffect, and then 2nd time returns the correct data from the call.
This is leading to the data not rendering in the component.
import { Grid } from '#material-ui/core';
import React, { useEffect, useState } from 'react';
import { useEasybase } from 'easybase-react';
export default function Data() {
const [easybaseData, setEasybaseData] = useState([]);
const { db } = useEasybase();
const mounted = async() => {
const ebData = await db("cars").return().limit(1).all();
setEasybaseData(ebData);
};
useEffect(() => {
mounted();
}, []);
console.log('easybaseData');
const ez = easybaseData[0];
const i = ez?.image;
const n = ez?.name;
const p = ez?.price;
console.log(i);
return (
<Grid item xs={6} id={"outputs-grid"}>
<div className="image-result-wrapper">
<img
src={`http://localhost:3000/assets/images/${i}`}
className="image-result"
/>
</div>
<div className="details-result-wrapper">
<p className="details-result">{n}</p>
</div>
<div className="price-result-wrapper">
<p className="price-result">{`Evaluation: $${p}`}</p>
</div>
</Grid>
)
}
and the console.log:
What am I missing about this? All 3 data for image, name and price are returning undefined in inspector.

How to properly use the UseCallback Hook

The following code gives me a warning that
React Hook useEffect has a missing dependency: 'getData'.
Either include it or remove the dependency array
I know in the new rules of react I must wrap it in a useCallBack but I am unsure how to do so. Can someone please provide me with how to use, useCallBack properly. Thank you in advance!!
import React, { useState, useEffect, useCallback } from "react"
import axios from "axios"
const Home = () => {
const [data, setData] = useState(null)
const [query, setQuery] = useState("reacthooks")
useEffect(() => {
getData()
}, [query])
const getData = async () => {
const response = await axios.get(
`http://hn.algolia.com/api/v1/search?query=${query}`
)
setData(response.data)
}
const handleChange = event => {
event.preventDefault()
setQuery(event.target.value)
}
return (
<div>
<input type='text' onChange={handleChange} />
{data &&
data.hits.map(item => (
<div key={item.objectID}>
{item.url && (
<>
<a href={item.url}>{item.title}</a>
<div>{item.author}</div>
</>
)}
</div>
))}
</div>
)
}
export default Home
I would simply add the getData function into useEffect hook in your case instead.
Try the following:
useEffect(() => {
const getData = async () => {
const response = await axios.get(
`http://hn.algolia.com/api/v1/search?query=${query}`
)
setData(response.data)
}
getData()
}, [query])
I hope this helps!

Categories

Resources