React Suspense not working as expected when fetching data - javascript

I have a react component in which I am fetching data from the pokeapi. I am trying to render it with Suspense using browser's fetch but I am not able to figure out where I am going wrong.
function FirstPageIndex() {
const [pokemon, setPokemon] = useState({ abilities: [] });
const [loading, setLoading] = useState(false);
console.log("This has been rendered only once");
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const getDataFunction = async (e) => {
try {
e.preventDefault();
setLoading(true);
const r = await fetch("https://pokeapi.co/api/v2/pokemon/ditto");
const res = await r.json();
await sleep(4000);
console.log("This is the data");
console.log(res);
setPokemon(res);
setLoading(false);
} catch (e) {}
};
return (
<div>
This is coming from the home page
<button onClick={getDataFunction}>Get Data</button>
<Suspense fallback={<h2>Suspense in loading...</h2>}>
{pokemon.abilities.map((ability, i) => (
<p key={i}>
This is my ability {i + 1}: {ability.ability.name}
</p>
))}
</Suspense>
</div>
);
}
I have imported {Suspense} from "react" in this component.
The expected behaviour is when I click on get data, it waits for 3 seconds and then it renders the data, during which time it should show the Suspense's fallback which it is not showing.
EDIT: Updated code to incorporate promise based delay
Please have a look at it?

setTimeout is not a promise so await won't work here.
try doing this
function FirstPageIndex() {
const [pokemon, setPokemon] = useState({ abilities: [] });
const [loading, setLoading] = useState(false);
useEffect(() => {}, []);
console.log("This has been rendered only once");
const getDataFunction = async (e) => {
try {
e.preventDefault();
setLoading(true);
const r = await fetch("https://pokeapi.co/api/v2/pokemon/ditto");
const res = await r.json();
setTimeout(() => {
console.log("This is the data");
console.log(res);
setPokemon(res);
setLoading(false);
}, 3000);
} catch (e) {}
};
if (loading) return <p>This is loading state</p>;
return (
<div>
This is coming from the home page
<button onClick={getDataFunction}>Get Data</button>
<Suspense fallback={<h2>Suspense in loading...</h2>}>
{/* <PokeData resource={pokemon} ></PokeData> */}
{pokemon.abilities.map((ability, i) => (
<p key={i}>
This is my ability {i + 1}: {ability.ability.name}
</p>
))}
</Suspense>
</div>
);
}

Related

Reload updated Table once a column is removed

this is a component that retrieve data from API and generate a table. One button in a column for removing the row opens a Modal.
For avoiding in first component render to trigger api.delete request, an useRef set as false in second useEffect
Modal's Delete button return row info in deleteHandler function which successfully trigger api.delete request on confirmation, remove user in backend however table is not reloading.
Once row is removed expected result is triggering api.get request and display table now updated and without row removed.
In order to get that result I tried with const [reload, setReload] = useState(false); state which introduce another dependency to both userEffect
reload state effectively reload table data updated however it cause also that api.delete request trigger with const ida undefined. Below component script it can find useEffect with my tried.
Any hint or possible solution is appreciated
import React, { Fragment, useEffect, useState, useRef } from "react";
... other imports ...
export default function AllUsers() {
const api = useApi();
const [userData, setUserData] = useState();
const [showModal, setShowModal] = useState(false);
const navigate = useNavigate();
const [modalMessage, setModalMessage] = useState();
const [removeUserId, setRemoveUserId] = useState();
const [ida, setIda] = useState();
let effectRan = useRef(false);
const [reload, setReload] = useState(false);
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
} else {
setUserData(null);
}
})();
}, [api]);
useEffect(() => {
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
} else {
console.log(response.body.errors);
}
})();
}
return () => (effectRan.current = true);
}, [api, ida]);
const handleEditClick = (e, rowIndex) => {
// console.log("user/" + rowIndex.username);
navigate("/user/" + rowIndex.username, [navigate]);
};
const handleRemoveClick = (e, rowIndex) => {
// console.log([rowIndex]);
setShowModal(true);
setModalMessage({
title: `Remove ${rowIndex.username}`,
message: `User's email to remove ${rowIndex.email}`,
});
setRemoveUserId(rowIndex.id);
};
const closeHandler = () => {
setShowModal(false);
};
const deleteHandler = () => {
// console.log(removeUserId);
setIda(removeUserId);
setShowModal(false);
};
// console.log(ida, idb);
return (
<Fragment>
{showModal && (
<BtModal
show={showModal}
title={modalMessage.title}
message={modalMessage.message}
handleClose={closeHandler}
onConfirm={deleteHandler}
/>
)}
<Body>
<h1>User Table</h1>
{userData === undefined ? (
<Spinner animation="border" />
) : (
<>
{userData === null ? (
<p>Could not retrieve users.</p>
) : userData.length === 0 ? (
<p>There are not users in system</p>
) : (
<UserListTable
newData={userData}
handleEditClick={handleEditClick}
handleRemoveClick={handleRemoveClick}
/>
)}
</>
)}
</Body>
</Fragment>
);
}
useEffect updated with reload state:
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
effectRan.current = false;
} else {
setUserData(null);
}
})();
}, [api, reload]);
useEffect(() => {
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
setReload(!reload);
} else {
console.log(response.body.errors);
}
})();
}
return () => (effectRan.current = true);
}, [api, ida, reload]);
Here you modify your last edit
const deleteHandler = () => {
// console.log(removeUserId);
setIda(removeUserId);
setReload(!reload)
setShowModal(false);
};
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
} else {
setUserData(null);
}
})();
}, [api]);
useEffect(() => {
effectRan.current = reload;
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
setReload(!reload);
} else {
console.log(response.body.errors);
}
})();
}
return () => {
effectRan.current = false;
};
}, [api, ida, reload]);
It looks like you're trying to delete an item in the table and confirm it via a prompt. I can see that you've used a lot of useEffects, which might be making things a bit complicated.
Don't worry though, I have a solution that should be much simpler. Let me show you what I mean!
const Page = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState([]);
const [deleting, setDeleting] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const deleteHandler = async() => {
setDeleting(true);
await api.delete(selectedItem);
// Next you can either update the state itself
let tD = [...data];
const index = tD.findIndex((i) => i.id == selectedItem);
tD.splice(index, 1);
setSelectedItem(tD);
// or refetch data from the backend and update the state
const d = await api.getData();
setData(d);
setDeleting(false);
setSelectedItem(null);
};
useEffect(() => {
const fetchData = async() => {
setLoading(true);
const d = await api.getData();
setData(d);
setLoading(false);
}
fetchData();
}, [])
return loading ? <div>Loading...</div> : <>
{/*Other JSX elements*/}
{selectedItem && <div>
{/*prompt body*/}
<button onClick={() => {setSelectedItem(null)}}>Cancel</button>
<button disabled={deleting} onClick={() => {deleteHandler()}}>Delete</button>
{/*Button will be disabled when the DELETE request is sent*/}
</div>}
{data.map((row) => {
return <tr key={row.id}>
<td>...</td>
<td>...</td>
<td>
<button onClick={setSelectedItem(row.id)}>Delete</button>
</td>
</tr>
})}
</>
}

How can i turn this React class component into a functional component?

I'm working on implementing a braintree payment method in my react/mui app. I've found a way that works, but it's in a class component. How can I convert this info a proper functional component?
const BraintreeDropInPaymentMethod = () => {
class Store extends React.Component {
instance;
state = {
clientToken: '<BRAIN TREE KEY>'
};
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
async buy() {
const { nonce } = await this.instance.requestPaymentMethod();
await fetch(`server.test/purchase/${nonce}`);
}
render() {
if (!this.state.clientToken) {
return (
<div>
<h1>Loading...</h1>
</div>
);
} else {
return (
<div>
<DropIn
options={{ authorization: this.state.clientToken }}
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant='contained'
onClick={this.buy.bind(this)}
>
Create Account
</Button>
<Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
Cancel
</Button>
</div>
);
}
}
}
const [user, setUser] = useState({})
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
return (
<Store/>
)
}
this is my attempt, but I'm coming up short on how I should handle componentDidMount(). I know how to handle useState in some situations, except for this one. Also, how can I handle the 'instance' section in a functional format? thanks.
const BraintreeDropInPaymentMethod = () => {
const [token, setToken] = useState('<BRAIN TREE KEY>')
const [user, setUser] = useState({})
const contactServer = async () => {
const res = await fetch('server.test/client_token')
const clientToken = await res.json()
console.log(clientToken)
setToken(token)
}
const buy = async () => {
const { nonce } = await this.instance.requestPaymentMethod()
await fetch(`server.test/purchase/${nonce}`)
}
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
const createAccountOptions = () => {
if (!token) {
return (
<div>
<h1>Loading...</h1>
</div>
) else {
return (
<div>
<DropIn
options={ authorization: {setToken})
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant="contained'
onClick={buy}
>
Create Account
</Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
<Button>
Cancel
</Button>
</div>
)
}
}
}
return(
<>
<createAccountOptions/>
</>
)
}
The functional equivalent of componentDidMount() is the useEffect hook.
In this case you would change this:
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
Into something like this:
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
setState((old) => clientToken);
};
Using the useEffect hook with an empty array as a dependency makes the function in it only run once as the component mounts.

Searchbar cant filter JSON File Javascript

today i have a problem with my searchbar.
const [posts, setPosts] = useState(null)
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div>
<input type={"text"} placeholder="Search..." onChange={event => {
setSearchTerm(event.target.value)
}}/>
</div>
)
}
This is my Searchbar Component. In the Index file, did i gave a props with.
const [posts, setPosts] = useState([])
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div className={styles.posts}>
<h1>Market-place Valando</h1>
<SearchList title={posts.filter(post => {
if (post.title.toLowerCase().includes(searchTerm.trim().toLowerCase()) && searchTerm.trim() !== "") {
return post.title
}
}).map(titles => {
{
{titles.title}
}
}
)}/>
{
posts.map(post => {
return (
<div key={post.id} className={styles.key}>
<h1>{post.title}</h1>
<Image width={1000} height={1000} src={post.image}/>
<p>Price: {post.price}.-</p>
<p>Description: {post.description}</p>
<Link href={`/posts/${post.id}`} passHref>
<a>Read more</a>
</Link>
</div>
)
})
}
</div>
)
}
I have a db.json file that i connected with an API File. In this Api File i made all the fetch stuff. This shouldnt be the problem. I think the problem is, that the filter doesnt work properly, with the titels.
You are correct, JavaScript filter does not return specific property values, but it returns the top entries of the array, a.k.a posts. So return post.title or return true will yield the same result. However, the problem in your code appears to be that you are not returning anything from the map function. All you need to do is to change it to the following:
.map(post => post.title)

how do I dynamically render layout based on dropdown filter?

I want to create a simple layout where the user can choose a dynamic dropdown that retrieves data based on an API call using React.js and Django as backend, but however, I want to pre-render category from the database and let the user choose which Industry, and change the layout data accordingly
async function fetchFeed(domain) {
return api.get(`http://localhost:8002/api/v1/xxxx/list/?domain=${domain}`);
}
async function fetchDomain() {
return api.get('http://localhost:8002/api/v1/xxxx/domain/'); # return all domains
}
export default function Board () {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const [responseData, setResponseData] = useState([])
const [domains, setDomains] = useState([]);
// fetches data
const fetchData = (domain) => {
fetchFeed(domain)
.then((response)=>{
setResponseData(response.data.results)
})
.catch((error) => {
console.log(error)
})
}
const handleOnClick = async (data) => {
try {
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
useEffect(() => {
fetchData();
}, []);
return (
<div className="container content">
<select>options</select>
{responseData.map((data) => (
<div className="col" key={data.t_id}>
<div className="row">{data.tactic_name}</div>
{data.data.map((item) => (
<div className="row" key={item._id} onClick={() => handleOnClick(item)}><span>{item.title}</span></div>
))}
</div>
))}
{isModalOpen && <Modal onRequestClose={() => setModalIsOpen(false)} data={users}/> }
</div>
);
}

SyntaxError Unexpected token < in JSON at position 0 in react.js API navigation

rnmservice.js
export function getrnm({ url }) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.json())
.then(data => {
resolve(data);
});
});
}
export async function getAllrnm(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.json())
.then(data => {
resolve(data);
});
});
}
app.js
import React, { useState, useEffect } from "react";
import Navbar from "./components/Navbar";
import Card from "./components/Card/Card";
import { getrnm, getAllrnm } from "./services/rmservice";
function App() {
const [rnmData, setRnmData] = useState([]);
const [nextUrl, setNextUrl] = useState("");
const [prevUrl, setPrevUrl] = useState("");
const [loading, setLoading] = useState(true);
const initialURL = "https://rickandmortyapi.com/api/episode/";
useEffect(() => {
async function fetchData() {
let response = await getAllrnm(initialURL);
setNextUrl(response.next);
setPrevUrl(response.previous);
await loadRnm(response.results);
setLoading(false);
}
fetchData();
}, []);
const next = async () => {
setLoading(true);
let data = await getAllrnm(nextUrl);
await loadRnm(data.results);
setNextUrl(data.next);
setPrevUrl(data.previous);
setLoading(false);
};
const prev = async () => {
if (!prevUrl) return;
setLoading(true);
let data = await getAllrnm(prevUrl);
await loadRnm(data.results);
setNextUrl(data.next);
setPrevUrl(data.previous);
setLoading(false);
};
const loadRnm = async data => {
let _rnmData = await Promise.all(
data.map(async rnm => {
let rnmRecord = await getrnm(rnm);
return rnmRecord;
})
);
setRnmData(_rnmData);
};
return (
<>
<Navbar />
<div>
{loading ? (
<h1 style={{ textAlign: "center" }}>Loading...</h1>
) : (
<>
<div className="btn">
<button onClick={prev}>Prev</button>
<button onClick={next}>Next</button>
</div>
<div className="grid-container">
{rnmData.map((rnm, i) => {
return <Card key={i} ricmor={rnm} />;
})}
</div>
<div className="btn">
<button onClick={prev}>Prev</button>
<button onClick={next}>Next</button>
</div>
</>
)}
</div>
</>
);
}
export default App;
Here My sandbox link is https://codesandbox.io/s/musing-thunder-frsk6 . I'm trying to fetch data and to do navigation using the API of ricky and morty (https://rickandmortyapi.com/api/episode/) but when I click next button I'm getting error as "SyntaxError Unexpected token < in JSON at position 0"
You just replace your useEffect with this
useEffect(() => {
async function fetchData() {
let response = await getAllrnm(initialURL);
console.log(response.info);
setNextUrl(response.info.next);
setPrevUrl(response.info.previous);
setPages(response.info.pages);
await loadRnm(response.results);
setLoading(false);
}
fetchData();
}, []);
you are directly accessing next and previous. access response.info.next like this because all next/previous coming in info.

Categories