so i'm trying to implement a custom react hook for fetch. It's working fine, but i can't seem to do it with the errors. if i try to display the error in a custom component it's says object is not a valid React child ... okey i know that, but how then it's working when there's no error in the componenet ? Here's the code:
Hook:
import { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal: signal })
.then(res => res.json())
.then(result => {
setData([result]);
})
.catch(err => {
setError(true);
setErrorMessage(err);
})
return () => {
setError(false);
controller.abort();
};
}, [url]);
return { data, error, errorMessage };
};
export default useFetch;
Component:
const WeatherNow = () => {
const { city } = useContext(CityContext);
import ErrorHandler from '../error-component/ErrorHandler';
const { data, error, errorMessage } = useFetch(`https://api.weatherapi.com/v1/forecast.json?key=${process.env.REACT_APP_API_KEY}&q=${city}&aqi=no`);
if (error) {
return <>
<ErrorHandler props={errorMessage} />
</>
};
return (
<>
{data && data.map(x => (
<div className="today-forecast" key={city}>
<h4>
{city}
</h4>
<div>
{x.current.condition.text}
</div>
<div>
<img src={x.current.condition.icon} alt='' />
</div>
<h3>
{x.current.feelslike_c} *C
</h3>
<h5 className='rain-wind'>
Rain: {x.current.precip_in} % / Wind: {x.current.wind_kph} km/h
</h5>
<div className='links'>
<Link to='/hourly'>Hourly</Link> <Link to='/daily'>Daily</Link>
</div>
</div>
))}
</>
);
};
The ErrorHandler:
import './ErrorHandler.css';
import error from './error.png';
const ErrorHandler = ({ props }) => {
return (
<div className="error-component">
<div>
<h4>
{props}
</h4>
</div>
<div>
<img src={error} />
</div>
</div>
);
};
export default ErrorHandler;
Because of the catch (err) is an unknown type, it might return anything and more likely an object with a message key.
Try to change the way you are setting the error message and make sure it’s a string:
setErrorMessage(typeof err?.message === "string" ? err.message : "Unknown Error");
Warning
Using process.env.REACT_APP_API_KEY in client side is not safe at all.
The problem is in the catch block , somehow the err is inpromise and i can't use it
Related
function App() {
const [data, setData] = useState([]);
useEffect(() => {
getPic();
}, []);
const getPic = useCallback(async () => {
try {
const pic = await axios({
url: "https://api.thecatapi.com/v1/images/search",
});
const picUrl = pic.data[0];
if (picUrl) {
setData((prevData) => [...prevData, picUrl]);
}
} catch (error) {
console.log(`error = ${error}`);
}
}, []);
return (
<div className="App">
<p>test</p>
<div>
{data.map((d) => (
<div key={d.id}>
<S.CatImg src={d.url} alt="" />
</div>
))}
</div>
</div>
);
}
export default App;
I am practicing implementing infinite scrolling.
I think the getPic function should be executed only once because nothing is added to UseEffect's dependencies array.
But I don't know why the first render shows 2 or more pictures of cats.
Please help.
hi i'm doing a little social network with reactjs
i've created an api with express ans sequelize where i can find the users and the messages that users have done
i want that on the wall, users can like a message
the route is done in my backend and it work but i have an issu with my front
when i want to check the checkox of a message all of theme are checked
i don't understand why all of my checkBox checked at the same time and i don't see how can i check only one and let the other do there life
here is the code
import React, { useEffect, useState } from 'react'
import './Wall.scss'
import axios from 'axios'
import {Link} from "react-router-dom"
import jwt_decode from "jwt-decode"
const token = localStorage.getItem('token')
const config = {
headers: { authorization: `Bearer ${JSON.parse(token)}` }
}
const getAllMessage = 'http://localhost:3001/api/message/'
function Wall() {
const [message, setMessage] = useState([])
useEffect(() => {
axios.get(getAllMessage, config)
.then((res)=>{setMessage(res.data) ; console.log('useEffect')})
.catch((err) => { console.log(err)})
},[])
const [isChecked, setIsChecked] = useState()
const checkHandler = (e) =>{
let item = e.target.closest('[data-id]')
const disLikeMessage = `http://localhost:3001/api/like/dislike/${item.dataset.id}`
const likeMessage = `http://localhost:3001/api/like/${item.dataset.id}`
if(isChecked === item.checked){
console.log('unchecked')
axios.post(disLikeMessage,{}, config)
.then((res)=>{setIsChecked(!isChecked)})
.catch((err) => { console.log(err)})
} else{
console.log('checked')
axios.post(likeMessage,{}, config)
.then((res)=>{setIsChecked(!isChecked)})
.catch((err) => { console.log(err)})
}
}
return (
<div className='containerWall'>
<section className='cardWall'>
<ul className='cardUl'>
{message.map(item =>(
<li key = {item.id} >
<div className ='cardMessage'>
<Link to = {`/Wall/Message/${item.id}`}>
<img src = {item.picture} alt="" className='cardImage'/>
<div className='cardContent'>
<h2>{item.title}</h2>
<p>{item.content} </p>
<p>{item.createdAt}</p>
</div>
</Link>
<div className='likeSystem'>
<input type="checkbox" className='like'data-id={item.id checked={isChecked} onChange={checkHandler} />
<p>{item.likes}</p>
</div>
</div>
</li>
))}
</ul>
</section>
</div>
)
}
export default Wall
HAve any idea how to solve this?
(ps i'm a react beginner so be easy on me i want to understand :) ty for your time )
You have a single state for all the checkbox so they all share the same isChecked value.
If you want to keep one checked value in state per message, you could refactor it in its own component.
A better approach would be to not store any checked state and just rely on the item.checked attribute, when it's updated, you refetch the messages and update the state. React query allows to do this very easily.
const Like = ({ item }) => {
const [checked, setChecked] = useState(item.checked);
function checkHandler({target}) {
const url = `http://localhost:3001/api/like/${target.checked ? 'dislike/' + item.id : item.id}`
axios
.post(url, {}, config)
.then((res) => {
setChecked(prev => !prev);
})
.catch((err) => {
console.log(err);
});
}
return (
<div className="likeSystem">
<input
type="checkbox"
className="like"
checked={checked}
onChange={checkHandler}
/>
<p>{item.likes}</p>
</div>
);
};
What would be the best way to handle errors and display them in a React App using Hooks, at the moment if I try to break the app by mistyping the URL it shows the error but still the data sometimes also, however if I update the state to an empty array in the catch block setData([]);, then it works fine, I just wanted to check and see if this is the ideal way or is there another way?
App.js
import React, {useEffect} from 'react';
import './App.css';
import axios from 'axios';
const App = () => {
interface DataHolder {
userId: string;
id: string;
title: string;
body: string;
}
const [data, setData] = React.useState<DataHolder[]>([]);
const [isLoading, setIsLoading] = React.useState<Boolean>(false);
const [hasError, setHasError] = React.useState<Boolean>(false)
useEffect( () => {
const fetchData = async (): Promise<any> => {
setIsLoading(true);
setHasError(false);
try {
const result = await axios('https://jsonplaceholder.typicode.com/posts');
setData(result.data);
} catch (err) {
setHasError(true);
setData([]);
console.log(err);
}
setIsLoading(false);
}
fetchData()
return () => {
console.log('cleaned');
}
}, [setData]);
return (
<>
{hasError && <p >Something went wrong. problem with the data feed.</p>}
{isLoading ? (
<p >Loading ...</p>
) : (
<ul>
{data.map(item => (
<li key={item.id} >
<p>{item.title}</p>
</li>
))}
</ul>
)}
</>
);
}
export default App;
Conditional rendering should help when you are dealing with hooks.
Loading
Error
Data display part
You can order like this.
if (isLoading) {
return <p>Loading ...</p>;
}
if (hasError) {
return <p>Something went wrong. problem with the data feed.</p>;
}
return (
<>
<ul>
{data?.map((item) => (
<li key={item.id}>
<p>{item.title}</p>
</li>
))}
</ul>
</>
);
You can also give a condition like this, checking the length of the data
return (
<>
<ul>
{data.length > 0 ? data.map((item) => (
<li key={item.id}>
<p>{item.title}</p>
</li>
)) : null}
</ul>
</>
);
I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());
I've created my backend and it works. I tested different Axios requests in order to create a form.
In my React front project, I created a POST axios request, I console.log(response.data) and I got an object with the id, the title and questions.
I am stuck because I don't know how I could display the data of the object in my front.
Here is my front React code:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back-formnest-lereacteur.herokuapp.com/form/create",
{
title: "Your event",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
I am completely stuck here to display the data of my backend in my front
This is my backend code:
const express = require("express");
const router = express.Router();
const Form = require("../models/Form");
router.post("/form/create", async (req, res) => {
try {
if (req.fields.title) {
const newForm = new Form({
title: req.fields.title,
});
await newForm.save();
return res.json(newForm);
} else {
return res.status(400).json({ error: "Missing parameters" });
}
} catch (e) {
return res.status(400).json({ error: e.message });
}
});
This is my console.log(response.data) I want to display in my front React page:
I edited my code and I got an error:
import React, { useState, useEffect } from "react";
/* import { Link } from "react-router-dom"; */
import axios from "axios";
const NewForm = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (data.length === 0) {
const response = await axios.post(
"https://back.herokuapp.com/form/create",
{
title: "Nouveau formulaire",
}
);
console.log(response.data);
setData(response.data);
}
};
fetchData();
}, [data]);
return (
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.map((question, index) => (
<div> {question} </div>
))}
</>
)}
</div>
</>
);
};
export default NewForm;
Hi Guys,
I updated my code but I have still an error code (TypeError: Cannot read property 'length' of undefined)
<>
<div>My forms</div>
<div>
{data && (
<>
<p>{data.title}</p>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again my code, I succeeded only to display the title of my form but I did not succeed to display the data included in my question array. I have a "0" which appears instead of my data. Please help
return (
<>
<div>My forms </div>
<div>
{data && data.questions && (
<>
<div>{data.title} </div>
{data.questions.length &
data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
</>
I updated again, same error appears:
return (
<>
<div>My forms </div>
<div>
{data &&
data.questions &&
data.questions.length(
<>
<div>{data.title} </div>
{data.questions.map((question, index) => {
return <p key={index}>{question}</p>;
})}
</>
)}
</div>
you've done the hard part!
now just .map over the question array if you want to display them out?
<div>
{data.questions.map((question => (
<div> {question.title} </div>
))}
</div>
I've only done a simple example but of course you can display as much or as little as you want
of course anything in state you can render. so if you want to display title do:
{data.title} wherever pleases you
It looks like your backend responds with an object, so here is how you could go about it.
1) Change your initinal state to undefined like this.
const [data, setData] = useState([]);
to
const [data, setData] = useState(undefined);
Then you can use it in the display like this
return (
<div>
{data && (
<>
<p>{data._id}</p>
<p>{data.title}</p>
{data.question.length && data.question.map((question,idx) => {
// this is assuming that each question is just a string and not an object
return (<p key={idx}>{question}</p>)
})}
</>
)}
</div>
)