Unable to use axios post request response outside of function - javascript

Using React & Axios. My post request response is an array of arrays, the second element of nested array is string I am trying to load onto a div through using map. Error is 'undefined is not iterable' I am trying to use useState to use the array outside of post request. The entire section opens with useState via a button and by default is closed/not loaded. There is also a user input which the post request uses to get it data, all of that works fine. I am just unable to map the string from the array into a div. I tried to use window.var to access it but this was unsuccessful as well. Appreciate any help!
import './Turnersdiscoverybody.css'
import axios from 'axios'
import { useState, useEffect } from 'react';
import React, { Component } from 'react'
import Turnershomenav from "../../../components/homepage/homepage-components/Turnershomenav.js";
import orangegtr1 from './turnersdiscovery-images/orangegtr-1.jpg'
import searchicon from './turnersdiscovery-images/searchicon.png'
export default function Turnersdiscoverybody() {
const [showSearchForm, setShowSearchForm] = useState('noForm')
const [input, setInput] = useState['']
//functions for opening and closing search feature
const handleClick = () => {
setShowSearchForm('showForm')
}
const handleClickBack = () => {
setShowSearchForm('noForm')
}
//axios post request starts
//function that handles searching the documents with the user input, using axios
const handleSearch = (e) => {
let userQuery = document.getElementById('userInput').value
e.preventDefault()
axios.post(`http://localhost:8081/getDocumentdata/${userQuery}`)
.then(res => {
setInput(res.data)
console.log(res.data)
})
}
//axios post request ends
return (
<div>
<div className="turnersdiscoverynav">
<Turnershomenav />
</div>
<div className='backgroundimg-container'>
<img src={orangegtr1} alt="background-img" className='turnersdiscovery-backgroundimg'></img>
</div>
{showSearchForm === 'showForm' &&
<>
<img className="img-btn-search" alt="search icon" src={searchicon} onClick={handleClickBack}></img>
<div className='form-search-container'>
<div className='form-search-container-top'>
<input
id="userInput"
required
type="text"
placeholder='enter your query'
></input>
<button onClick={handleSearch}>hello click me for stuff</button>
</div>
<div className='form-search-container-bottom'>
<div className='form-search-container-bottom-content'>
{input.map((data) => (
<div>{data[1]}</div>
)
)}
</div>
</div>
</div>
</>
}
{showSearchForm === "noForm" && <img className="img-btn-search" alt="search icon" src={searchicon} onClick={handleClick}></img>}
</div>
)
}

You have some errors in your code:
//1 => const [input, setInput] = useState([])
//2 => check if inputs is not empty then map!
Now your code must be like this:
export default function Turnersdiscoverybody() {
const [showSearchForm, setShowSearchForm] = useState('noForm')
const [input, setInput] = useState([]);
const [empty, setEmpty] = useState(false);
//functions for opening and closing search feature
const handleClick = () => {
setShowSearchForm('showForm')
}
const handleClickBack = () => {
setShowSearchForm('noForm')
}
//axios post request starts
//function that handles searching the documents with the user input, using axios
const handleSearch = (e) => {
let userQuery = document.getElementById('userInput').value
e.preventDefault()
axios.post(`http://localhost:8081/getDocumentdata/${userQuery}`)
.then(res => {
if(res && res.length > 0){
setInput(res.data)
setEmpty(false)
}else{
setEmpty(true)
}
console.log(res.data)
})
}
return (
<div>
<div className="turnersdiscoverynav">
<Turnershomenav />
</div>
<div className='backgroundimg-container'>
<img src={orangegtr1} alt="background-img"
className='turnersdiscovery-backgroundimg'></img>
</div>
{showSearchForm === 'showForm' &&
<>
<img className="img-btn-search" alt="search icon" src={searchicon} onClick={handleClickBack}></img>
<div className='form-search-container'>
<div className='form-search-container-top'>
<input
id="userInput"
required
type="text"
placeholder='enter your query'
></input>
<button onClick={handleSearch}>hello click me for stuff</button>
</div>
<div className='form-search-container-bottom'>
<div className='form-search-container-bottom-content'>
{empty ? <p>Nothing Found </p> :
<>
{input.length > 0 && input.map((data, index) => (
<div>{data[1]}</div>
)
)}
</>
}
</div>
</div>
</div>
</>
}
{showSearchForm === "noForm" && <img className="img-btn-search" alt="search icon" src={searchicon} onClick={handleClick}></img>}
</div>
)
}

const [input, setInput] = useState['']
should be
const [input, setInput] = useState('')
P.s. if you run into more issues, a minimal code reproduction would be good, somewhere like codepen or codesandbox.

const [input, setInput] = useState['']
should have been
const [input, setInput] = useState([])

Related

Material UI Pagination

I don't understand why my page can't recognize other pages when I click (for example on page 2, the same page appears again and again)
This is in MealNew.js component:
import React, {useEffect, useState } from "react";
import './MealNew.css';
import Card from "../UI/Card";
import AppPagination from "./AppPagination";
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query,setQuery] = useState('');
const[page,setPage] = useState(9);
const[numberOfPages,setNumberOfPages]= useState(10);
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=991fbfc719c743a5896bebbd98dfe996&page=${page}`;
fetch (link)
.then ((response)=> response.json())
.then ((data) => {
setData(data.results)
setNumberOfPages(data.total_pages)
const elementFood = data?.map((meal,key) => {
return (<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image}
alt='e-meal'/>
</div> )
})
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
}
useEffect(()=> {
handleClick();
},[page])
return (
<Card className="meal">
<form onSubmit={handleSubmit}>
<input
className="search"
placeholder="Search..."
value={query}
onChange={(e)=>setQuery(e.target.value)}/>
<input type='submit' value='Search'/>
</form>
<li className="meal">
<div className = 'meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination
setPage={setPage}
pageNumber={numberOfPages}
/>
</div>
</li>
</Card>
) }
export default MealNew;
This is in AppPagination.js component:
import React from "react";
import { Pagination } from "#mui/material";
const AppPagination = ({setPage,pageNumber}) => {
const handleChange = (page)=> {
setPage(page)
window.scroll(0,0)
console.log (page)
}
return (
<div >
<div >
<Pagination
onChange={(e)=>handleChange(e.target.textContent)}
variant="outlined"
count={pageNumber}/>
</div>
</div>
)
}
export default AppPagination;
Thanks in advance, I would appreciate it a lot
The only error I am getting in Console is this:
Line 64:3: React Hook useEffect has a missing dependency: 'handleClick'. Either include it or remove the dependency array react-hooks/exhaustive-deps
You are not following the spoonacular api.
Your link looks like this:
https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&page=${page}
I checked the spoonacular Search Recipes Api and there's no page parameter you can pass. You have to used number instead of page.
When you receive response from the api, it returns the following keys: offset, number, results and totalResults.
You are storing totalResults as totalNumberOfPages in state which is wrong. MUI Pagination count takes total number of pages not the total number of records. You can calculate the total number of pages by:
Math.ceil(totalRecords / recordsPerPage). Let say you want to display 10 records per page and you have total 105 records.
Total No. of Pages = Math.ceil(105/10)= 11
Also i pass page as prop to AppPagination component to make it as controlled component.
Follow the documentation:
Search Recipes
Pagination API
Complete Code
import { useEffect, useState } from "react";
import { Card, Pagination } from "#mui/material";
const RECORDS_PER_PAGE = 10;
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const [numberOfPages, setNumberOfPages] = useState();
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&number=${page}`;
fetch(link)
.then((response) => response.json())
.then((data) => {
setData(data.results);
const totalPages = Math.ceil(data.totalResults / RECORDS_PER_PAGE);
setNumberOfPages(totalPages);
});
};
const elementFood = data?.map((meal, key) => {
return (
<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image} alt='e-meal' />
</div>
);
});
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
};
useEffect(() => {
handleClick();
console.log("first");
}, [page]);
return (
<Card className='meal'>
<form onSubmit={handleSubmit}>
<input className='search' placeholder='Search...' value={query} onChange={(e) => setQuery(e.target.value)} />
<input type='submit' value='Search' />
</form>
<li className='meal'>
<div className='meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination setPage={setPage} pageNumber={numberOfPages} page={page} />
</div>
</li>
</Card>
);
};
const AppPagination = ({ setPage, pageNumber, page }) => {
const handleChange = (page) => {
setPage(page);
window.scroll(0, 0);
console.log(page);
};
console.log("numberOfPages", pageNumber);
return (
<div>
<div>
<Pagination
page={page}
onChange={(e) => handleChange(e.target.textContent)}
variant='outlined'
count={pageNumber}
/>
</div>
</div>
);
};
export default MealNew;

Why my screen goes white on calling API for first time but it works on second time

As I mentioned in title my screen goes white on calling API for first time but when I refresh and inputs the value it works properly
on console the error is : Uncaught TypeError: Value.map is not a function
const [data, setData] = useState({});
const [user, setUser] = useState('');
const [Value, setValue] = useState('');
const APIurl = async () => {
await axios.get(`https://api.github.com/users/${user}`).then((response) => {
setData(response.data);
console.log(response.data);
});
};
const APIurl1 = () => {
axios
.get(`https://api.github.com/users/${user}` + `/repos`)
.then((response) => {
setValue(response.data);
console.log(response.data);
});
};
const handleKey = (e) => {
if (e.key === 'Enter') {
APIurl();
APIurl1();
}
};
return (
<div className="bbody">
<input
id="search-bar"
onChange={(event) => setUser(event.target.value)}
placeholder="Enter User"
type="text"
onKeyPress={handleKey}
/>
{!data.name ? (
<div>
<p className="errorMsg">No Data Found</p>
</div>
) : (
<div className="inner-img">
<img src={data.avatar_url} alt="" />
<h2>{data.name}</h2>
<p>{data.bio}</p>
<div id="repos">
{Value.map((value) => {
return (
<a key={value.id} href={value.html_url} target="_blank">
<button id="repos-link">{value.name}</button>
</a>
);
})}
</div>
</div>
)}
</div>
);
I m trying to get Github profile of a person by inputting name in the input field and extracting the info from the API.
Because the initial state of Value is an empty string. Nothing is there, so there's nothing to map. When you use setValue after typing in the input its state is updated to reflect whatever you've typed, so only then can you map over Value.
You should check if there is Value, add Value && like so:
{Value &&
Value?.map((value) => {
return (
<a key={value.id} href={value.html_url} target="_blank">
<button id="repos-link">{value.name}</button>
</a>
);
})}

React how to make a call to an API and display an array in an array of objects PokeAPI

I need help with a project i am working on, i want to be able to display the moves of a pokemon i type in, i can console.log an array of objects that have smaller arrays inside that i want to grab and display, how can i do so? ive tried the cluster of a method getPokemonNames which is supposed to get the names of the moves of the pokemon but this is where i couldnt think anymore.
import React, {useState} from 'react';
import PokemonName from './PokemonName';
import axios from 'axios';
function App() {
const [pokemon, setPokemon] = useState([])
const [loading, setLoading] = useState(false)
const [moves, setMoves] = useState([])
const getPokemon = async (name) =>{
setLoading(true);
const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${name}`)
const data = response.data;
setPokemon(data);
setMoves(data.moves);
setLoading(false);
console.log(data)
getPokemonNames(data.moves)
// const pokemonMovesAmount = pokemon.moves.map
}
const getPokemonNames = (data) =>{
console.log(data);
data.move.name.map((moves, key)=>(
<div key={key}>
<span>{moves.moves.name}</span>
</div>
))
}
return (
<>
<PokemonName getPokemon={getPokemon}/>
<div>
{!loading && pokemon ? (<div>
</div>): null}
<img src={pokemon.sprites?.front_default}/>
<div className="container">
{getPokemonNames}
</div>
</div>
</>
);
}
export default App;
this is the pokemon name component
import React, {useState} from 'react'
const onSubmit =(e) =>{
e.preventDefault();
}
export default function PokemonName(props) {
const [search, setSearch] = useState('');
return (
<div>
<div>
<h1>{search}</h1>
<form onSubmit={onSubmit}>
<input onChange={e => setSearch(e.target.value)} type ="text" placeholder="Search for Pokemon"></input>
<button onClick={(e) => props.getPokemon(search)}>Search</button>
</form >
</div>
</div>
)
}
EDIT, this shows a bit more about the data i get back after searching up the pokemon mew
First step, making sure you pass the parameter data
const [moves, setMoves] = useState([])
// since you set setMoves to be data.moves second step you can iterate over data only
return (
<>
<PokemonName getPokemon={getPokemon}/>
<div>
{!loading && pokemon ? (<div>
</div>): null}
<img src={pokemon.sprites?.front_default}/>
<div className="container">
{getPokemonNames(moves)}
</div>
</div>
</>
);
Second step, tweak your getPokemon method
const getPokemonNames = (data) => {
return data.map(moveD, key)=>(
<div key={key}>
<span>{moveD.move.name}</span>
</div>
))
}
There is an mistake in your getPokemonNames . You are trying to do data.move.name.map which means you are mapping "move" from inside "data". You need to map "moves" array. Here is how you can do it.
const getPokemonNames = (data) =>{
data.moves.map(item, key)=>(
<div key={key}>
<span>{item.move.name}</span>
</div>
))
}

React Loading Msg

I am working on a react app that shortens a URL after a button is clicked. I am having a hard time figuring out the best logic to do so. The loading message does not show then when I click the button. The loading button appears and then disappears when the message loads.
The current behavior with the code below is the following.
When it renders "Loading..." does not show.
I click my "Shorten it" button and "Loading..." shows.
The shortened url appears below the "Loading..." button.
Shorten.js
import { useEffect, useState } from "react";
const Shorten = () => {
const [shortLink, setShortLink] = useState(null);
const [isPending, setIsPending] = useState(false);
const [input, setInput] = useState('example.org/very/long/link.html');
const url = "https://api.shrtco.de/v2/shorten?url=";
const fullUrl = (url.concat(input));
console.log(fullUrl);
useEffect(() => {
fetch(fullUrl)
.then(res => {
return res.json();
})
.then(data => {
setShortLink(data.result.short_link);
// setIsPending(false);
})
}, [fullUrl ])
// input
// value={input}
const loadMsg = () =>{
setIsPending(true);
}
return (
<main>
<section className="purple-card">
<input onInput={e => setInput(e.target.value)} type="text" placeholder="Shorten a link here..." className="shorten-input"/>
<button className="shorten-it" onClick={() => loadMsg()}>Shorten It!</button>
</section>
<section className="white-card">
{isPending && <div className="loading-text">Loading...</div>}
{shortLink && <div className="shorten-text">{shortLink}</div>}
<hr></hr>
<button className="shorten-it" >Copy</button>
</section>
</main>
);
}
export default Shorten;
There seem to be a few issues here. The reason the loading message and the shortened URL display at the same time is that their render conditions are not mutually exclusive. Fixing that is as simple as not showing the shortened URL while the component is loading.
Also something that can cause issues is that the "Shorten It!" button does not control actually performing the shortening action, it just sets the loading (pending) state to true. The shortening action runs whenever the input's value changes. Basically the loading state and the shortening action are fairly independent of each other.
To fix this you should only run the shortening action when the user clicks the button and at that point set the isPending state to true at the same time (then set back to false when done).
Instead of useEffect, you can just use a function that gets called when the button is clicked.
For example:
import { useState, useCallback } from "react";
const Shorten = () => {
const [shortLink, setShortLink] = useState(null);
const [isPending, setIsPending] = useState(false);
const [input, setInput] = useState('example.org/very/long/link.html');
const shortenUrl = useCallback(() => {
setIsPending(true);
const baseUrl = "https://api.shrtco.de/v2/shorten?url=";
const fullUrl = baseUrl + input;
fetch(fullUrl)
.then(res => res.json())
.then(data => {
setShortLink(data.result.short_link)
})
.finally(() => setIsPending(false));
}, [input]);
return (
<main>
<section className="purple-card">
<input onInput={e => setInput(e.target.value)} type="text" placeholder="Shorten a link here..." className="shorten-input"/>
<button className="shorten-it" onClick={shortenUrl}>Shorten It!</button>
</section>
<section className="white-card">
{isPending && <div className="loading-text">Loading...</div>}
{!isPending && shortLink && <div className="shorten-text">{shortLink}</div>}
<hr></hr>
<button className="shorten-it" >Copy</button>
</section>
</main>
);
}
export default Shorten;
Alternatively, to make the loading/result more clearing mutually exclusive, you can define a single variable with a value of either the loading message, the result URL, or nothing. For example:
const result = useMemo(() => {
if (isPending) {
return <div className="loading-text">Loading...</div>;
}
if (shortLink) {
return <div className="shorten-text">{shortLink}</div>;
}
return null;
}, [isPending, shortLink]);
Then render like so:
<section className="white-card">
{result}
</section>

React Hooks - useEffect still being called even when object is empty

I have a question on React Hooks. This is a sample of my code :-
import React, { useState, useEffect } from "react";
import Card from "./Card";
const CardsBoard = () => {
useEffect(() => {
doRatingClickProcessing()
}, [ratingObj])
const doRatingClickProcessing = () => {
const { player, title, rating } = ratingObj
}
return (
<div className="container-fluid justify-content-center">
<div className="row">
<div className="col-md-6">
<Card
cardInfo={player1Card}
player={1}
showCard={visiblePl1}
clickableRatings = {clickableRatings}
onClick={ratingObj => setRatingObj(ratingObj)}
/>
</div>
<div className="col-md-6">
<Card
cardInfo={player2Card}
player={2}
showCard={visiblePl2}
clickableRatings = {clickableRatings}
onClick={ratingObj => setRatingObj(ratingObj)}
/>
</div>
</div>
)}
</div>
)
}
export default CardsBoard
Then in the card component I am returning the ratingObj successfully when the user clicks on a rating.
In the Card Component I have something like this:-
<div
className="col-md-2 text-left card-rating-color"
onClick={() =>
onClick({
player: player,
title: row[0].title,
rating: row[0].rating,
})
}
>
{row[0].rating}
</div>
However I am puzzled why useEffect() is triggered even when the Card component is loaded, and ratingObj is still empty. Shouldn't it be triggered only if the ratingObj is filled up?
Thanks for your help and time
useEffect will call at least once. it doesn't matter either your object is updating or not because when you write
useEffect(()=>{
},[ratingObj]);
In above code you are passing object into square brackets right. That means you are mentioning dependencies as e second parameter and empty [] in argument list will call once at least. After that, it depends on ratingObj that you have passed in.
import React, {useState,useMemo} from 'react';
const App = () => {
const [name, setName] = useState('');
const [modifiedName, setModifiedName] = useState('');
const handleOnChange = (event) => {
setName(event.target.value);
}
const handleSubmit = () => {
setModifiedName(name);
}
const titleName = useMemo(()=>{
console.log('hola');
return `${modifiedName} is a Software Engineer`;
},[modifiedName]);
return (
<div>
<input type="text" value={name} onChange={handleOnChange} />
<button type="button" onClick={handleSubmit}>Submit</button>
<Title name={titleName} />
</div>
);
};
export default App;
const Title = ({name}) => {
return <h1>{name}</h1>
}

Categories