I'm building a website using React Hooks and I've got two different pages (Workshops.js and Shows.js) fetching data from the same API, just with different parameters (?type=0 and ?type=1).
Once the data is fetched I'm mapping the results (It would be nice to have a reusable component there..see the comments in the code below). When the user click either on a show or a workshop he will be redirected to the same page.
Now singularly the code works.
Is there a more elegant way to avoid repeating the same code? ...something like Services in Angular?
Thank you!
Here is Workshop.js.
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'
import api from '../../maps/Api'
const Workshops = () => {
const [ workshops, setWorkshop ] = useState([])
const [ isLoading, setIsLoading ] = useState(false)
const [ error, setError ] = useState(null)
const GET_URL = api.get.workshops /* http://someapi/workshops?type=0 */
useEffect(() => {
setIsLoading(true)
fetch(GET_URL, {headers: {
"Accept": "application/json",
"Access-Control-Allow-Origin": "*"
}})
.then(res => {
return (res.ok) ? res.json() : new Error("Mistake!")
})
.then(workshops => {
if(workshops.upcoming) {
setWorkshop(workshops.upcoming);
}
setIsLoading(false);
})
.catch(error => {
setError(error)
})
}, [GET_URL])
if ( error ){ return <p>{ error.message }</p> }
if ( isLoading ){
return <p>Loading workshops...</p>
}
return(
<main>
<div className='content'>
<div className='contentCol'>
<ul id='workshopBox'>
{
workshops.map( (workshop, i) => (
<li> // FROM HERE...
<div
className='workshop-active'>
<h2>{ workshop.title }</h2>
<p>{ workshop.description }</p>
<p>{ workshop.place }</p>
<p>{ (new Date(workshop.date).toLocaleDateString("it-IT", {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}))}</p>
<p>{ (new Date(workshop.date).toLocaleTimeString("it-IT", {
hour: '2-digit',
minute: '2-digit',
hour12: true
}))}</p>
<p> Full price { workshop.price_full + ', 00'} € </p>
<p> Early bird price { workshop.price_earlybirds + ', 00'} € </p>
<p>
<Link to={`/workshops/${ workshop.id}`}>
<button>Details</button>
</Link>
</p>
<br/>
</div>
</li> //..to HERE I WOULD LIKE TO USE A REUSABLE COMPONENT
))
}
</ul>
</div>
</div>
</main>
)
}
export default Workshops
and here's Shows.js
import React, { useState, useEffect } from 'react';
//import { Link } from 'react-router-dom'
import api from '../maps/Api'
const Spettacoli = () => {
const [ shows, setShows ] = useState([])
const [ isLoading, setIsLoading ] = useState(false)
const [ error, setError ] = useState(null)
const GET_URL = api.get.shows /* http://someapi/workshops?type=1 */
useEffect(() => {
setIsLoading(true)
fetch(GET_URL, {headers: {
"Accept": "application/json",
"Access-Control-Allow-Origin": "*"
}})
.then(res => {
return (res.ok) ? res.json() : new Error("Mistake!")
})
.then(shows => {
setShows(shows)
setIsLoading(false)
})
.catch(error => {
setError(error)
})
}, [GET_URL])
return(
<main>
<div className='content'>
<div className='contentCol'>
/* SAME INTERFACE AS WORKSHOP */
</div>
</div>
</main>
)
}
export default Shows
So you may create your custom hook:
function useMyDataFetch(GET_URL) {
const [ data, setData ] = useState([])
const [ isLoading, setIsLoading ] = useState(true)
const [ error, setError ] = useState(null)
useEffect(() => {
let hasBeenAborted = false; // added
setIsLoading(true)
fetch(GET_URL, {headers: {
"Accept": "application/json",
"Access-Control-Allow-Origin": "*"
}})
.then(res => {
return (res.ok) ? res.json() : new Error("Mistake!")
})
.then(data => {
if (hasBeenAborted) return; // added
if(data.upcoming) {
setData(data.upcoming);
}
setIsLoading(false);
})
.catch(error => {
if (hasBeenAborted) return; // added
setIsLoading(false); // added
setError(error)
});
return () => { hasBeenAborted = true; } // added
}, [GET_URL]);
return { data, error, isLoading };
}
and use that in your components.
Notice lines I've marked with // added.
hasBeenAborted allows us react in case GET_URL has been updated for any reason for the same component. Cleanup in useEffect is really important so we avoid race conditions.
Instead of hasBeenAborted flag we could use AbortController but with that we would still fall into catch branch and need additional if to distinguish if request has been cancelled or actually failed. So just matter of taste to me.
As for your components they will use hook like that:
const Workshops = () => {
const {isLoading, error, data: workshops} = useMyDataFetch(api.get.workshops);
if ( error ){ return <p>{ error.message }</p> }
if ( isLoading ){
return <p>Loading workshops...</p>
}
return(
// the same here
);
}
export default Workshops
Related
This is my code to show the get request data in frontend
import React, { useEffect, useState } from "react";
import axios from "axios";
const Users = () => {
const [users, setusers] = useState({ collection: [] });
const [Error, setError] = useState();
useEffect(() => {
axios
.get("http://127.0.0.1:5000/users/users-list")
.then((response) => {
console.log(response.data);
// console.log(response.status);
// console.log(response.statusText);
// console.log(response.headers);
// console.log(response.config);
setusers({ collection: response.data });
return response.data;
})
.catch((error) => {
console.log({ Error: error });
setError(error);
// return error;
});
}, []);
return (
<div>
{users.collection.length > 0 &&
users.collection.map((element, i) => {
return (
<div key={i}>
{element.Name}‑{element.Email}
‑{element.Message}
</div>
);
})}
{Error && <h2>{Error}</h2>}
</div>
);
};
export default Users;
As you can see in the following code I am trying to display my get data in the browser web page .
but its is not displaying in the browser but showing in console.log()
First of all dont make variable starts with capital letter as you have used Error (which refers to Error class in JavaScript) in useState.
You can show component with different state as follows:
const [isLoading, setIsLoading] = useState(false);
const [users, setUsers] = useState([]);
const [error, setError] = useState("");
useEffect(() => {
setIsLoading(true);
axios.get("http://127.0.0.1:5000/users/users-list")
.then((res => {
setUsers(res.data);
setIsLoading(false);
})
.catch(err => {
setError(err.response.data);
setIsLoading(false);
}
},[]);
if (isLoading) {
return <LoadingComponent />
}
if (error !== "") {
return <h1>{error}</h1>
}
if (users.length < 1) {
return <h1>There is no user.</h1>
}
return <div>
{users.collection.map((element, i) => {
return (
<div key={i}>
{element.Name}‑{element.Email}
‑{element.Message}
</div>
);
})}
</div>
You implementation ok it's work with [{"Name":"T","Email":"t#email.com","Message":"T user"}] API response format. Just check what is API response in your end, It should render the results.
I have notice catch block you have to set error message instead of Err object
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const Users = () => {
const [users, setusers] = useState({ collection: [] });
const [Error, setError] = useState('');
useEffect(() => {
axios
.get('https://63a0075424d74f9fe82c476c.mockapi.io/api/collection/Test')
.then((response) => {
console.log(response.data);
// console.log(response.status);
// console.log(response.statusText);
// console.log(response.headers);
// console.log(response.config);
setusers({ collection: response.data });
})
.catch((error) => {
console.log({ Error: error });
setError('Something went wrong');
// return error;
});
}, []);
return (
<div>
{users.collection.length > 0 &&
users.collection.map((element, i) => {
return (
<div key={i}>
{element.Name}‑{element.Email}
‑{element.Message}
</div>
);
})}
{Error && <h2>{Error}</h2>}
</div>
);
};
export default Users;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I tested your code (with a different API) and could not find any issues. As you can see in the codesandbox, the values appear on the screen:
https://codesandbox.io/s/wonderful-ganguly-5ecbid?file=/src/App.js
I noticed that you capitalised the object properties, Name, Email and Message. Perhaps this caused you the issue. You will need to check the console logged object to see whether the properties are capitalised or not. Usually, they will not be. So you would call them like this: element.name, element.email and element.message.
I guess your response data is maybe your problem. I don't know what is your response but it must be array.
I have replace the axios url with some other fake urls and it worked. but remember that the user.collection must be array. Therefor, you need to make sure that response.data is array. Otherwise, you need to set response.data as array in user.collection.
import React, { useEffect, useState } from "react";
import axios from "axios";
const Users = () => {
const [users, setusers] = useState({ collection: [] });
const [Error, setError] = useState();
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => {
console.log(response.data);
setusers({ collection: [response.data] });
return response.data;
})
.catch((error) => {
console.log({ Error: error });
setError(error);
// return error;
});
}, []);
return (
<div>
{users.collection.length > 0 &&
users.collection.map((element, i) => {
return <div key={i}>{element.title}</div>;
})}
{Error && <h2>{Error}</h2>}
</div>
);
};
export default Users;
I have problem with fetching data when language changed. I tried a lot of things that I found from Stack overflow, but unfortunately it just changing the direction and it didn't fetch the data based on language changed.
I fetching data with a custom hook and call it inside my functional component. let me share the code that I write.
Note: I'm using I18nextLng for translation.
App.js
import { RouterProvider } from "react-router-dom";
import Loading from './components/loading';
import routes from './routes/routes';
import { useEffect } from "react";
import i18n from "./utils/i18n";
function App() {
useEffect(() => {
let cleanup = true;
if (cleanup) {
i18n.on('languageChanged', (local) => {
let direction = i18n.dir(local);
document.body.dir = direction;
})
}
return () => {
cleanup = false;
};
}, []);
return (
<RouterProvider router={routes} fallbackElement={<Loading />} />
)
}
export default App;
LanguageSwitcher.js
import { useTranslation } from "react-i18next";
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
return (
<select
className="form-select-sm rounded-pill text-center"
aria-label="Default select example"
value={i18n.language}
onChange={(e) =>
i18n.changeLanguage( e.target.value)
}
>
<option value="en">English</option>
<option value="fa">دری</option>
</select>
);
}
export default LanguageSwitcher;
Internships.js
import Image from "react-bootstrap/Image"
import { useFetchWebsiteData } from "../../hooks/website/useFetchWebsiteData";
import Loading from '../../components/loading'
import { useEffect, useState } from "react";
const Internships = () => {
let lang = localStorage.getItem("i18nextLng")
const { data, isLoading } = useFetchWebsiteData("getInternship", lang);
console.log("language changed", language);
return !isLoading ? (
<div className="container-fluid news-wrapper">
<div className="container">
<div className="row py-5">
<div className="col-md-12">
<div className="col-md-8">
<h4 className="title mb-4">{data?.title}</h4>
<p className="sub-title">{data?.content}</p>
</div>
<div className="col-md-2 text-center">
<Image
src={require("../../images/internships.png")}
fluid={true}
/>
</div>
</div>
</div>
</div>
</div>
) : (
<Loading />
);
}
export default Internships;
useFetchWebsiteData.js (Custom hook for fetching data)
import { useState, useEffect } from "react";
import { axiosPublic } from "../../utils/axios";
export const useFetchWebsiteData = (url,lang) => {
const [data, setData] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// const lang = localStorage.getItem("i18nextLng");
console.log('lang inside hook', lang)
useEffect(() => {
// const controller = new AbortController()
const fetchData = async () => {
setIsLoading(true);
await axiosPublic
.get(url, {
headers: { lang: lang === "fa" ? "dr" : "en" },
// signal: controller.signal,
})
.then((response) => {
if (response.status === 200) {
if (lang === "en") {
setIsLoading(false);
response.data.data.en.map((d) => {
let title = d.title;
let content = d.content;
return setData({ title: title, content: content });
});
}
if (lang === "fa") {
setIsLoading(false);
console.log("fa intern", response.data.data.dr)
response.data.data.dr.map((d) => {
let title = d.title;
let content = d.content;
return setData({ title: title, content: content });
});
setIsLoading(false);
}
} else {
setIsError(true);
}
})
.catch((error) => {
setIsLoading(false);
setIsError(true);
console.error(error.message);
});
};
fetchData();
// return () => {
// controller.abort()
// };
}, [url, lang]);
return { data, isLoading, isError };
};
I really appreciate for your helping.
Hey folks really hope someone can help me here. I'm successfully updating my object in my mongo cluster, it updates but it does not render that update straight away to the browser. It will only update after a reload or when I run my update function again, it doesn't fetch that update straight away and I can't understand why. Does anyone have any suggestions?
I'm using context and reducer.
PlantDetails
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
console.log('updated')
setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
<input onChange={(e) => setNewWaterDate(e.target.value)}/>
<button onClick={updatePlant}>update</button>
<input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/>
<label for="toWater">watered</label>
{watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>}
</div>
</div>
);
};
export default PlantDetails;
Context which wraps my
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p )
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My plantController (update)
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200)
.json(plant)
}
Home component
import { useEffect } from "react";
import PlantDetails from "../components/PlantDetails";
import PlantForm from "../components/PlantForm";
import CalendarComponent from "../components/CalendarComponent";
import { usePlantsContext } from "../hooks/usePlantsContext";
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
Any help would be greatly appreciated.
My patch requests were going through smoothly but my state would not update until I reloaded my page. It was not returning the document after the update was applied.
https://mongoosejs.com/docs/tutorials/findoneandupdate.html#:~:text=%3B%20//%2059-,You,-should%20set%20the
I am fetching data in my react component
import React, { useState, useEffect } from 'react';
import { fetchBookData } from './bookData';
import "./App.css";
export default function Books ({ books }) {
const [payload, setPayload] = useState(null)
fetchBookData(books).then((payload) => setPayload(payload));
return (
<div className="App">
<h1>Hello</h1>
</div>
);
}
Here is the fetch function itself
const dev = process.env.NODE_ENV !== 'production';
const server = dev ? 'http://localhost:3001' : 'https://your_deployment.server.com';
// later definable for developement, test, production
export const fetchBookData = (books) => {
const options = {
method: `GET`,
headers: {
accept: 'application/json',
},
};
return fetch(`${server}/books`, options)
.then((response) => {
if(response.ok){
return response.json()
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data in book data: ', error)
})
}
But when I start the server fetch runs in a loop, component making endless get requests to the server. I tried to wrap it in a useEffect, but didn't work. Fetch should run once on load
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. More
example (codesandbox)
export default function App({books}) {
const [payload, setPayload] = useState(null);
useEffect(() => {
fetchBookData(books).then((payload) => setPayload(payload));
}, [books]);
if (!payload) {
return <h1>Loading...</h1>;
}
return (
<div className="App">
<h1>{payload.title}</h1>
</div>
);
}
I am using vercel to deploy but I cannot figure out how to set up environmental variables, so I want to try method using fetch("/data.json"). I also have custom hook for fetching data.
But this does not work and I don't even see data on my local.
data.json file is directly inside /public folder. Can someone help me?
useFetch.js
import { useState, useEffect } from "react";
export const useFetch = (url, method = "GET") => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
const [options, setOptions] = useState(null);
const postData = (postData) => {
setOptions({
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
};
useEffect(() => {
const controller = new AbortController();
const fetchData = async (fetchOptions) => {
setIsPending(true);
try {
const res = await fetch(url, {
...fetchOptions,
signal: controller.signal,
});
if (!res.ok) {
throw new Error(res.statusText);
}
const json = await res.json();
setIsPending(false);
setData(json);
setError(null);
} catch (err) {
if (err.name === "AbortError") {
console.log("the fetch was aborted");
} else {
setIsPending(false);
setError("Could not fetch the data");
}
}
};
if (method === "GET") {
fetchData();
}
if (method === "POST" && options) {
fetchData(options);
}
return () => {
controller.abort();
};
}, [url, options, method]);
return { data, isPending, error, postData };
};
TaskList.js
import { useFetch } from "../hooks/useFetch";
import { useFrequency } from "../hooks/useFrequency";
//images
import Dot from "../assets/icon-ellipsis.svg";
// styles
import "./TaskList.scss";
export default function TaskList() {
// const [url, setUrl] = useState("http://localhost:3000/stats");
const { data: stats, isPending, error } = useFetch("/data.json");
const { frequency } = useFrequency();
const urlDot = "#";
return (
<div className="main__inner">
{isPending && <div>Loading stats...</div>}
{error && <div>{error}</div>}
<ul className="main__task-list">
{stats &&
stats.map((stat) => (
<li
className={
stat.title === "Self Care"
? "main__task-item selfcare"
: `main__task-item ${stat.title.toLowerCase()}`
}
key={stat.id}
>
<div className="main__task-item-container">
<h3 className="main__task-title">{stat.title}</h3>
<a href={urlDot} className="main__task-dot">
<img src={Dot} alt="more details" />
</a>
<span className="main__task-current">
{/* {frequency === "daily"
? stat.timeframes.daily.current
: frequency === "weekly"
? stat.timeframes.weekly.current
: stat.timeframes.monthly.current}
hrs */}
{stat.timeframes[frequency].current}hrs
</span>
<span className="main__task-previous">
{frequency === "daily"
? "Yesterday"
: frequency === "weekly"
? "Last Week"
: "Last Month"}{" "}
-{" "}
{
/* {frequency === "daily"
? stat.timeframes.daily.previous
: frequency === "weekly"
? stat.timeframes.weekly.previous
: stat.timeframes.monthly.previous} */
stat.timeframes[frequency].previous
}
hrs
</span>
</div>
</li>
))}
</ul>
</div>
);
}
You can configure the Environment variable in Vercel after you have imported your site from GitHub or another repository.
You can set it on the Configure Project window by clicking the Environment Variable Tab. See the image below