how to update fetch with useLoaderData() using react-router? - javascript

I would like to update the data of useLoaderData() after submit a form.
In my case :
export const countriesLoader = async () => {
const res = await fetch("https://restcountries.com/v3.1/all/");
if (!res.ok) {
throw Error("Could not reach the data !");
}
return res.json();
};
<Route index element={<CoutriesList />} loader={countriesLoader} />
on CountriesList element :
const countries = useLoaderData();
new data that i would like to update in countries useLoaderData() :
const findCountry = async () => {
const res = await fetch(
`https://restcountries.com/v3.1/name/${searchText}`
);
const data = res.json();
return data;
};
So when I submit, I would like that the data of countriesLoader to become the data from findCountry.
any solution ? thanks

I think you may be looking to use findCountry as a route action when a form is submitted. Loaders run when a route is loaded for the first time. An action can be dispatched later.
Basic Example:
const router = createBrowserRouter([
{
index: true,
element: <CountriesList />,
loader: countriesLoader,
action: findCountry
}
]);
<RouterProvider router={router} />
const countriesLoader = async () => {
const res = await fetch("https://restcountries.com/v3.1/all/");
if (!res.ok) {
throw Error("Could not reach the data !");
}
return res.json();
};
const findCountry = async ({ request }) => {
const formData = await request.formData();
const res = await fetch(
`https://restcountries.com/v3.1/name/${formData.get("country")}`
);
if (!res.ok) {
throw Error("Could not reach the data !");
}
return res.json();
};
import {
Form,
useActionData,
useLoaderData
} from "react-router-dom";
const CountriesList = () => {
const searchResult = useActionData();
const countriesData = useLoaderData();
return (
<>
...
<Form method="post" replace>
<label>
Search <input required type="text" name="country" />
</label>
<button type="submit">Create</button>
</Form>
...
</>
);
};

Related

useQuery Hook result set to loading without being called

I'm trying to build a search bar in my new project, and I seem to be doing some things(maybe a lot) wrong.
I set the book state to null and the useQuery hook seems to be using it to search for books.
I don't want it to search for anything unless I click the button.
These are my codes:
fetchBooks.jsx
async function fetchBooks({ queryKey }) {
const book = queryKey[1];
const response = await fetch(
`https://www.googleapis.com/books/v1/volumes?q=${book}`
);
if (!response.ok) {
throw new Error(`Search not found for ${book}`);
}
return response.json();
}
export default fetchBooks;
Here is the main component.
import { useState } from "react";
import { useQuery } from "#tanstack/react-query";
import fetchBooks from "../helper/fetchBooks";
const Home = () => {
const [book, setBook] = useState(null);
const results = useQuery(["search", book], fetchBooks);
const handleSubmit = (e) => {
e.preventDefault();
setBook(e.target.elements.book.value);
};
return (
<>
<form onSubmit={handleSubmit}>
<label htmlFor="book">
Book Name:
<input type="text" name="book" />
</label>
<button type="submit">Submit</button>
</form>
{results.isLoading ? (
<div>Loading...</div>
) : results.isError ? (
<div>{results.error.message}</div>
) : (
<div>
<h2>Results</h2>
<ul>
{results.data.items.map((item) => {
return (
<div key={item.id}>
<h3>{item.volumeInfo.title}</h3>
<p>{item.volumeInfo.authors}</p>
</div>
);
})}
</ul>
</div>
)}
</>
);
};
export default Home;
You can return a default value in the fetch function if the book is null. Then, the query won't actually request the API.
async function fetchBooks({ queryKey }) {
const book = queryKey[1];
if(!book) return { items: [] }
const response = await fetch(
`https://www.googleapis.com/books/v1/volumes?q=${book}`
);
if (!response.ok) {
throw new Error(`Search not found for ${book}`);
}
return response.json();
}
export default fetchBooks;
Instead of restricting the useQuery to call the fecthBooks functions, you can modify the fetchBooks functions to return an empty array if book is set to null. The fetchBooks can be modified as below:-
async function fetchBooks({ queryKey }) {
const book = queryKey[1];
if(!book){
return {
isLoading : false,
error : null,
data : null
}
}
const response = await fetch(
`https://www.googleapis.com/books/v1/volumes?q=${book}`
);
if (!response.ok) {
throw new Error(`Search not found for ${book}`);
}
return response.json();
}
export default fetchBooks;
The idiomatic way would be to set the useQuery itself to disabled (via the enabled property) when your params aren't ready:
const results = useQuery({
queryKey: ["search", book],
queryFn: fetchBooks
enabled: !!books
})
this will prevent the query function from executing when you have no books.

How do I pass two (or more) API props to a NextJs Page?

I'm trying to render a page with two props from different API fetches.
The adress bar looks like this: http://localhost:3000/startpage?id=1
And the code looks like this, with the first API fetch:
import { useRouter } from "next/router";
export const getServerSideProps = async (context) => {
const { id } = context.query;
const res = await fetch(`${process.env.BACKEND_URL}/User/${id}`);
const data = await res.json();
// console.log(data);
return {
props: { user: data },
};
};
Second API fetch looks like this
export const getServerSideProps2 = async (context) => {
const { id } = context.query;
const res = await fetch(`${process.env.BACKEND_URL}/User/${id}/favorites`);
const data = await res.json();
//console.log(data);
return {
props: { favorites: data },
};
};
And the page that I am trying to render then looks like this:
function StartPage( {user, favorites} ){
return (
<div>
<div className={styles.formGroup}>
<h1>Welcome {user.name}</h1>
</div>
<div>
<h1>These are your favorite movies:</h1>
{favorites.map(favorite => (
<div key={favorite.id}>
<h5>favorite.name</h5>
</div>
))}
</div>
</div>
)
}
I'm guessing that there's a way to put both API fetches in the same function. But I don't know how to. If anyone has any suggetions on how to do that I'd be happy to listen.
Thank you in advance.
You can make the calls in the same method and pass both data:
export const getServerSideProps = async (context) => {
const { id } = context.query;
const res = await fetch(`${process.env.BACKEND_URL}/User/${id}`);
const data = await res.json();
const resFav = await fetch(`${process.env.BACKEND_URL}/User/${id}/favorites`);
const dataFav = await resFav.json();
return {
props: { user: data, favorites: dataFav },
};
};
No need to declare getServerSideProps2

var existData = props.quotes.find((obj) => obj.id === params.id); find is undefined

var existData = props.quotes.find((obj) => obj.id === params.id); find is undefined
Fetching data from firebase, displaying data in quote list, and single quote page,
using props,
Using react-dom
const AllQuotes = (props) => {
const [movies, setMovies] = useState([]);
async function movieData() {
try {
const response = await fetch(
"https://react-post-api-default-rtdb.firebaseio.com/NewQuotes.json"
);
const data = await response.json();
const loadedData = [];
for (const key in data) {
loadedData.push({
id: key,
// id : Math.random(0,1).toString(),
author: data[key].author,
quote: data[key].quote,
});
}
setMovies(loadedData);
} catch (error) {}
}
useEffect(() => {
movieData();
}, []);
return (
<React.Fragment>
<div>
<h1>All Quotes</h1>
{/* <button onClick={movieData}>fetch data</button> */}
<QuoteList quotes={movies} />
</div>
<QuotesDetail quotes={movies} />
</React.Fragment>
);
};
Single quote display page
const QuotesDetail = (props) => {
const params = useParams();
const match = useRouteMatch();
// console.log(match)
// console.log(params.id)
var existData = props.quotes.find((obj) => obj.id === params.id);```
Getting error find is undefined.

Next JS [id] error Error serializing `.data` returned from `getServerSideProps` in "/services/[id]"

I make Next JS project and I am new to coding in this program and have a "Service" folder. In this folder there are index.js and [id].js (details page). All data come from Next API. Index.js works, there is no problem. But when I click the details element the error is seen. I don't know what is my mistake
Error: Error serializing `.data` returned from `getServerSideProps` in "/services/[id]". Reason: `object` ("[object Promise]") cannot be serialized as JSON. Please only return JSON serializable data types.
index.js
<section className="services-main">
<div className="services-main-context container">
<MainPageServices posts={posts} />
</div>
</section>
....
export async function getStaticProps() {
const res = await fetch("http://localhost:3000/api/servicesApi/");
const posts = await res.json();
return {
props: {
posts,
},
};
}
MainPageServices component
<div className="main-page-services-cards">
{posts.map((card, key) => (
<div key={card.id} className="service-card">
<Link href={`/services/${card.id}`}>
<a>
<div className="card-img">
<Image src={card.img} alt="Services" />
</div>
</a>
</Link>
</div>
))}
</div>
Not working component (Details)
const ServiceDetails = ({ data }) => {
console.log(data);
return (
<h1>{data.header}</h1>)
);
};
export const getServerSideProps = async (context) => {
const res = await fetch(`http://localhost:3000/api/servicesApi/${context.params.id}`);
const data = res.json();
return {
props: {
data,
},
};
};
My details page API
import { servicesData } from "../../../data";
export default function handler(req, res) {
const { id } = req.query;
const service = servicesData.find((service) => service.id === parseInt(id));
res.status(200).json(service);
}
I think you need to await res.json() because your error says you are passing a promise into your props.
const ServiceDetails = ({ data }) => {
console.log(data);
return (
<h1>{data.header}</h1>)
);
};
export const getServerSideProps = async (context) => {
const res = await fetch(`http://localhost:3000/api/servicesApi/${context.params.id}`);
const data = await res.json();
return {
props: {
data,
},
};
};

Refreshing UI after deleting item in React

I'm new to React and I have the issue that my UI ain't refreshing once I send a delete fetch in my React app. I tried to use a useEffect on my deleteTaskHandler but it broke my code. Any ideas how to accomplish this refresh?
This is my Task.js file, which is receiving props from a TaskList.js file, and TaskList.js file sends a component to App.js:
import React, { useState } from 'react';
import classes from './Task.module.css';
const Task = (props) => {
const [isCompleted, setIsCompleted] = useState(props.isCompleted);
const changeCompleteStatus = () => {
setIsCompleted(!isCompleted);
}
const deleteTaskHandler = async () => {
try {
const key = props.id
const response = await fetch('http://localhost:5050/delete-task/' + key, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
};
const updateTaskHandler = async () => {
const id = props.id
const taskData = {
id: id,
content: props.content,
isCompleted: !props.isCompleted,
dateCreation: props.dateCreation,
};
try {
const response = await fetch('http://localhost:5050/edit-task/' + id, {
method: 'PATCH',
body: JSON.stringify(taskData),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
};
let task;
if (props.isAllView) {
task = <div >
<input type="checkbox" onClick={updateTaskHandler} onChange={changeCompleteStatus} checked={isCompleted} />
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
<button onClick={deleteTaskHandler}>X</button>
</div>
} else {
task = <div >
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
</div>
}
return (
<li>{task}</li>
);
};
export default Task;
This is TaskList.js:
import React, { useState } from 'react';
import classes from './TaskList.module.css';
import Task from './Task';
const TaskList = (props) => {
const [taskView, setTaskView] = useState('all');
const getCompleteURL = () => {
setTaskView('complete')
props.onChangeTaskURL('http://localhost:5050/completed');
};
const getAllURL = () => {
setTaskView('all')
props.onChangeTaskURL('http://localhost:5050/');
};
const getPendingURL = () => {
setTaskView('pending')
props.onChangeTaskURL('http://localhost:5050/pending');
};
let taskList;
if (taskView != 'all') {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={false}
/>
));
} else {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
id={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={true}
/>
));
}
return (
<div>
<ul >
{taskList}
</ul>
<button onClick={getCompleteURL}>Completed</button>
<button onClick={getAllURL}>All</button>
<button onClick={getPendingURL}>Pending</button>
</div>
);
};
export default TaskList;
This is App.js:
import React, { useState, useEffect, useCallback } from 'react';
import './App.css';
import TaskList from './components/Tasks/TaskList';
import NewTask from './components/NewTask/NewTask';
function App() {
const [tasks, setTasks] = useState([]);
const [taskURL, setTaskURL] = useState('http://localhost:5050/');
const fetchTasksHandler = useCallback(async (url) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const loadedTasks = [];
for (const key in data) {
loadedTasks.push({
id: data[key]._id,
content: data[key].content,
isCompleted: data[key].isCompleted,
dateCreation: data[key].dateCreation
});
}
console.log(loadedTasks)
setTasks(loadedTasks);
} catch (error) {
// throw new Error('Something went wrong!');
console.log(error)
}
}, []);
useEffect(() => {
fetchTasksHandler(taskURL);
}, [fetchTasksHandler, taskURL]);
const changeTaskURL = url => {
console.log(url)
setTaskURL(url);
};
return (
<React.Fragment>
<TaskList taskData={tasks} onChangeTaskURL={changeTaskURL}></TaskList>
<NewTask></NewTask>
</React.Fragment>
);
}
export default App;
Extract deleteTaskHandler and updateTaskHandler in your App.js and pass them down to the TaskList => Task. In both methods, on successful operation update the tasks state array (for delete - filter out the deleted task, for update - swap the old task with the updated one). This way, the Task component will call the relevant handler which will update the parent tasks state, which in turn will spill down to the TaskList and Task and everything will get updated automatically.
Here is a sample. Consider it more as a pseudo code as you'll have to modify some of the parts to handle your case appropriately.
Your Task.js:
import React, { useState } from 'react';
import classes from './Task.module.css';
const Task = (props) => {
const {
updateTaskHandler,
deleteTaskHandler
} = props;
const [isCompleted, setIsCompleted] = useState(props.isCompleted);
const changeCompleteStatus = () => {
setIsCompleted(!isCompleted);
}
const updateHandler = () => {
const taskData = {
id: id,
content: props.content,
isCompleted: !props.isCompleted,
dateCreation: props.dateCreation,
};
updateTaskHandler(props.id, taskData);
};
const deleteHandler = () => {
deleteTaskHandler(props.id);
};
let task;
if (props.isAllView) {
task = <div >
<input type="checkbox" onClick={updateHandler} onChange={changeCompleteStatus} checked={isCompleted} />
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
<button onClick={deleteHandler}>X</button>
</div>
} else {
task = <div >
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
</div>
}
return (
<li>{task}</li>
);
};
export default Task;
TaskList.js:
import React, { useState } from 'react';
import classes from './TaskList.module.css';
import Task from './Task';
const TaskList = (props) => {
const [taskView, setTaskView] = useState('all');
const getCompleteURL = () => {
setTaskView('complete')
props.onChangeTaskURL('http://localhost:5050/completed');
};
const getAllURL = () => {
setTaskView('all')
props.onChangeTaskURL('http://localhost:5050/');
};
const getPendingURL = () => {
setTaskView('pending')
props.onChangeTaskURL('http://localhost:5050/pending');
};
let taskList;
if (taskView != 'all') {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={false}
updateTaskHandler={props.updateTaskHandler}
deleteTaskHandler={props.deleteTaskHandler}
/>
));
} else {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
id={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={true}
updateTaskHandler={props.updateTaskHandler}
deleteTaskHandler={props.deleteTaskHandler}
/>
));
}
return (
<div>
<ul >
{taskList}
</ul>
<button onClick={getCompleteURL}>Completed</button>
<button onClick={getAllURL}>All</button>
<button onClick={getPendingURL}>Pending</button>
</div>
);
};
export default TaskList;
And App.js:
import React, { useState, useEffect, useCallback } from 'react';
import './App.css';
import TaskList from './components/Tasks/TaskList';
import NewTask from './components/NewTask/NewTask';
function App() {
const [tasks, setTasks] = useState([]);
const [taskURL, setTaskURL] = useState('http://localhost:5050/');
const fetchTasksHandler = useCallback(async (url) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const loadedTasks = [];
for (const key in data) {
loadedTasks.push({
id: data[key]._id,
content: data[key].content,
isCompleted: data[key].isCompleted,
dateCreation: data[key].dateCreation
});
}
setTasks(loadedTasks);
}
catch (error) {
// throw new Error('Something went wrong!');
console.log(error)
}
}, []);
const deleteTaskHandler = async (taskID) => {
try {
const response = await fetch(`http://localhost:5050/delete-task/${taskID}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
setTasks(tasks => {
return tasks.filter(task => task.id !== taskID)
});
}
catch (error) {
console.log(error);
}
};
const updateTaskHandler = async (taskID, taskData) => {
const id = props.id
const taskData = {
id: id,
content: props.content,
isCompleted: !props.isCompleted,
dateCreation: props.dateCreation,
};
try {
const response = await fetch(`http://localhost:5050/edit-task/${taskID}`, {
method: 'PATCH',
body: JSON.stringify(taskData),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
setTasks(tasks => {
return tasks.map(task => {
if (task.id !== taskID) {
return task;
}
else {
return data; // The updated task
}
})
});
}
catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchTasksHandler(taskURL);
}, [fetchTasksHandler, taskURL]);
const changeTaskURL = url => {
console.log(url)
setTaskURL(url);
};
return (
<React.Fragment>
<TaskList
taskData={tasks}
onChangeTaskURL={changeTaskURL}
deleteTaskHandler={deleteTaskHandler}
updateTaskHandler={updateTaskHandler}
/>
<NewTask />
</React.Fragment>
);
}
export default App;
You have to pass a callback that removes a task and pass it as a prop to the Task component.
In your App.js:
function deleteTask(id) {
setTasks(loadedTasks.filter(x => x.id !== id);
}
// ...
<TaskList taskData={tasks} onDelete={deleteTask}></TaskList>
In your TaskList.js:
tasks.map(task => <Task id={task.id} key={task.id} onDelete={props.deleteTask}></Task>)
In the Task.js call props.deleteTask(props.id) whenever you need to.
Note that passing a prop through two components or more is called "prop drilling" and should be avoided (by keeping the state in TaskList.js for example).
you can use
window.location.reload();
to refresh the page or
this.setState({});
to refresh the component or
const [value,setValue] = useState();
const refresh = ()=>{
setValue({});
}
to refresh the component using hooks
i hope you found this answer helpful

Categories

Resources