Updating array using react hooks - arrays

I am making an application using the Upsplash API.
Upon rendering I want to display 30 images, witch works correctly.
import React, { useState, useEffect } from "react"
const ContextProvider =({ children }) =>{
const [allPhotos, setAllPhotos] = useState([])
const [cartItems, setCartItems] = useState([])
const [imageQuery, setImageQuery] = useState('')
useEffect(() => {
const url = `https://api.unsplash.com/photos?page=5&per_page=30&client_id=${process.env.REACT_APP_UNSPLASH_KEY}`
async function getPhotos() {
const photosPromise = await fetch(url)
const photos = await photosPromise.json()
setAllPhotos(photos)
}
getPhotos()
},[])
I then pass AllPhotos to my Photos.js using my context, and map over allPhotos, passing the photo to my Image component to display information about the image.
import React, {useContext} from "react"
import {Context} from "../Context"
function Photos(){
const {allPhotos} = useContext(Context)
const imageElements = allPhotos.map((photo,index) =>(
<Image key={photo.id} photo={photo}/>
))
return(
<>
<main>
{imageElements}
</main>
</>
)
}
export default Photos
const Image = ({ photo }) => {
return (
<div
<img src={photo.urls.thumb} className="image-grid" alt="" />
</div>
)
}
From here the images from the API display and everything is working correctly.
What I want to do now is add a search query, where the users can search for certain images.
I made a component for the input value
import React, { useContext } from "react"
import {Context} from "../../Context"
const QueryInput = () =>{
const {imageQuery, setImageQuery, SearchImage} = useContext(Context)
return(
<form onSubmit={SearchImage} >
<label>
Search Photos
<input
type="text"
className="query-input"
placeholder="Search Images"
value={imageQuery}
onChange={(e) => setImageQuery(e.target.value) }
/>
</label>
<button type="submit">Search Image</button>
</form>
)
}
export default QueryInput
I made a searchQuery function in my context
const SearchImage = async (e) =>{
e.preventDefault()
const queryUrl = `https://api.unsplash.com/search/photos?
age=5&per_page=30&query=${imageQuery}&client_id=${APP_KEY}`
const response = await fetch(queryUrl)
const queryPhotos = await response.json();
setAllPhotos(prevState => [...prevState, ...queryPhotos])
}
Everything works so far, I can console.log(queryPhotos) and get the users images of the query they searched for. If I search for "stars" I will get a bunch of images with stars.
What im having trouble doing is mapping through allPhotos again and displaying the query search images.
The error im having is
TypeError: queryPhotos is not iterable
I have been at this for awhile. Any information/advice would be greatly appreciated. Any questions about the code or need additional information I can provide it. THANK YOU.

In short.
queryPhotos is not an array.
unsplash api response for api /photos and /search/photos is a bit different. One return an array, while the other is an object, you need to access photos in results
So, change this line from
setAllPhotos(prevState => [...prevState, ...queryPhotos])
to
setAllPhotos(prevState => [...prevState, ...queryPhotos.results])
Should fix your problem.

Related

How to add searchinputs as array in localStorage with React TypeScript

I'm trying to make a search bar, where the search will occur once the button (or enter) is clicked. To this, I want to save the searched phrases in localStorage.
I have no problem with the first part. Things work fine when searching with a button or on enter-click. However, when I try to add the search as an array to localStorage I keep getting issues (like string doesn't work with an array, and many more. I've tried A LOT of different things). I've done this with JS and vanilla React, but never with TS.
In the provided code, the search works, and to put in at least something (as simple as I could) - the latest value is also stored in localStorage.
The code can be found here
Or here:
// index file
import { useState } from "react";
import { Searchbar } from "./searchbar";
import { SearchContainer } from "./style";
export const Search = () => {
const [search, setSearch] = useState<string>("");
return (
<SearchContainer>
<Searchbar setSearch={setSearch} />
<p>SEARCHED: {search}</p>
</SearchContainer>
);
};
// search bar file
import { useRef, KeyboardEvent, useEffect } from "react";
type SearchProps = {
setSearch: React.Dispatch<React.SetStateAction<string>>;
};
export const Searchbar = ({ setSearch }: SearchProps) => {
// with useRef we don't have to reload the page for every input
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
setSearch(String(inputRef.current?.value));
localStorage.setItem("form", JSON.stringify(inputRef.current?.value)); // Issue here
};
// Enable search with the enter-key
const log = (e: KeyboardEvent): void => {
e.key === "Enter" ? handleClick() : null;
};
useEffect(() => {
const value = localStorage.getItem("form");
value ? JSON.parse(value) : "";
}, [setSearch]);
return (
<div>
<input
type="text"
autoComplete="off"
ref={inputRef}
placeholder="search game..."
onKeyDown={log}
/>
<button onClick={handleClick}>SEARCH</button>
</div>
);
};
I've been on this for a while now so any help is appreciated. (I am also trying to learn TS from scratch, but until I get to here...).

How to display dynamic text and images with sanity?

index.js export const
getServerSideProps = async () => {
const query = '*[_type == "product"]';
const products = await client.fetch(query);
const bannerQuery = '*[_type == "banner"]';
const bannerData = await client.fetch(bannerQuery);
return {
props: {products, bannerData}
}import { urlFor } from '../lib/client';
HeroBanner.JSX
const HeroBanner = ({heroBanner}) => {
return (
<div className='hero-banner-container'>
<div>
<p className='beats-solo'> {heroBanner.smallText} </p>
<h3> {heroBanner.midText} </h3>
<h1> {heroBanner.largeText1} </h1>
{console.log(heroBanner.largeText1)}
<img src={urlFor(heroBanner.image)} alt='Trending'
className='hero-banner-image' />
Client.js
import sanityClient from '#sanity/client';
import imageUrlBuilder from '#sanity/image-url';
export const client = sanityClient({
projectId:'*********',
dataset:'production',
apiVersion:'2022-12-04',
useCdn:true,
token:process.env.NEXT_PUBLIC_SANITY_TOKEN
});
const builder = imageUrlBuilder(client);
export const urlFor = (source) => builder.image(source);
This is the code here I tried. If normal text is in the p,h1, and h3 elements it works fine. However, the code I have there now is
{heroBanner.smallText} and that's where my problem starts. The text doesn't display when I use those values. I did change the values on sanity.io through localhost to what I want and nothing displays. P.S: When I console logged the bannerData I got the values I'm now trying to display.
sanity body text doesn't get displayed, you need to convert it using block-content
npm install #sanity/block-content-to-react --save
import SanityBlockContent from "#sanity/block-content-to-react";
then wrap the text you want to display in block-content.
As an example, post.body is what I want to display, it'll go something like this SanityBlockContent blocks={post.body} projectId="" dataset="*" />

React State and Arrays - Double rendering causes elements duplication

I am developing a fullstack blockchain Nft Dapp with React, Ethers and Solidity. I have made some routes and a mint page with wallet connection and mintbutton. Under the mint section there's the personal collection, where infos about property and metadata are retrieved from contract.
That's the collection component code.
import { useEffect, useState } from "react";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import Dino from "./Dino";
import { Contract, providers } from "ethers";
import { truncateAddress } from "./utils";
import { useWeb3React } from "#web3-react/core";
import { abi } from './abi';
export default function MyDinos() {
const { library, account} = useWeb3React();
const [dinosUri, setDinosUri] = useState([]);
const dinosTD = dinosUri.map((dino) => {
return (
<Dino key={dino} uriMetadata={dino} />
)
});
useEffect(() => {
if (!account) return;
if (!library) return;
const getDinosUri = async () => {
try {
const provider = await library.provider;
const web3Provider = new providers.Web3Provider(provider);
const signer = web3Provider.getSigner();
const contract = new Contract(process.env.REACT_APP_CONTRACT_ADDRESS, abi, signer);
const idArray = await contract.tokensOfWallet(account);
const idArrayFormatted = idArray.map(id => id.toNumber()).sort();
const uri = await contract.tokenURI(1);
const uriInPieces = uri.split("/");
const tmpDinos = [];
idArrayFormatted.forEach(id => {
const uriFormatted = `https://ipfs.io/ipfs/${uriInPieces[2]}/${id}`;
tmpDinos.push(uriFormatted);
//setDinosUri(prevArray => [...prevArray, uriFormatted])
});
setDinosUri(tmpDinos);
} catch (err) {
console.log(err);
}
}
getDinosUri();
return () => {
setDinosUri([]);
}
}, [library, account]);
return (
<>
{dinosUri.length > 0 &&
<div className='late-wow appear'>
<div className='svg-border-container bottom-border-light'></div>
<Container fluid className='sfondo-light py-4'>
<Container className='wow-container'>
<h2 className='wow appear mb-4 text-center'>Account: {truncateAddress(account)}</h2>
<h3 className='wow appear mb-4 text-center'>Dinos owned: {dinosUri.length} Dinos</h3>
<h4 className='wow appear mb-4 text-center'>Races won: COMING SOON</h4>
</Container>
</Container>
<div className='svg-border-container'></div>
<Container fluid className='sfondo-dark py-4'>
<Container>
<h2 className='mb-4'>My {dinosUri.length} Dinos</h2>
<Row className='my-5'>
{[...dinosTD]}
</Row>
</Container>
</Container>
</div>
}
</>
)
}
I managed to get the wanted result using a temporary variable tmpDinos to store the array of info, because if I used the commented method below //setDinosUri(prevArray => [...prevArray, uriFormatted]) on the first render I get the correct list, but if I change route and then get back to mint page, the collection is doubled. With the temp variable I cheated on the issue because it saves 2 times the same array content and it works good, but I don't think that's the correct React way to handle this issue. How can I get the previous code working? May it be a useEffect dependancy thing?
Thanks in advance for your attention.
A simple solution is to check if dinosUri is populated before setting its value.
if (dinosUri.length === 0) setDinosUri(prevArray => [...prevArray, uriFormatted])

How to get 5 day weather forecast using React Hooks and OpenWeatherMap API

So I want to be able to show the 5 day weather forecast for a chosen city, using the OpenWeatherMap API and React.
I've seen a few tutorials online but they all use Class components, I want to use mine using a functional Component and the UseState hook.
I have this working code which allows me to get the CURRENT weather,location name and displays a little weather icon.
I want to be able to get the info for 5 days, and put it into a list. Specificially I want the high, low, main, description and an icon, for each day.
I'm really inexperienced at making API calls so I'm struggling to figure it out. I have my API key, and I think my API call should look something like this
https://api.openweathermap.org/data/2.5/weather?q=${placename},IE&appid=${apiKey}&units=metric
where placename is a prop I pass to it, and IE is my country code.
I was looking at this tutorial which does what I want, but it uses class-based components instead. I can't figure out how to do it without using classes.
https://medium.com/#leizl.samano/how-to-make-a-weather-app-using-react-403c88252deb
If someone could show me how to do this, that would be much appreciated. Here is my current code that gets just the current temperature.
export default function Weather (props) {
// State
const [apiData, setApiData] = useState({});
const [state, setState] = useState('Belfast');
var placename = props.placeprop
// API KEY AND URL
const apiKey = process.env.REACT_APP_API_KEY;
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${placename},IE&appid=${apiKey}&units=metric`;
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((data) =>
setApiData(data),);
}, [apiUrl]);
return (
<div className="weather">
<div>
{apiData.main ? (
<div>
<img
src={`http://openweathermap.org/img/w/${apiData.weather[0].icon}.png`}
alt="weather status icon"
/>
<br/>
{apiData.name}
<br/>
{apiData.main.temp}° C
</div>
)
: (
<h1>Loading</h1>
)}
</div>
</div>
)
} ```
This is not a complete answer but I came across this question so I'm sharing what I have.
import React, {useEffect, useState} from 'react';
import css from './Weather.module.css';
function useOpenWeather ({apiKey, lat, lon, units = 'metric'}) {
const [apiData, setApiData] = useState(null);
const apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&appid=${apiKey}&units=${units}`;
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((data) => {
setApiData(data);
});
}, [apiUrl]);
return apiData;
}
function Weather ({lat, lon}) {
const weather = useOpenWeather({
apiKey: API_KEY
lat,
lon,
units: 'imperial'
});
return (
<div className={css.weather}>
{weather && weather.daily.slice(0, 5).map(d => (
<div>
<img
src={`http://openweathermap.org/img/w/${d.weather[0].icon}.png`}
alt={d.weather[0].main}
/>
<div>{d.temp.max} / {d.temp.min}</div>
</div>
))}
</div>
);
}
export default Weather;
.weather {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-gap: 16px;
margin: 16px;
}

React Hook useEffect() run continuously although I pass the second params

I have problem with this code
If I pass the whole pagination object to the second parameters of useEffect() function, then fetchData() will call continuously. If I only pass pagination.current_page so It will call only one time, but when I set new pagination as you see in navigatePage() function, the useEffect() does not call to fetchData() although pagination has changed.
How to solve this. Thank you very much!
Besides I do not want the use useEffect() call when first time component mounted because the items is received from props (It is fetch by server, this is nextjs project).
import React, {useEffect, useState} from 'react';
import Filter from "../Filter/Filter";
import AdsListingItem from "../AdsListingItem/AdsListingItem";
import {Pagination} from "antd-mobile";
import styles from './AdsListing.module.css';
import axios from 'axios';
const locale = {
prevText: 'Trang trước',
nextText: 'Trang sau'
};
const AdsListing = ({items, meta}) => {
const [data, setData] = useState(items);
const [pagination, setPagination] = useState(meta);
const {last_page, current_page} = pagination;
const fetchData = async (params = {}) => {
axios.get('/ads', {...params})
.then(({data}) => {
setData(data.data);
setPagination(data.meta);
})
.catch(error => console.log(error))
};
useEffect( () => {
fetchData({page: pagination.current_page});
}, [pagination.current_page]);
const navigatePage = (pager) => {
const newPagination = pagination;
newPagination.current_page = pager;
setPagination(newPagination);
};
return (
<>
<Filter/>
<div className="row no-gutters">
<div className="col-md-8">
<div>
{data.map(item => (
<AdsListingItem key={item.id} item={item}/>
))}
</div>
<div className={styles.pagination__container}>
<Pagination onChange={navigatePage} total={last_page} current={current_page} locale={locale}/>
</div>
</div>
<div className="col-md-4" style={{padding: '15px'}}>
<img style={{width: '100%'}} src="https://tpc.googlesyndication.com/simgad/10559698493288182074"
alt="ads"/>
</div>
</div>
</>
)
};
export default AdsListing;
The issue is you aren't returning a new object reference. You save a reference to the last state object, mutate a property on it, and save it again.
const navigatePage = (pager) => {
const newPagination = pagination; // copy ref pointing to pagination
newPagination.current_page = pager; // mutate property on ref
setPagination(newPagination); // save ref still pointing to pagination
};
In this case the location in memory that is pagination remains static. You should instead copy all the pagination properties into a new object.
const navigatePage = (pager) => {
const newPagination = {...pagination}; // shallow copy into new object
newPagination.current_page = pager;
setPagination(newPagination); // save new object
};
To take it a step further you really should be doing functional updates in order to correctly queue up updates. This is in the case that setPagination is called multiple times during a single render cycle.
const navigatePage = (pager) => {
setPagination(prevPagination => {
const newPagination = {...prevPagination};
newPagination.current_page = pager;
});
};
In the case of pagination queueing updates may not be an issue (last current page set wins the next render battle), but if any state updates actually depend on a previous value then definitely use the functional update pattern,

Resources