When i search for a country, The searchInput useState works fine and receives the value, problem is it doesn't update the country in the Dom immediately unless i remove the useEffect hook dependency array, And this causes too many re-renders, So how can i update the DOM when i search search, Here is my code.
const countryUrl = `https://restcountries.com/v2/name`;
const [searchInput, setSearchInput] = useState<string>("Nigeria");
const [countryData, setCountryData] = useState<object>([]);
const fetchCountry = (searchInput: any) => {
axios
.get(`${countryUrl}/${searchInput}?fullText=true`)
.then((res) => setCountryData(res.data[0]))
.catch((err) => console.log(err));
};
useEffect(() => {
fetchCountry(searchInput);
}, []);
const handleSubmit = (e: any) => {
e.preventDefault();
fetchWeather(searchInput);
fetchCountry();
};
<form onSubmit={handleSubmit}>
<input
onChange={(e) => setSearchInput(e.target.value)}
placeholder="Enter The Country"
type="text"
/>
<button type="submit">{<CiSearch />} </button>
</form>
Remove searchInput from fetchCountry = (searchInput: any) => {, it will look like fetchCountry = () => {
Reason is you have searchInput in useState so no need to pass as foo param
your fetchCountry will look like
const fetchCountry = () => { // removed params
axios
.get(`${countryUrl}/${searchInput}?fullText=true`)
.then((res) => setCountryData(res.data[0]))
.catch((err) => console.log(err));
};
Related
I am not able to implement onClick functionality on AsyncTypeahead to console log the user ID after I find the user. can someone please help. thanks
const SEARCH_URI = 'https://api.github.com/search/users';
const AsyncExample = () => {
const [isLoading, setIsLoading] = useState(false);
const [options, setOptions] = useState([]);
const handleSearch = (query) => {
setIsLoading(true);
fetch(`${SEARCH_URI}?q=${query}+in:login&page=1&per_page=50`)
.then((resp) => resp.json())
.then(({ items }) => {
const options = items.map((i) => ({
avatar_url: i.avatar_url,
id: i.id,
login: i.login,
}));
setOptions(options);
setIsLoading(false);
});
};
const filterBy = () => true;
return (
<AsyncTypeahead
filterBy={filterBy}
id="async-example"
isLoading={isLoading}
labelKey="login"
minLength={2}
onSearch={handleSearch}
options={options}
placeholder="Search for a Github user..."
/>
);
};
Try using onChange, which fires after a menu option has been selected:
<AsyncTypeahead
...
onChange={(selected) => {
console.log(selected[0]?.id);
}}
/>
Note that selected is always an array.
I want to do a movie search with the oMdb api using React Hooks.
The result is not as expected. I seem to break some React Hooks rule that I don't understand.
Here is the code.
HOOK TO SEARCH
The Hook inside of a store.
(If I use searchMovies('star wars') in a console.log I can see the result of star wars movies and series.)
import React, { useState, useEffect } from "react";
const useSearchMovies = (searchValue) => {
const API_KEY = "731e41f";
const URL = `http://www.omdbapi.com/?&apikey=${API_KEY}&s=${searchValue}`
// Manejador del estado
const [searchMovies, setSearchMovies] = useState([])
//Llamar y escuchar a la api
useEffect(() => {
fetch(URL)
.then(response => response.json())
.then(data => setSearchMovies(data.Search))
.catch((error) => {
console.Console.toString('Error', error)
})
}, []);
return searchMovies;
};
THE INPUT ON A SANDBOX
Here i have the input to search with a console log to see the result.
import React, { useState } from "react";
import searchMovies from "../store/hooks/useSearchMovies";
const Sandbox = () => {
const [search, setSearch] = useState('')
const onChangeHandler = e =>{
setSearch(e.target.value)
console.log('Search result', searchMovies(search))
}
const handleInput =()=> {
console.log('valor del input', search)
}
return (
<div>
<h1>Sandbox</h1>
<div>
<input type="text" value={search} onChange={onChangeHandler}/>
<button onClick={handleInput()}>search</button>
</div>
</div>
)
}
export default Sandbox;
Issue
You are breaking the rules of hooks by conditionally calling your hook in a nested function, i.e. a callback handler.
import searchMovies from "../store/hooks/useSearchMovies";
...
const onChangeHandler = e => {
setSearch(e.target.value);
console.log('Search result', searchMovies(search)); // <-- calling hook in callback
}
Rules of Hooks
Only call hooks at the top level - Don’t call Hooks inside loops,
conditions, or nested functions.
Solution
If I understand your code and your use case you want to fetch/search only when the search button is clicked. For this I suggest a refactor of your useSearchMovies hook to instead return a search function with the appropriate parameters enclosed.
Example:
const useSearchMovies = () => {
const API_KEY = "XXXXXXX";
const searchMovies = (searchValue) => {
const URL = `https://www.omdbapi.com/?apikey=${API_KEY}&s=${searchValue}`;
return fetch(URL)
.then((response) => response.json())
.then((data) => data.Search)
.catch((error) => {
console.error("Error", error);
throw error;
});
};
return { searchMovies };
};
Usage:
import React, { useState } from "react";
import useSearchMovies from "../store/hooks/useSearchMovies";
const Sandbox = () => {
const [search, setSearch] = useState('');
const [movies, setMovies] = useState([]);
const { searchMovies } = useSearchMovies();
const onChangeHandler = e => {
setSearch(e.target.value)
};
const handleInput = async () => {
console.log('valor del input', search);
try {
const movies = await searchMovies(search);
setMovies(movies);
} catch (error) {
// handle error/set any error state/etc...
}
}
return (
<div>
<h1>Sandbox</h1>
<div>
<input type="text" value={search} onChange={onChangeHandler}/>
<button onClick={handleInput}>search</button>
</div>
<ul>
{movies.map(({ Title }) => (
<li key={Title}>{Title}</li>
))}
</ul>
</div>
);
};
export default Sandbox;
I'm trying to re-render a component using useEffect and passing dependencies, but it isn't working. I'm quite new to react hooks so I think I might not be passing the correct dependency. I'm fetching some info and updating the state, however, when I passed the dependencies the re-render cycle does not happen.
Here is the code:
import React, { useRef, useState, useEffect } from "react";
import Loader from "../UI/loader/loader";
import axios from "axios";
import "./surveybox.css";
interface surveryAnswer {
id: number;
answers: string[];
}
const SurveyBox: React.FC = () => {
const [surveyUserAnswers, setSurveyUserAnswers] = useState<surveryAnswer>();
const [loading, setLoading] = useState(false);
const [numberOfAnswers, setNumberOfAnswers] = useState<number>();
const programmingQRef = useRef<HTMLSelectElement>(null);
const skillsQRef = useRef<HTMLSelectElement>(null);
const stateManagementQRef = useRef<HTMLSelectElement>(null);
const programmerTypeQRef = useRef<HTMLSelectElement>(null);
useEffect(() => {
axios
.get(`${DB_URL}/users-answers.json`)
.then((res) => {
const fetchedAnswers = [];
for (let item in res.data) {
fetchedAnswers.push({
...res.data[item],
id: item,
});
}
setNumberOfAnswers(fetchedAnswers.length);
})
.catch((err) => {});
}, [numberOfAnswers, setNumberOfAnswers]);
const onSubmitSurvey = (e: React.FormEvent): void => {
e.preventDefault();
setLoading((prevLoading) => !prevLoading);
const newAnswer = {
id: Math.random(),
answers: [
programmerTypeQRef.current!.value,
skillsQRef.current!.value,
stateManagementQRef.current!.value,
programmerTypeQRef.current!.value,
],
};
setSurveyUserAnswers(newAnswer);
axios
.post(`${DB_URL}/users-answers.json`, newAnswer)
.then((res) => {
setLoading((prevLoading) => !prevLoading);
})
.catch((error) => {
console.log(error);
setLoading((prevLoading) => !prevLoading);
});
};
return (
<div className="surveybox-container">
{loading ? (
<div className={"loader-holder"}>
<Loader />
</div>
) : (
<React.Fragment>
<h2>Quick survey!</h2>
<form action="submit" onSubmit={onSubmitSurvey}>
<label>favorite programming framework?</label>
<select ref={programmingQRef} name="programming">
<option value="React">React</option>
<option value="Vue">Vue</option>
<option value="Angular">Angular</option>
<option value="None of the above">None of the above</option>
</select>
<br></br>
<label>what a junior developer should have?</label>
<select ref={skillsQRef} name="skills">
<option value="Eagerness to lear">Eagerness to learn</option>
<option value="CS Degree">CS Degree</option>
<option value="Commercial experience">
Commercial experience
</option>
<option value="Portfolio">Portfolio</option>
</select>
<br></br>
<label>Redux or Context Api?</label>
<select ref={stateManagementQRef} name="state-management">
<option value="Redux">Redux</option>
<option value="Context Api">Context Api</option>
</select>
<br></br>
<label>Backend, Frontend, Mobile?</label>
<select ref={programmerTypeQRef} name="profession">
<option value="Back-end">back-end</option>
<option value="Front-end">front-end</option>
<option value="mobile">mobile</option>
</select>
<br></br>
<button type="submit">submit</button>
</form>
<p>answered by {numberOfAnswers} visitors</p>
</React.Fragment>
)}
</div>
);
};
I'm passing the two dependencies that are involved in the useEffect and changes but still not working.
Thank you!
Edited Solution after recommendation:
...
interface surveryAnswer {
id: number;
answers: string[];
}
const SurveyBox: React.FC = () => {
const [surveyUserAnswers, setSurveyUserAnswers] = useState<surveryAnswer>();
const [loading, setLoading] = useState(false);
const [numberOfAnswers, setNumberOfAnswers] = useState<number>(0);
const programmingQRef = useRef<HTMLSelectElement>(null);
const skillsQRef = useRef<HTMLSelectElement>(null);
const stateManagementQRef = useRef<HTMLSelectElement>(null);
const programmerTypeQRef = useRef<HTMLSelectElement>(null);
const fetchAnswersFromServer = () => {
axios
.get(`${DB_URL}/users-answers.json`)
.then((res) => {
const fetchedAnswers = [];
for (let item in res.data) {
fetchedAnswers.push({
...res.data[item],
id: item,
});
}
setNumberOfAnswers(fetchedAnswers.length);
})
.catch((err) => {});
};
const onSubmitSurvey = (e: React.FormEvent): void => {
e.preventDefault();
setLoading((prevLoading) => !prevLoading);
const newAnswer = {
id: Math.random(),
answers: [
programmerTypeQRef.current!.value,
skillsQRef.current!.value,
stateManagementQRef.current!.value,
programmerTypeQRef.current!.value,
],
};
setSurveyUserAnswers(newAnswer);
axios
.post(`${DB_URL}/users-answers.json`, newAnswer)
.then((res) => {
fetchAnswersFromServer();
setLoading((prevLoading) => !prevLoading);
})
.catch((error) => {
console.log(error);
setLoading((prevLoading) => !prevLoading);
});
};
return (...
You probably only want the HTTP call to be made once (when the component mounts), so use [] as the dependency list. The HTTP call doesn't depend on any other props or state, so this most likely what you want.
If you want it to run more often than that, then you'll need to decide what data changes that requires you to make the HTTP call again, and add that to the dependency list instead of numberOfAnswers or setNumberOfAnswers.
The problem is that your effect lists numberOfAnswers as a dependency, and it also updates numberOfAnswers when it runs. So, this is likely what's happening:
The component mounts. numberOfAnswers is undefined by default.
The effect runs for the first time.
The HTTP request returns, say with 10 answers. It calls setNumberOfAnswers(10)
The component rerenders with numberOfAnswers=10
The effect runs again, because numberOfAnswers changed.
The HTTP request returns, probably with the same 10 answers. It calls setNumberOfAnswers(10)
The component does not rerender again because the value hasn't changed.
Issue
numberOfAnswers doesn't appear to be a dependency as it's not referenced in the effect callback, and secondly, unconditionally calling setNumberOfAnswers to update the numberOfAnswers can likely create an infinite loop. I suspect this effect runs once on mount, updates the state and runs a second time, and updates state again to the same value and then never updates again since the state is the same value.
Solution
So I was thinking to re-render every time there is a new submission.
You can trigger the useEffect on the surveyUserAnswers being updated.
Additional suggestions:
No need to create an new array and push elements into it just to compute the number of answers. Use the res.data.length if an array, or Object.values(res.data).length if it's an object.
Don't toggle the loading state, just set to true when starting the asynchronous loading action, and use a finally block to clear the loading state regardless if the Promise resolved or rejected.
Code:
const SurveyBox: React.FC = () => {
const [surveyUserAnswers, setSurveyUserAnswers] = useState<surveryAnswer>();
const [loading, setLoading] = useState(false);
const [numberOfAnswers, setNumberOfAnswers] = useState<number>();
const programmingQRef = useRef<HTMLSelectElement>(null);
const skillsQRef = useRef<HTMLSelectElement>(null);
const stateManagementQRef = useRef<HTMLSelectElement>(null);
const programmerTypeQRef = useRef<HTMLSelectElement>(null);
useEffect(() => {
axios
.get(`${DB_URL}/users-answers.json`)
.then((res) => {
setNumberOfAnswers(res.data.length); // or Object.values(res.data).length
})
.catch((err) => {});
}, [surveyUserAnswers]);
const onSubmitSurvey = (e: React.FormEvent): void => {
e.preventDefault();
setLoading(true);
const newAnswer = {
id: Math.random(),
answers: [
programmerTypeQRef.current!.value,
skillsQRef.current!.value,
stateManagementQRef.current!.value,
programmerTypeQRef.current!.value,
],
};
setSurveyUserAnswers(newAnswer);
axios
.post(`${DB_URL}/users-answers.json`, newAnswer)
.then((res) => {
// handle any processing
})
.catch((error) => {
console.log(error);
// handle any other error processing
})
.finally(() => setLoading(false));
};
Alternative Solution
Define onSubmitSurvey async and handle all the asynchronous logic via awaits and completely remove the useEffect hook.
const onSubmitSurvey = async (e: React.FormEvent): void => {
e.preventDefault();
setLoading(true);
const newAnswer = {
id: Math.random(),
answers: [
programmerTypeQRef.current!.value,
skillsQRef.current!.value,
stateManagementQRef.current!.value,
programmerTypeQRef.current!.value,
],
};
setSurveyUserAnswers(newAnswer);
try {
await axios.post(`${DB_URL}/users-answers.json`, newAnswer);
const res = await axios.get(`${DB_URL}/users-answers.json`);
setNumberOfAnswers(res.data.length); // or Object.values(res.data).length
} catch(error) {
console.log(error);
// handle any other error processing
} finally {
setLoading(false);
}
};
I am building an application that fetches a player's details, using the input. But the api only allows fetching the details using player's id, hence I have to use another method to first get the id using player's name. But there is some problem getting the input. I also tried using e.target.value, but it isn't working
import React, { useEffect, useState } from 'react'
import HLTV from 'hltv';
// Getting player id using this fn.
const getPlayerIdByName = async (text) => {
return await HLTV.getPlayerByName({ name: text })
.then(res => res.id)
// .then(data => console.log(data))
.catch(err => console.log(err));
}
//Getting player stats using id obtained from above
const getPlayerStats = (playerId) => {
HLTV.getPlayerStats({ id: playerId })
.then(res => Object.entries(res))
}
const Search = () => {
const [name, setName] = useState('');
const [id, setId] = useState('');
useEffect(() => {
getPlayerIdByName(name)
.then(id => setId(id))
}, [name]);
const onChange = (e) => {
setName(e.target.value)
}
const onSubmit = (e) => {
e.preventDefault();
setName(name);
console.log(name)
}
return (
<div>
<form onSubmit={onSubmit} className="player">
<input type="text" value={name} placeholder="Enter Player's in game name" onChange={onChange} />
<button type="Submit" defaultValue="Search">Search</button>
</form>
</div>
)
}
export default Search;
I would refactor your code like this:
The main problem I see, is that you are using useEffect() to get the playerIdByName every time that name changes. Instead, just call that function inside the onSubmit handler. And instead of storing the id in state, store your stats instead.
Then, when you have stats in state, you can render them by maping the key value pairs.
import HLTV from 'hltv';
// Getting player id using this fn.
const getPlayerByName = async (text) => await HLTV.getPlayerByName({ name: text });
//Getting player stats using id obtained from above
const getPlayerStats = async (playerId) => await HLTV.getPlayerStats({ id: playerId });
const Search = () => {
const [name, setName] = useState('');
const [stats, setStats] = useState([]);
const onChange = (e) => {
setName(e.target.value);
};
const fetchStats = async () => {
const player = await getPlayerByName(name);
const stats = await getPlayerStats(player.id);
const statsEntries = Object.entries(stats);
setStats(statsEntries);
};
const onSubmit = async (e) => {
e.preventDefault();
try {
await fetchStats();
} catch (error) {
console.error(error);
}
};
return (
<div>
<form onSubmit={onSubmit} className="player">
<input
type="text"
value={name}
placeholder="Enter Player's in game name"
onChange={onChange}
/>
<button type="Submit" defaultValue="Search">
Search
</button>
</form>
{stats.length > 0 && (
<div>
{stats.map(([key, value]) => (
<p>
{key}: {value}
</p>
))}
</div>
)}
</div>
);
};
export default Search;
I try to training in react and want to make a form who call the api marvel when submitted with the current input and display the name + description of the character search.
The Api call is ok but when i submit the form nothing show any advice?
import React, { Component, useEffect, useState } from 'react'
import axios from 'axios'
const SearchEngine = React.forwardRef((props, ref) => {
const [asked, setAsked] = useState([]);
const [characterInfos, setCharacterInfos] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(true);
const [inputs, setInputs] = useState('');
const handleChange = (event) => {
setInputs(event.target.value);
console.log(inputs);
}
const getCharacters = (inputs) => {
setSearchTerm(inputs)
axios
.get(`https://gateway.marvel.com:443/v1/public/characters?name=${searchTerm}&apikey=XXX`)
.then(response => {
console.log(searchTerm)
console.log(response)
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name)
response.data.data.results.map((item) => {
return characterInfos.push(item.name)
})
localStorage.setItem(characterInfos, JSON.stringify(response.data))
if (!localStorage.getItem('marvelStorageDate')) {
localStorage.setItem('marvelStorageDate', Date.now());
}
})
.catch(error => {
console.log(error);
})
}
return (
<div className="search-container">
<h1>Character Infos</h1>
<form onSubmit={getCharacters}>
<input
type="text"
placeholder="Search"
value={inputs}
onChange={handleChange}
/>
<input type="submit" value="Envoyer" />
</form>
<ul>
<li>{characterInfos.name}</li>
</ul>
</div>
)
})
export default React.memo(SearchEngine)
Thanks for your help. Any to advice to show a list of all the character and make a search filter who work with minimum 3 characters?
getCharacters is fired with form submit event as param. You are assuming that is getting inputs from the state wrongly:
const getCharacters = event => {
event.preventDefault() // Prevent browser making undesired form native requests
// setSearchTerm(inputs); // Not sure what are you trying here but, again, inputs is a form submit event
axios
.get( // use searchValue as query string in the url
`https://gateway.marvel.com:443/v1/public/characters?name=${searchValue}&apikey=XXX`
)
.then(response => {
console.log(searchTerm);
console.log(response);
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name);
response.data.data.results.map(item => {
return characterInfos.push(item.name);
});
localStorage.setItem(characterInfos, JSON.stringify(response.data));
if (!localStorage.getItem("marvelStorageDate")) {
localStorage.setItem("marvelStorageDate", Date.now());
}
})
.catch(error => {
console.log(error);
});
};