Send catch error state from child to parent and show message - reactjs

I have a page with 2 different containers that each of them make a data request but if fails I want replace ALL page with a big error message in the middle of the page, all blank
If User catch any error, should fail everything and not just show the message where is, but replace all UserApps code, User and UserProjects will be replaced by the big blank page with error message in the middle
(this is just an example):
Parent Component - UsersApp.tsx
import React, { FC } from "react"
import User from "../containers/User"
import UserProjects from "../containers/UserProjects"
const UsersApp: FC = () => {
return (
<>
<div>
<h1>Welcome!</h1>
</div>
<hr />
<div>
{/* //this error must be from child <User/> catch error component */}
{error ? (
<ErrorState />
) : (
<User />
<UserProjects />
)}
</div>
</>
)
}
export default UsersApp
Child Component - User.tsx
import React, { FC, useState, useEffect } from "react"
import { getUserData } from "../requests/getUserData"
const User: FC<{}> = () => {
const [error, setError] = useState(false)
const [loading, setLoading] = useState(false)
const [name, setName] = useState("")
const [age, setAge] = useState("")
const [country, setCountry] = useState("")
const getData = async () => {
setLoading(true)
setError(false)
const response = await getUserData()
const data = response.data
return data
}
useEffect(() => {
getData()
.then((data) => {
setName(data.name)
setAge(data.age)
setCountry(data.country)
})
.catch((error) => {
setLoading(false)
setError(true)
})
return
}, [])
return (
<div>
<h1>{name}</h1>
<p>{age}</p>
<p>{country}</p>
</div>
)
}
export default User
ErrorState.tsx
import React, { FC } from 'react'
const ErrorState: FC = (props) => (
<div>
<h1>Something went wrong!!!!! </h1>
</div>
)
export default ErrorState

Your parent needs to hold the error state. Pass the setError as a prop to the children
const UsersApp = () => {
const [error, setError] = useState(false)
return (
<>
<div>
<h1>Welcome!</h1>
</div>
<hr />
<div>
{error && <ErrorState/>}
{!error && (
<User setError={setError} />
<UserProjects setError={setError} />
)}
</div>
</>
)
}
const User = ({ setError }) => {
//remove set error state from child
}
const UserProjects = ({ setError }) => {
//remove set error state from child
}

You can use Error Boundaries, it will catch all the errors.

Related

I'm getting this error map is not a function error

This is my code:
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import { useLocation } from 'react-router-dom'
import Button from 'react-bootstrap/esm/Button'
const BookingScreen = () => {
const [room, setRoom] = useState([])
const [loading, setLoading] = useState()
const location = useLocation()
const path = location.pathname.split("/",5)[4]
useEffect(() => {
fetchData()
},[])
const fetchData = async () => {
const res = await axios.get("http://localhost:5000/api/room/" + path)
setRoom(res.data)
console.log(res.data)
setLoading(false)
}
const singleRoom = room.map((item) => {
return (
<div key={item._id} >
<div className="row justify-content-md-center mt-4 ">
<div className="col-md-6">
<img src={item.imageUrl
[0]
} alt="singleRoom" />
</div>
<div className="col-md-4">
<h2>{item.name}</h2>
<p>{item.desc}</p>
<p>Category : {item.categoy}</p>
<Button variant="secondary">
Book Now </Button>
</div>
</div>
</div>
)
})
return (
<>
{loading ? <h1>Loading ...</h1> : singleRoom}
</>
)
}
export default BookingScreen
I'm trying to populate the list singleRoom but it keeps saying map is not a function when I console data I'm getting everything and after that, I used setRoom hook to set the data, but somehow it's empty.
Things i would say, is that if the data returns as an array then its fine, otherwise you'll need to change your logic of return.
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import { useLocation } from 'react-router-dom'
import Button from 'react-bootstrap/esm/Button'
const BookingScreen = () => {
const [room, setRoom] = useState([])
const [loading, setLoading] = useState(true) // set to true by default
const location = useLocation()
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
const path = location.pathname.split("/", 5)[4]
setLoading(true)
try {
const res = await axios.get(`http://localhost:5000/api/room/${path}`)
if (res.data) {
setRoom(res.data); // does this actually return an array?
setLoading(false)
} else {
setRoom([]) // force back an empty array if data wasn't returned
setLoading(false)
}
} catch (err) {
setRoom([]) // set back to empty
setLoading(false)
// I'd usually also do an error state here.
}
}
const singleRoom = room.length > 0 ? room.map(({ _id, imageUrl, name, desc, categoy }) => (
<div key={_id} >
<div className="row justify-content-md-center mt-4 ">
<div className="col-md-6">
<img src={imageUrl
[0]
} alt="singleRoom" />
</div>
<div className="col-md-4">
<h2>{name}</h2>
<p>{desc}</p>
<p>Category : {categoy}</p>
<Button variant="secondary">
Book Now </Button>
</div>
</div>
</div>
)
) : <div>No room data found</div>
return (
<>
{loading ? <h1>Loading ...</h1> : singleRoom}
</>
)
}
export default BookingScreen

Debounce input does not read keyboard information

I need DebounceInput to read information from my keyboard, but when I type in Ervin for example, the whole list stays and not just Ervin as it used to work. I don't really know where I went wrong. I know that Debounce is supposed to wait until the user stops typing, but even after waiting a minute nothing changed.
This is my files:
DebounceInput:
import {DebounceInput} from 'react-debounce-input';
type Props ={
onChange:Function;
}
const Debounce:React.FC<Props> = ({onChange}) => {
return(
<DebounceInput
onChange={(e) => onChange(e.target.value)}
debounceTimeout={500}
className="SearchInput"
placeholder="Search by user name..."
/>
)
}
export default Debounce
UsersList:
import {UsersContext} from '../../contexts/Users'
type Props = {
filteredUsers:Array<Person>;
}
type Person = {
name:string;
username:string;
}
const UsersList: React.FC = () => {
const filteredUsers = useContext(UsersContext);
return(
<div className="ListHead">
<ol className="list">
{filteredUsers.map((Person) => (
<li key={Person.name}>
<span>{Person.name}</span>
#{Person.username}
</li>
))}
</ol>
</div>
)}
Users:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
type Users = Person[];
type Person = {
name:string;
username:string;
}
export const UsersContext = React.createContext<Users>([]);
const UsersProvider:React.FC= ({children}) => {
const [users, setUsers] = useState<Person[]>([]);
useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then((response) =>{
setUsers(response.data);
})
.catch((error) => {
console.log(error);
});
},[]);
return (
<UsersContext.Provider value={users}>
{children}
</UsersContext.Provider>
)
}
And Main:
const Main:React.FC= () => {
const [showUser, setShowUser] = useState("");
return (
<div>
<div>
<Header/>
</div>
<UsersProvider>
<UsersList />
<Debounce onChange={setShowUser}/>
</UsersProvider>
</div>
)
}
Your debouncing Input does not do anything except for changing the showUser state. Since your users resides in the UsersProvider . You can pass the value of your showUser as a prop to UsersProvider .
const Main:React.FC= () => {
const [showUser, setShowUser] = useState("");
return (
<div>
<div>
<Header/>
</div>
<UsersProvider searchText={showUser}>
<UsersList />
<Debounce onChange={setShowUser}/>
</UsersProvider>
</div>
)
}
Now inside your UsersProvider you can do the following
type UsersProviderProps = {
children: React.ReactNode;
searchText: string;
}
export const UsersContext = React.createContext<Users>([]);
const UsersProvider:React.FC<UsersProviderProps>= ({children, searchText}) => {
const [users, setUsers] = useState<Person[]>([]);
useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then((response) =>{
setUsers(response.data);
})
.catch((error) => {
console.log(error);
});
},[]);
const filteredUsers = useMemo(() => {
if(searchText.trim().length > 0 && users.length > 0){
return users.filter((person) =>
person.name.toLowerCase().includes(searchText.toLowerCase())
);
} else {
return users;
}
}, [searchText, users])
return (
<UsersContext.Provider value={filteredUsers}>
{children}
</UsersContext.Provider>
)
}

React useState hook - I want to render certain component when only press submit button not when onChange

I'm new to react.
Now trying to make form with react hooks, and I want to render Cloud component only when press submit button. But it rendered every onChange called.
I know that onChange re-rendered cause also useState hook.
But have no idea how to render only when press submit button.
My final goal is when write name and press enter, if value is not contained in api, setShake make shake True and if True, put shake-cloud class in Cloud.js.
REACT IS TOO DIFFICULT :(
Thanks for help tho :)
App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
<Cloud cloudhex={cloudHex} shake={shake} />
</div>
</div>
)}
</>
);
};
export default App;
Cloud.js
import React, {useEffect} from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCloud } from "#fortawesome/free-solid-svg-icons";
import './cloud.css';
const Cloud = ({cloudhex, shake}) => {
useEffect(() => {
}, [])
console.log(shake)
return (
<div className={`cloud-wrap ${ shake ? "shake-cloud":''}`}>
<span className="cloudhexname">{cloudhex}</span>
<FontAwesomeIcon icon={faCloud} className="cloud" style={{color:`${cloudhex}`}} />
</div>
);
};
export default Cloud;
A good approach in this case is to use useRef() Hook to store our search field value, instead of using useState(). Because useRef() Hook does not force a re-render while useState() does. This approach is known as un-controlled way to use input field.
You basically need to make few modifications in your code which are as follows:
const search = useRef("");
Then remove onChange={updateSearch} and value={search} from input and use a property ref={search}. So that your input looks like below:
<input
className="search-bar"
type="text"
ref={search}
/>
Then in the submit handler, you can get the value of the input field using search.current.value. So your getSearch() would look like
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search.current.value);
isColor();
};
Assuming user has typed an input. If not then you can set a validation before using setQuery() in the getSearch() form submit handler.
if(search.current.value){
setQuery();
}
Note: If you have any other controlled inputs in your project, you can change then to un-controlled inputs using refs and this way re-renders would not happen in your code.
Do it like that
If you want to render cloud component after form submit then put one flag and toggle that, here I take clicked state
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
const [clicked, setClicked] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
{clicked && <Cloud cloudhex={cloudHex} shake={shake} />}
</div>
</div>
)}
</>
);
};
export default App;

TypeError: Cannot read property 'map' of undefined in react-hooks

I'm a beginner. Thank you in advance for sharing your knowledge.
This error did not appear originally.
But even though they used the same code, it is now appearing.
What's the reason?
Although the data received through Api has the form of an array, the 'map' method does not work.
I read the other same question but I couldn't solve this problem.
This error bothered me for a day. Let me know what I have to do.
import React, { useState, useEffect } from "react";
import axios from "axios";
import styled from "styled-components";
const MyModal = ({ onClose, selectedItem }) => {
const [data, setData] = useState([]);
let id = selectedItem;
let url = `https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${id}`;
useEffect(() => {
axios
.get(url)
.then((res) => {
setData(res.data.drinks);
})
.catch((error) => {
console.log(error);
});
}, [url]);
return (
<MyModals onClick={onClose}>
<Wrapper>
<button onClick={onClose}>X</button>
{data.map((result) => { 👈 This is the part of the problem.
return (
<Container>
<Image>
<img src={result.strDrinkThumb} alt={result.idDrink} />
</Image>
<About>
<Name>{result.strDrink}</Name>
</Container>
);
})}
</Wrapper>
</MyModals>
);
};
export default MyModal;
Likewise, this file has the same problem. Errors appear and then appear.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Portal from "../Components/Portal";
import Modal from "../Components/Modal";
const Search = () => {
const [searchTerm, setSearchTerm] = useState("a");
const [cocktails, setCocktails] = useState([]);
const [open, setOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState("");
const handleOpen = (idDrink) => {
setSelectedItem(idDrink);
setOpen(true);
console.log("open");
};
const handleClose = () => {
setOpen(false);
console.log("close");
};
useEffect(() => {
const getDrinks = async () => {
try {
const response = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${searchTerm}`
);
const data = await response.json();
setCocktails(data);
} catch (error) {
console.log(error);
}
};
getDrinks();
console.log("useEffect");
}, [searchTerm]);
return (
<main style={{ width: "100%" }}>
<SearchForm setSearchTerm={setSearchTerm} />
<Wrapper className="cocktail-list">
{cocktails &&
cocktails.drinks.map(({ idDrink, strDrink, strDrinkThumb }) => (
<Container
className="cocktail"
onClick={() => {
handleOpen(idDrink);
}}
>
<Img>
<img src={`${strDrinkThumb}`} alt={`${strDrink}`} />
</Img>
<Name key={`${idDrink}`}>{`${strDrink}`}</Name>
</Container>
))}
</Wrapper>
{open && (
<Portal>
<Modal selectedItem={`${selectedItem}`} onClose={handleClose} />
</Portal>
)}
</main>
);
};
export default Search;
Is there a problem with the part where I receive the Api data?
import React from "react";
import styled from "styled-components";
import { useState, useEffect } from "react";
import Search from "./Search";
import Modal from "../Components/Modal";
import Portal from "../Components/Portal";
const Main = () => {
const url = "https://www.thecocktaildb.com/api/json/v1/1/random.php";
const [data, setData] = useState([]);
const [open, setOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState("");
useEffect(() => {
const fetchUrl = async () => {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (error) {
console.log(error);
}
};
console.log("useEffect");
fetchUrl();
}, []);
const handleOpen = (idDrink) => {
setSelectedItem(idDrink);
setOpen(true);
console.log("open");
};
const handleClose = () => {
setOpen(false);
console.log("close");
};
return (
<Wrapper className="main">
{data &&
data.drinks.map(({ idDrink, strDrink, strDrinkThumb }) => (
<>
<Container
onClick={() => {
handleOpen(idDrink);
console.log(handleOpen(idDrink));
}}
>
<img src={`${strDrinkThumb}`} alt={`${strDrink}`} />
<div key={`${idDrink}`}>{`${strDrink}`}</div>
</Container>
{open && (
<Portal>
<Modal selectedItem={`${selectedItem}`} onClose={handleClose} />
</Portal>
)}
</>
))}
<Search />
</Wrapper>
);
};
export default Main;
The error occurred in all files that wrote the 'map' method.
I really don't know what the problem is. Help me!
This has occurred because your map() called before the data come from API. So I will suggest first complete the API call and let the data came properly. Then you should map. You can use-
{data && data.map((result) => { 👈 This will solve ur problem
return (
<Container>
<Image>
<img src={result.strDrinkThumb} alt={result.idDrink} />
</Image>
<About>
<Name>{result.strDrink}</Name>
</Container>
);
})}
Make sure of your data from API is a valid Array, you can check it with Array.isArray().
Code could be like this:
{Array.isArray(data) &&
data.map(result => (
<Container>
<Image>
<img src={result.strDrinkThumb} alt={result.idDrink} />
</Image>
<About />
<Name>{result.strDrink}</Name>
</Container>
))}

useContext hook update from child component

I'm fairly new to hooks as well as useContext, I'm attempting to update onClick from a child component and keep running into the error 'setPokemonId' is not a function, or.. it just doesn't do anything at all.
PokedexContext.js
import React, { useState, useEffect } from "react";
import axios from "axios";
const PokedexContext = React.createContext([{}, () => {}]);
const PokedexProvider = (props) => {
const [state, setState] = useState({});
const [loading, setLoading] = useState(true);
let [error, setError] = useState(false);
let [pokemonId, setPokemonId] = useState(10); //807 max
useEffect(() => {
setLoading(true);
setError(false);
axios
.get(`https://pokeapi.co/api/v2/pokemon/${pokemonId}`)
.then((res) => {
setState(res.data, loading);
setLoading(false);
})
.catch((err) => {
setLoading(false);
if (err.response) {
// pokemon not found
setError("Response");
} else if (err.request) {
// api error
setError("Request");
} else {
// everything else
setError(true);
}
});
}, [pokemonId]);
return (
<PokedexContext.Provider
value={[
state,
setState,
error,
loading,
setLoading,
pokemonId,
setPokemonId,
]}
>
{props.children}
</PokedexContext.Provider>
);
};
export { PokedexContext, PokedexProvider };
App.js
import React from "react";
import { PokedexProvider } from "../../context/PokedexContext";
import Pokedex from "../Pokedex";
const Landing = () => {
return (
<PokedexProvider>
<main className="container">
<Pokedex />
</main>
</PokedexProvider>
);
};
export default Landing;
Pokedex.js
import React, { useState, useContext } from "react";
import pokedex from "../assets/pokedex.png";
import PokedexScreen from "./PokedexScreen";
import errorHandling from "../utils/errorHandling";
import { PokedexContext } from "../context/PokedexContext";
import spinner from "../assets/pika-load.gif";
const Pokedex = () => {
const [state, setState, error, loading, setPokemonId, pokemonId] = useContext(
PokedexContext
);
const [power, setPower] = useState(false);
const [shinyDisplay, setShinyDisplay] = useState(false);
console.log(pokemonId);
return (
<div
alt="Pokedex"
data-testid="Pokedex"
className="pokedex"
style={{ backgroundImage: `url(${pokedex})` }}
>
<button className="pokedex--onButton" onClick={() => setPower(!power)}>
{power ? "Off" : "On"}
</button>
{error ? (
<div className="pokedex--screen__error">{errorHandling(error)}</div>
) : power ? (
<>
{loading ? (
<img
className="pokedex--screen__load"
src={spinner}
alt="Loading..."
/>
) : (
""
)}
<button
className="pokedex--shinyButton"
onClick={() => setShinyDisplay(!shinyDisplay)}
>
S
</button>
<span>
<button
className="pokedex--negativeButton"
onClick={() => setPokemonId(pokemonId - 1)}
>
-
</button>
<button
className="pokedex--positiveButton"
// onClick={}
>
+
</button>
</span>
</>
) : (
<div className="pokedex--screen__off" />
)}
</div>
);
};
export default Pokedex;
My goal here is to update the pokemonId when clicking the positive or negative buttons, and is what is currently causing the crashes.
Issue
When you're exporting your values in Context Provider. It's in the 6th index of the array
<PokedexContext.Provider
value={[
state,
setState,
error,
loading,
setLoading,
pokemonId,
setPokemonId, (6)
]}
>
{props.children}
</PokedexContext.Provider>
But you're using from the 4th index in Pokedex.
const [state, setState, error, loading, setPokemonId, pokemonId] = useContext(
PokedexContext
);
Solution
const [
state,
setState,
error,
loading,
setLoading,
pokemonId,
setPokemonId, (6)
] = useContext(
PokedexContext
);

Resources