Reactjs, typescript - reactjs

I am trying to make Searchbar where I can fetch the authors and show the author Id and picture, Why I'm getting this error (any Property 'toLowerCase' does not exist on type 'never') in my code?
import { useEffect, useState } from 'react';
import axios from 'axios';
function Searchbar() {
const [query, setQuery] = useState('');
const [data, setData] = useState([]);
// Add one more state to store the authors being searched for
const [searchResults, setSearchResults] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get(`https://picsum.photos/v2/list`);
setData(res.data);
};
fetchData();
}, []);
useEffect(() => {
setSearchResults(
data.filter((authorData) =>
authorData['author'].toLowerCase().includes(query)
)
);
}, [query, data]);
return (
<div className='app'>
<input
className='search'
placeholder='Search...'
onChange={(e) => setQuery(e.target.value.toLowerCase())}
/>
<div>
{searchResults.map((author) => (
<div>{author}</div>
))}
</div>
</div>
);
}
export default Searchbar;

The problem seems to be you are relying on typescript to infer types of an api call. (Instead typescript inferred it as never which would always error if you try anything with it)
To fix this, simply set a type for the fetch data
type fetchData = {
id: number,
author: string,
width: number,
height: number,
url: string,
download_url: string
}
Then set your states as such
const [data, setData] = useState<fetchData[]>([]);
const [searchResults, setSearchResults] = useState<fetchData[]>([]);
Full code
import { useEffect, useState } from "react";
import axios from "axios";
type fetchData = {
id: number,
author: string,
width: number,
height: number,
url: string,
download_url: string
}
function Searchbar() {
const [query, setQuery] = useState("");
const [data, setData] = useState<fetchData[]>([]);
// Add one more state to store the authors being searched for
const [searchResults, setSearchResults] = useState<fetchData[]>([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get(`https://picsum.photos/v2/list`);
setData(res.data);
};
fetchData();
}, []);
useEffect(() => {
setSearchResults(
data.filter((authorData : fetchData) =>
authorData.author.toLowerCase().includes(query)
)
);
}, [query, data]);
return (
<div className="app">
<input
className="search"
placeholder="Search..."
onChange={(e) => setQuery(e.target.value.toLowerCase())}
/>
<div>
{searchResults.map((author) => (
<div key={author.id}>{author.author}</div>
))}
</div>
</div>
);
}
export default Searchbar;

Related

How to pass a value from an input to a submit button?

I'm currently working on a project to implement a website to check the weather forecast.
I'm trying to get the value from the input field and when I click the submit button, this value should be set to cityName. What do I have to change in order to make this work?
import { useState, useEffect } from "react"
export function WeatherInfo() {
const token: string = '7ebe7c2a03cd48c090a193437'
async function getCurrentWeather(cityName: string): Promise<any> {
const response = await fetch(`http://api.weatherapi.com/v1/current.json?key=${token}&q=${cityName}`)
const data = await response.json()
console.log(data)
return data
}
const [cityName, setCityName]: any = useState('')
const [cityWeather, setCityWeather] = useState({})
const [value, setValue] = useState('')
const handleChange = (event: any) => {
setValue(event.target.value)
}
const handleSubmit = (event: any) => {
event.preventDefault()
setCityName(value)
}
useEffect(() => {
async function fetchData() {
const cityWeather = await getCurrentWeather(cityName)
}
fetchData()
})
return (
<div >
<form onSubmit={handleSubmit}>
<input onChange={handleChange} placeholder="Type here" />
<button>Search</button>
</form>
</div>
);
}
You should add a dependency array to your effect hook so that it triggers whenever cityName changes.
Updating the cityWeather state should only be done via the setCityWeather function.
useEffect(() => {
if (cityName) { // only fetch when you've got a value
getCurrentWeather(cityName).then(setCityWeather);
}
}, [cityName]);
You should also try to use as few any types as possible, preferably none
// define stand-alone functions outside your components
// eg weather-api.ts
const token = "your-api-key";
export interface CurrentWeather {
temp_c: number;
feelslike_c: number;
// etc
}
export async function getCurrentWeather(
cityName: string
): Promise<CurrentWeather> {
// safely encode URL query params
const params = new URLSearchParams({
key: token,
q: cityName,
});
const response = await fetch(
`http://api.weatherapi.com/v1/current.json?${params}`
);
// don't forget to check for errors
if (!response.ok) {
throw response;
}
return response.json(); // will be cast to the `CurrentWeather` type
}
import { useState, useEffect, FormEventHandler } from "react";
import { getCurrentWeather, CurrentWeather } from "./weather-api";
export function WeatherInfo() {
const [cityName, setCityName] = useState("");
const [cityWeather, setCityWeather] = useState<CurrentWeather>(); // default undefined
const [value, setValue] = useState("");
useEffect(() => {
getCurrentWeather(cityName).then(setCityWeather).catch(console.error);
}, [cityName]);
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();
setCityName(value);
};
return (
<div>
{cityWeather && (
<p>
The current temperature in {cityName} is {cityWeather.temp_c} °C
</p>
)}
<form onSubmit={handleSubmit}>
<input
onChange={(e) => setValue(e.target.value)}
placeholder="Type here"
/>
<button>Search</button>
</form>
</div>
);
}

React Context loosing state on page refresh

So basicaly i have a subreddit context where i get a bunch of subreddis from the api
import axios from "axios";
import { createContext, useCallback, useEffect, useState } from "react";
import { getUniqueObjects } from "../Helpers/Helpers";
import { ChildrenType } from "../Types/ProviderChildrenType";
import { Subreddit, SubredditsResponse } from "../Types/Subreddits";
type InitialState = {
subredditsData?: Subreddit[];
subredditsLoading?: boolean;
subredditsError?: boolean;
subredditsHasMore?: boolean;
getSubreddits?: (arg0: string) => void;
};
export const SubredditContext = createContext<InitialState>({});
export const SubredditContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Subreddit[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [hasMore, setHasMore] = useState(true);
const UseSubreddits = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response: SubredditsResponse) => {
setData(getUniqueObjects([...data, ...response.data]));
setHasMore(response.data.length > 0);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => apiCall(), [apiCall]);
};
return (
<SubredditContext.Provider
value={{
subredditsData: data,
subredditsLoading: loading,
subredditsError: error,
subredditsHasMore: hasMore,
getSubreddits: UseSubreddits,
}}
>
{children}
</SubredditContext.Provider>
);
};
In my home page I trigger the custom hook of the context "UseSubreddits" which I pass it as the "getSubreddits" prop,
function Homepage() {
const navigate = useNavigate();
const [pageNumber, setPageNumber] = useState(1);
const {
subredditsData,
subredditsLoading,
subredditsError,
subredditsHasMore,
getSubreddits,
} = useContext(SubredditContext);
getSubreddits!(`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=${pageNumber}&limit=16`)
window.addEventListener("scroll", () => {
if (
window.scrollY + window.innerHeight >=
document.documentElement.scrollHeight &&
subredditsHasMore
) {
setPageNumber(pageNumber + 1);
}
});
return (
<>
<Navbar pageTitle="subreddits" />
<div className="homepage">
<div className="homepage__subreddits">
{subredditsData?.map((item) => {
return (
<div key={item.id}>
<SubredditCard
key={item.id}
onClick={() => navigate(`/posts/${item.id}`)}
title={item.title}
description={item.description}
/>
</div>
);
})}
</div>
</div>
<div className="homepage__text">
{subredditsLoading && <h2>Loading...</h2>}
{subredditsError && (
<h2>An error has occured please refresh your page.</h2>
)}
</div>
</>
);
}
export default Homepage;
I have the same kind of context file where I get the posts of the selected subreddit
type InitialState = {
postData?: Post[];
postLoading?: boolean;
postError?: boolean;
getPost?: (arg0: string) => void;
voteHandler?: (
arg0: string,
arg1: string,
arg2: boolean,
arg3: boolean
) => void;
};
export const PostContext = createContext<InitialState>({});
export const PostContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const UsePosts = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => {
apiCall();
}, [apiCall]);
};
return (
<PostContext.Provider
value={{
postData: data,
postLoading: loading,
postError: error,
getPost: UsePosts,
}}
>
{children}
</PostContext.Provider>
);
};
and then I do the same thing in the Post component as I do in the Homepage
function Posts() {
const { subredditId } = useParams();
const [urlParam, setUrlParam] = useState("");
const {
postData,
postLoading,
postError,
getPost,
voteHandler,
} = useContext(PostContext);
const { subredditsData } = useContext(SubredditContext);
const selectedSubreddit = useMemo(
() => subredditsData!.find((subreddit) => subreddit.id === subredditId),
[subredditsData]
);
const navigate = useNavigate();
const sortByTitle = "?sortBy=title";
getPost!(
`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits/${subredditId}/posts${urlParam}`
);
return (
<>
<Navbar pageTitle={selectedSubreddit!.title} />
<div className="posts-screen">
<div className="posts-screen__left-panel">
<div className="posts-screen__left-panel-content">
<SortBy onClick={() => setUrlParam(sortByTitle)} />
<div className="posts-screen__posts-container">
{postData?.map((post) => {
return (
<PostCard
key={post.id}
id={post.id}
title={post.title}
description={post.body}
user={post.user}
voteCount={post.upvotes - post.downvotes} ...
Everything works fine except that in the post screen if I reload the page the subreddit state is lost and the posts screen gives and error. I know that the state is lost on refresh but shouldn't it make the api call again for the subreddits?
I'm new at Context so I don't know how to handle it better

I want stop rendering in react-hooks

It's continuing to be rendered. I want to stop it.
Because of the problem, photos and text received from Api keep changing randomly.
I think useEffect is the problem. Please let me know because I am a beginner.
This is useFetch.jsx
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
useEffect(() => {
fetchUrl();
});
return [data, loading];
}
export default useFetch;
This is Main.jsx
import React from "react";
import styled from "styled-components";
import useFetch from "./useFetch";
import "./font.css";
const url = "https://www.thecocktaildb.com/api/json/v1/1/random.php";
const Main = () => {
const [data, loading] = useFetch(url);
return (
<Wrapper>
<Header>My Cocktail Recipe</Header>
{loading ? (
"Loading..."
) : (
<>
{data.drinks.map(
({ idDrink, strDrink, strAlcoholic, strGlass, strDrinkThumb }) => (
<Container>
<img src={`${strDrinkThumb}`} alt="" />
<div key={`${idDrink}`}>{`${strDrink}`}</div>
</Container>
)
)}
</>
)}
<Search type="text" placeholder="검색하세요" val />
</Wrapper>
);
};
export default Main;
You must update useFetch.jsx:
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
useEffect(() => {
fetchUrl();
}, []); //<--- Here
return [data, loading];
}
export default useFetch;
The problem is that the useEffect hook receives two arguments and you forgot the second argument, which is the effect's dependencies array.
useEffect(() => {
fetchUrl();
}, []);
return [data, loading];
}
Add Array Thanks Nick Parsons

TypeError: Cannot read property 'map' of undefined React Hooks

I need some help understanding why I'm getting the error from the title: 'TypeError: Cannot read property 'map' of undefined'. I need to render on the page (e.g state & country here) some data from the API, but for some reason is not working.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return JSON.stringify(data);
}
useEffect(() => {
fetchData().then((res) => {
setUser(res)
setInfo(res.results);
})
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Can you guys provide me some help? Thanks.
Try this approach,
const APIFetch = () => {
const [user, setUser] = useState("");
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get("https://randomuser.me/api");
return data; <--- Heres is the first mistake
};
useEffect(() => {
fetchData().then((res) => {
setUser(res);
setInfo(res.data.results);
});
}, []);
const getName = (user) => {
const { state, country } = user.location; <--- Access location from the user
return `${state} ${country}`;
};
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>;
})}
</div>
);
};
Return data without stringify inside the fetchData.
Access user.location inside getName.
Code base - https://codesandbox.io/s/sharp-hawking-6v858?file=/src/App.js
You do not need to JSON.stringify(data);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return data.data
}
Do it like that
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('https://randomuser.me/api');
setUser(res.data);
setInfo(res.data.results);
}
featchData();
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Codesandbox: https://codesandbox.io/s/vigorous-lake-w52vj?file=/src/App.js

React Load More using Hooks

I try to do Load More on a list of data as written below:
import React, { useState, useEffect } from "react";
import { render } from "react-dom";
import axios from "axios";
import "./style.css";
const App = () => {
const LIMIT = 2;
const [data, setData] = useState([]);
const [isLoading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const loadData = async (skip = 1, limit = LIMIT) => {
const URL = "https://reqres.in/api/users";
const headers = {
"Content-Type": "application/json",
Accept: "application/json"
};
const params = {
page: skip,
per_page: limit
};
const a = await axios.get(URL, { params, headers });
// const b = [...new Set([...data, ...a.data.data])]; <-- setting this will thrown error
setData(a.data.data);
setLoading(false);
};
useEffect(() => {
setLoading(true);
loadData(page);
}, [page]);
useEffect(() => {
console.log("page", page, "data", data.length);
}, [page, data]);
const doReset = evt => {
evt.preventDefault();
setPage(1);
};
const doLoadMore = evt => {
evt.preventDefault();
setPage(page + 1);
};
return (
<div className="container">
<h1>Listing</h1>
<button className="btn text-primary" onClick={evt => doReset(evt)}>
Reset
</button>
<button className="btn text-primary" onClick={evt => doLoadMore(evt)}>
Load More
</button>
{isLoading && <p>Loading..</p>}
{!isLoading && (
<ul>
{data.map(a => (
<li key={a.id}>
{a.id}. {a.email}
</li>
))}
</ul>
)}
</div>
);
};
render(<App />, document.getElementById("root"));
a fully working example in here.
i think this code should be working, but is not.
const a = await axios.get(URL, { params, headers });
const b = [...new Set([...data, ...a.data.data])];
setData(b);
so please help, how to do Load More in React Hooks?
after a few try, i think this is the best thing i can do. make the code working but also not let the compiler warning:
import React, { useState, useEffect, useCallback } from "react";
import axios from "axios";
import Navbar from "./Navbar";
const App = () => {
const LIMIT = 2;
const [tube, setTube] = useState([]);
const [data, setData] = useState([]);
const [isLoading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const loadData = useCallback(
async (limit = LIMIT) => {
setLoading(true);
const URL = "https://reqres.in/api/users";
const headers = {
"Content-Type": "application/json",
Accept: "application/json"
};
const params = {
page,
per_page: limit
};
const a = await axios.get(URL, { params, headers });
if (!a.data.data) {
return;
}
setData(a.data.data);
setLoading(false);
},
[page]
);
useEffect(() => {
if (!isLoading) {
return;
}
setTube([...new Set([...tube, ...data])]);
}, [data, isLoading, tube]);
useEffect(() => {
loadData();
}, [loadData]);
useEffect(() => {
console.log("page", page, "data", data.length);
}, [page, data]);
const doLoadMore = evt => {
evt.preventDefault();
setPage(page + 1);
};
return (
<>
<Navbar />
<main role="main" className="container">
<div className="starter-template text-left">
<h1>Listing</h1>
<button className="btn text-primary" onClick={evt => doLoadMore(evt)}>
Load More
</button>
<ul>
{tube &&
tube.map(a => (
<li key={a.id}>
{a.id}. {a.email}
</li>
))}
</ul>
{isLoading && <p>Loading..</p>}
</div>
</main>
</>
);
};
export default App;
also i found, it could be much easier just apply this eslint-disable-next-line react-hooks/exhaustive-deps to let the compiler ignore the warning. something like this.
useEffect(() => {
setConfig({...config, params: {...params, skip}});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [skip]);
for information can be found on this:
how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook
https://stackoverflow.com/a/55844055/492593
react #14920
I got your example to work by changing to this:
const b = [...data, ...a.data.data];
setData(b);

Resources