I've been trying to build a random quote app. So far I have this: https://codesandbox.io/s/nice-heyrovsky-4msq4?file=/src/App.js
In short, I fetch a quotes list from an API, and then try to display the quotes and the authors when the page loads, and every time I press the button. But there's a problem.
export default function App() {
useEffect(() => {
fetch(
"https://gist.githubusercontent.com/natebass/b0a548425a73bdf8ea5c618149fe1fce/raw/f4231cd5961f026264bb6bb3a6c41671b044f1f4/quotes.json"
)
.then((data) => data.json())
.then((quotes) => setAllQuotes(quotes));
}, []);
const [allQuotes, setAllQuotes] = useState([]);
// this is the array of all quote objects
const [quoteIndex, setQuoteIndex] = useState(0);
//this is the number used as array index
const [text, setText] = useState("This app is Broken");
// this is the quote text to be displayed
const [author, setAuthor] = useState("Press set twice");
// this is the quote author to be displayed
const [chosenQuote, setChosenQuote] = useState({});
// this is the selected quote object
function randomNumber() {
return Math.floor(Math.random() * allQuotes.length);
}
let handleClick = () => {
setQuoteIndex(randomNumber);
setText(chosenQuote.quote);
setAuthor(chosenQuote.author);
setChosenQuote(allQuotes[quoteIndex]);
// this is supposed to choose a random object within the array, and set text and quote state to the quote and author properties of the selected object
};
console.log(chosenQuote);
return (
<div id="quote-container">
<div id="quote-box">
<h1 id="text">{text}</h1>
<p id="author">{author}</p>
{/*when I press the button, it loads the next quote, but displays the previous quote, hence why nothing displays on first click */}
<button onClick={handleClick}>New Quote</button>
</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
If I try to load the quotes while the page loads, it gives me the "Cannot read property(quote) of undefined" error. If I try to load the quotes after the page has loaded by editing the javascript, then it works, but it displays the messages from the previously loaded object, not the current one. You can see the behavior in codesandbox.
So, can someone explain to me why this is happening and how I can fix it so that the app displays the quotes properly on load and then changes on click?
this is because setState from useState hook is asynchronous so you can do something like:
setChosenQuote(allQuotes[quoteIndex]);
setText(allQuotes[quoteIndex].quote);
setAuthor(allQuotes[quoteIndex].author);
Related
I have an application in react native where i'm developing a search feature like Instagram.
It is like if user stop typing show him his query result.
my current approach is messing up redux. And sometimes it returns same element multiple times or sometime random elements which are irrelevant of that query.
right now. I'm calling search api immediately as use start typing in searchbar.
here is code below of my component.
import { getSearchDataApi } from "../../api/search/search";
import { clearSearchData, setSearchData } from "../../redux/action/search";
const SearchScreen =(props)=>{
const [autoFocus,setAutoFocus] = useState(true)
const [keyWord,setKeyWord] = useState(null)
const [isLoading,setIsLoading] = useState(false)
const [isError,setIsError] = useState(false)
const [pageNumber,setPageNumber] = useState(1)
const [loadMore,setLoadMore] = useState(true)
const loadMoreDataFunc =()=>{
if (pageNumber <= props.totalSearchPage) {
setPageNumber(pageNumber+1)
}
else {
setLoadMore(false)
}
}
const searchData = async(keyWord)=>{
console.log(keyWord,pageNumber)
try {
setIsLoading(true)
var searchResponse = await getSearchDataApi(keyWord,pageNumber)
props.setSearchData(searchResponse.data)
setIsLoading(false)
}
catch (e) {
setIsError(true)
console.log("Error --- ", e.response.data.message)
showMessage({
message: e.response.data.message,
type: "danger",
});
}
}
return (
<View>
....
</View>
)
}
const mapStateToProps = (state)=>({
searchData: state.searchReducer.searchData,
totalSearchPage: state.searchReducer.totalSearchPage,
})
export default connect(mapStateToProps,{setSearchData,clearSearchData})(SearchScreen);
I will really every thankful to someone how can help me in fixing. Appreciation in advance!
GOAL :
The goal that i want to achieve is when user stop typing then i call searchAPI with the keyword he/she entered in searchBar that's all.
I have also tried setTimeOut but that made things more worse.
The best solution to your problem is to debounce the state variable that is responsible for the user input. This way, you can use the effect hook to watch for changes on the debounced variable, and call the search API if/when conditions for the search API variables are met.
Well, I have put some effort to solve it with setTimeout once again and i have done it by following code of snippet.
useEffect(()=>{
setPageNumber(1)
props.clearSearchData()
const delayDebounceFn = setTimeout(() => {
console.log(keyWord)
if (keyWord) {
searchData(keyWord)
}
}, 500)
return () => clearTimeout(delayDebounceFn)
},[keyWord])
You can use a setInterval to create a countDown starting from 2 to 0, or 3 to 0, put it a state.
whenever user types, onChange is called, the from the callback you reset the countDown.
using useEffect with the countDown as dependency, you can open the search result whenever the countdown reaches 0. (which means the user hasn't typed anything since 2s ago)
this might help for creating the countdown https://blog.greenroots.info/how-to-create-a-countdown-timer-using-react-hooks
I am trying to add a feature in my Next.js website that allows users to click a button to create a new group, then redirect them to an "Invite members" page that uses the auto generated group_id in the URL with dynamic routing. I am currently using Next.js's Router, but I feel like there is a better (or working) way to do this.
JS (within export default function groups ()):
const [num, setNum] = useState("");
const router = useRouter()
const createGroup = async () => {
const { data, error } = await supabase
.from("chatgroups")
.insert([
{
group_name: groupName,
creator: user.email,
description: groupDesc,
accepted_members: user.email + " ",
invited_members: ""
}
]);
if (error) {
console.log(error);
} else {
setNum("/groups/" + String(data[0].group_id));
router.push(num);
}
};
HTML (returned in same JS script):
<button type="button" className="bg-yellow-500 rounded px-12 py-2" onClick={()=> {
createGroup()
}} >Insert test group</button>
I have tried using the Router for both server and client and both did not recognize .push()
import { Router } from "next/dist/client/router";
import { Router } from "next/dist/server/router"; //neither of these recognized the method.
My goal is to execute the function createGroup(), in which the value of "data" will receive the auto-generated int id of the new SQL row. Then I want to use this id to redirect to "/groups/[id]", where [id] is the new ID. I tried using a Link with the const [num, setNum], but it performs the redirect BEFORE the new value is set. I am relatively new to Next.js, so I would appreciate any help with this.
Desired output:
Click button -> adds row to SQL table with group_id = 17.
Redirect user to "/groups/invite_members/17".
Edit: I have updated my main JS code above to use useRouter(), now it only works every second click.
The issue is that calling setNum does not update num immediately, as setting state is an asynchronous operation.
This means that on the first button click num will still have its default value ("") when router.push(num) is called, and only when clicking the button a second time will the num state have updated with the value set previously.
To fix it, you can set the value to a variable and use that in the router.push call instead.
const path = "/groups/" + String(data[0].group_id);
setNum(path);
router.push(path);
Thanks for checking this post.
I'm new to React and I have a question about fetching data before opening a modal.
I have a card list and when I click on a card, a modal pops up with the data of clicked item (handleModal function takes id of clicked item). And I want the data inside already without delay every time the modal pops up.
const [eachUser, setEachUser] = useState({});
const [isModalOn, setIsModalOn] = useState(false);
const loadDetailInfo = async id => {
if (isModalOn) {
return;
}
await axios
.get(`${API}/api/users/${id}`)
.then(res => setEachUser(res.data.data))
.catch(err => console.log(err));
};
const handleModal = id => {
if (!isModalOn) {
setEachUser('');
}
loadDetailInfo(id);
setIsModalOn(!isModalOn);
};
return (
<>
<CardList data={filteredUser} handleModal={handleModal} />
{isModalOn && <Modal data={eachUser} handleModal={handleModal}/>}
</>
);
But with this code, since it takes time to fetch data from the API, modal shows blank view at first and I don't know how to fix this.
I've tried useEffect and putting the loadDetailInfo function inside with the id state in a dependency array but I couldn't fix it for some reason..
It would be really appreciate if you give me some advice.Thank you for your time and advice in advance.
const searchQuery=document.querySelector('#searchQ').value
const p=document.querySelector('p')
const log= (e)=>{
e.preventDefault()
console.log(searchQuery)
p.innerHTML=`${searchQuery}`
fetchData()
}
This is the part of my code in the React app that gives me the error, in particular the first line. the #searchQ is a normal HTTML input field that should start a get request, and fetchData() is a normal fetch API function and it's working properly. that way it gives me this error:
TypeError: document.querySelector(...) is null
App
src/App.js:5
5 | let searchQuery=document.querySelector('#searchQ').value
when I remove the .value it runs normally but of course, it won't fetch the user's input. I tried let instead of const. Also I took the code in a vanilla js, HTML page and it logged successfully, but the same method doesn't work in react.
Any help?
It is considered as a top rated bad practice to use querySelector() or getElementById() except for some specific cases, however in this case you can get to work with useEffect as you need to wait for the component to mount.
useEffect(() => {
const searchQuery = document.querySelector("#searchQ").value;
console.log(searchQuery);
}, []);
A Better version would be
const [value, setValue] = useState("");
const pRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
pRef.current.innerHTML = value;
console.log(value);
};
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<>
<form onSubmit={handleSubmit}>
<input type="text" value={value} id="searchQ" onChange={handleChange} />
</form>
<p ref={pRef}></p>
</>
);
In react u don't directly load the components just as normal HTML. It uses the render() method and there querySelector method doesn't work.
Either use state to directly control/retrieve the value or use ref as #boxdob said.
How do I use setState to render individual data returned from API call. As the question implies... What I am getting instead is another array of data.
Here's the code:
const [likes, setLikes] = useState(0);
useEffect( async () => {
const query = await getDevsApi(); //This returns a collection of data I can map.
const likes = query.map(like => like.upvotes); //The problem here is, it returns an array of data... How do I get individual data for each user? and set it at as the current state in the setLikes() below.
setLikes(likes)
},[]);
I am still wrapping my head around reactjs. Can anyone explain the best approach for me?
How it looks...
UPDATED CODE
ver. 1.
const [likes, setLikes] = useState(0);
useEffect(() => {
//using an anonymous async function inside a useEffect
( async () => {
const query = await getDevsApi();
setLikes(query.map(like => like.upvotes));
})
(); //calling the anonymous function.
},[]);
ver. 2.
const [likes, setLikes] = useState(0);
useEffect(() => {
( async () => {
const query = await getDevsApi();
const likes = query.map(like => like.upvotes);
setLikes(likes);
})
();
},[]);
However, they both produce the same result.
The idea is to get the number of likes from the API and display them in this section
<span className={styles.iconLeft}>
<a href="#" onClick={addLike} ><small>{likes} Likes <FontAwesomeIcon className={styles.columnIcon} icon={faHeart} /></small> </a>
</span>
The {likes} is where the magic happens. The initial state was set to 0... and the useEffect will be used to display the real data gotten from the API. And Yes, I got the data BUT it's not distributed to each user as supposed, rather it's giving me an array and appending it to all the user.
See the screenshot above.
I hope I'm able to communicate clearly though.
I just pushed my project to vercel.
here is a branch of what I am having now... https://naijadev-4g9llmolb-badt0men.vercel.app/
And here is another branch of how I want it... https://naijadev-five.vercel.app
And yes this is my very first attempt working with Jamstack... using the now popular headless CMS (strapi)
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array. see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
But this won't be an issue. if you want to render individual data from an array you should use the map() method in the return block of a functional component where UI is rendered.
const [likes, setLikes] = useState([]);
useEffect( async () => {
const query = await getDevsApi();
const likes = query.map(like => like.upvotes);
setLikes(likes)
},[]);
return (
...
<span className={styles.iconLeft}>
{ likes?.map(like => <a href="#" onClick={addLike} ><small>{like} Likes
<FontAwesomeIcon className={styles.columnIcon} icon={faHeart} /></small> </a>
}
</span>
)