Read data from request which just finished - javascript

If user type id in input, I'd like to fetch post by specific id typed by user. If not, I'd like to fetch whole array of posts, and then get some id from fetched data. I know it doesn't make sense, but it just only for tests.
It doesn't work, cause useState works asynchronously. I tried few solutions, but all of them looked very ugly.
LIVE DEMO
I received an error:
Cannot read properties of undefined (reading 'id')
Cause setPosts hasn't set yet.
What is the best, clear way to handle this case?
import { useState } from "react";
export default function App() {
const [id, setId] = useState("");
const [post, setPost] = useState(null);
const [posts, setPosts] = useState([]);
const fetchPost = async (id) => {
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
setPost(res.data);
};
const fetchPosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
setPosts(res.data);
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
await fetchPosts();
await fetchPost(posts[0].id);
}
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => setId(e.target.value)} />
<button type="submit">submit</button>
</form>
</div>
);
}

You can treat fetchPosts as a side-effect and wrap fetchPost(posts[0].id) in a useEffect dependant on posts.
Or just use the result directly in onSubmit() (presuming you don't need posts for something else).
const fetchPosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
// setPosts(res.data); // this state is transitory and not used directly by the render
return res.data;
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
const posts = await fetchPosts(); // Only used as part of submit event?
await fetchPost(posts[0].id);
}
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => setId(e.target.value)} />
<button type="submit">submit</button>
</form>
<div>{(post && post.title) || "No post yet"}</div>
</div>
);

Just like you said useState works asynchronously , if you want to do something after mutating it you will have to use useEffect and set posts as its arguments , now whenever the posts get mutated your funcion will be run and the first index of array will be sent to the fetchPost(id),
import axios from "axios";
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [id, setId] = useState("");
const [post, setPost] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
if (posts.length) {
fetchPost(posts[0].id);
}
}, [posts]);
const fetchPost = async (id) => {
console.log(`fetching ${id}`);
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
console.log(res.data);
setPost(res.data);
};
const fetchPosts = async () => {
console.log(`fetching all posts`);
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
setPosts(res.data);
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
await fetchPosts();
// res = await fetchPost(posts[0].id); we dont need it here it defined in useffect function
}
};
const setDefaultId = (e) => {
setId(e.target.value);
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => setDefaultId(e)} />
<button type="submit">submit</button>
</form>
</div>
);
}
Also consider never to update state directly in your return function it will cause performance issues

The problem is in the method "fetchPost". Inside this method, you have two variables with the same name. "id" from the state hook, and "id" from the function parameter.
You can solve the problem changing one of those variables names.
One more thing, if "id" doesn't have value, your way to get the first post won't work because the await method won't wait to the change of the state.
I have edit a bit the code to solve both problems.
import { useState } from 'react';
import axios from 'axios';
export default function App() {
const [id, setId] = useState('');
const [post, setPost] = useState(null);
const fetchPost = async (idProp) => {
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${idProp}`,
);
setPost(res.data);
};
const fetchPosts = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
await fetchPost(res.data[0].id);
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
await fetchPosts();
}
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => {
setId(e.target.value);
}} />
<button type="submit">submit</button>
</form>
</div>
);
}
I hop I've helped you.

Related

State does not get updated in functional component in React for the first time after axios call

Question : "detectLanguageKey" is getting updated only after selecting the language from the dropdown twice.
When I select the option from the dropdown first time, detectLanguageKey is still "", and gets updated only after selecting the option second time.
Can you please explain why ? I have tried using async await and callbacks as well.
import React, { useState, useEffect } from "react";
import axios from "axios";
function SearchBar() {
const [inputText, setInputText] = useState("");
const [detectLanguageKey, setdetectedLanguageKey] = useState("");
const [selectedLanguageKey, setLanguageKey] = useState("");
const [languagesList, setLanguagesList] = useState([]);
const [resultText, setResultText] = useState("");
const getLanguageSource = () => {
axios
.post(`https://libretranslate.de/detect`, {
q: inputText,
})
.then((response) => {
setdetectedLanguageKey(response.data[0].language);
});
};
useEffect(() => {
axios.get("https://libretranslate.de/languages").then((res) => {
setLanguagesList(res.data);
console.log("languagesList", languagesList);
});
}, [inputText]);
const languageKey = (selectedLanguage) => {
setLanguageKey(selectedLanguage.target.value);
};
const translateText = async () => {
await getLanguageSource();
let data = {
q: inputText,
source: detectLanguageKey,
target: selectedLanguageKey,
};
axios.post(`https://libretranslate.de/translate`, data).then((response) => {
setResultText(response.data.translatedText);
});
};
return (
<div>
<textarea
rows="10"
cols="80"
onChange={(e) => setInputText(e.target.value)}
placeholder="Type text to translate.."
></textarea>
<textarea
rows="10"
cols="80"
placeholder="Your translated text will be here..."
value={resultText}
disabled={true}
></textarea>
{languagesList.length > 0 && (
<select onChange={languageKey} name="selectedLanguageKey">
<option>Please Select Language..</option>
{languagesList.map((lang) => {
return <option value={lang.code}>{lang.name}</option>;
})}
</select>
)}
<button
class="submit-btn"
onClick={(e) => {
translateText();
}}
>
Submit
</button>
</div>
);
}
Change translateText function to this
const translateText = async () => {
const detectedLanguageKey = await getLanguageSource();
const data = {
q: inputText,
source: detectedLanguageKey,
target: selectedLanguageKey,
};
axios.post(`https://libretranslate.de/translate`, data).then((response) => {
setResultText(response.data.translatedText);
});
};
Change getLanguageSource function to this
const getLanguageSource = async () => {
const response = await axios
.post(`https://libretranslate.de/detect`, {
q: inputText,
})
return response.data[0].language;
};
Remove inputText from the dependency array of the useEffect.
Remove const [detectLanguageKey, setdetectedLanguageKey] = useState("");
There were a few problems in your code.
First, inside translateText, you are awaiting a function that does not return a promise i.e. getLanguageSource.
Secondly, even if getLanguageSource returned a promise, you are expecting setdetectedLanguageKey inside getLanguageSource to take effect immediately. State updates are not instantaneous.

How to combine these two functions so one waits until it's finished and then runs the other

The two functions in question:
const handleSubmit = async (e) => {
e.preventDefault();
console.log(songLink)
const newSong = {
songName,
songLink,
userId
};
const song = await dispatch(postSong(newSong))
.catch(async (res) => {
const data = await res.json()
if (data && data.errors) setErrors(data.errors)
})
reset();
};
const uploadSong = (files) => {
console.log(files[0])
const formData = new FormData()
formData.append('file', songSelected)
formData.append('upload_preset', 'd3gthd7l')
Axios.post("https://api.cloudinary.com/v1_1/dyhfkvy6u/video/upload", formData).then((response) => {
console.log(response.data.url, 'responseeee')
setSongLink(response.data.url)
})
}
I need the uploadSong function to finish the upload so I can get the response.data.url and save it to a variable, THEN handle the submit and add the variable to my database when creating the song. I'm not sure if it's something small or if I'm completely missing a concept. Should I return the url and then await the function?
The entire file:
import { useState } from "react";
import { useDispatch } from "react-redux";
import { postSong } from "../../store/song";
import { useSelector } from "react-redux";
// import { Image, Audio } from 'cloudinary-react'
import Axios from 'axios'
const SongForm = () => {
const dispatch = useDispatch();
const [songName, setSongName] = useState("");
const [songLink, setSongLink] = useState("");
const [errors, setErrors] = useState([]);
const [songSelected, setSongSelected] = useState("")
const [url, setUrl] = useState('')
const reset = () => {
setSongName("");
setSongLink("");
// setAlbumName('');
// setArtistName('')
};
const user = useSelector((state) => state.session.user);
const userId = user?.id
const handleSubmit = async (e) => {
e.preventDefault();
console.log(songLink)
const newSong = {
songName,
songLink,
userId
};
const song = await dispatch(postSong(newSong))
.catch(async (res) => {
const data = await res.json()
if (data && data.errors) setErrors(data.errors)
})
reset();
};
const uploadSong = (files) => {
console.log(files[0])
const formData = new FormData()
formData.append('file', songSelected)
formData.append('upload_preset', 'd3gthd7l')
Axios.post("https://api.cloudinary.com/v1_1/dyhfkvy6u/video/upload", formData).then((response) => {
console.log(response.data.url, 'responseeee')
setSongLink(response.data.url)
})
}
return (
<div className="inputBox">
<h1>Add A Song</h1>
<ul>
{errors.map((error, idx) => <li className='errors' key={idx}>{error}</li>)}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setSongName(e.target.value)}
value={songName}
placeholder="Song Name"
name="Song Name"
/>
{/* <input type="text"
type="text"
onChange={(e) => setSongLink(e.target.value)}
value={songLink}
/> */}
<input
// type="text"
// onChange={(e) => setSongLink(e.target.value)}
type='file'
onChange={(e) => { setSongSelected(e.target.files[0]) }}
// value={songLink}
placeholder="Song Link"
name="Audio File"
/>
<button onClick={uploadSong} type="submit">Submit</button>
{/* <Audio cloudName='dyhfkvy6u' publicId='https://res.cloudinary.com/dyhfkvy6u/image/upload/v1639007386/x8cgeebtzdfeou4p6bhw.png' /> */}
</form>
</div>
);
};
export default SongForm;
It looks like you have a onSubmit handler for your form, but you are also assigning an onClick action for the form's submit button (with a button of type submit).
Namely:
<form onSubmit={handleSubmit}>
<button onClick={uploadSong} type="submit">Submit</button>
The impact of this is two actions will fire.
If I were you, I would remove the onClick (on the button) or the onSubmit (on the form) so that you only have one action that happens.
Then, let's say you decide to keep your onSubmit as the action you want to fire, in that function I would call the two functions you want to perform. If the first function (upload) is async, I'd await its result before calling the next function.

How to use useEffect on button Click?

I've to call useEffect / Fetch the data only when user click on Search Button otherwise not fetch the data..
Code:
const App = () => {
const[datas,setDatas] = useState([])
const [space,setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val){
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
return(
<div className="App">
{ //Displaying on search
print?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<p>{field.title}</p>
<p>{field.author}</p>
)}
</div>
</>
:null
}
<input type="text" onChange={getData} />
<button onClick={() => { setSpace(true); fetchPosts() }}>search</button>
</div>
)
}
};
export default App;
It's not working Error:
fetchPosts() is not defined...
I've also tried like this:
function trigger(){
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
}
<button onClick={() => { setSpace(true); trigger() }}>search</button>
It's not working Error:
React Hook useEffect has unnecessary dependencies:'space'
/PLZZ help to out...
make a separate function for api call and in your UseEffect function just call that function and on Button click function call the Api Function and it fetch data automatically
Use useCallback not useEffect
useCallback is similar to useEffect but is for when a function needs a callback, like what you're doing here onClick. useEffect is used in response to some prop changing not an action taken by a user.
You have to set your fetchPosts outside of the useEffect.
Then, you can use a new state search to track any click on the button.
const App = () => {
const [datas, setDatas] = useState([]);
const [space, setSpace] = useState(null);
const [print, setPrint] = useState(false);
const [search, setSearch] = useState(false);
const fetchPosts = async () => {
let initial_url = `http://localhost:4000/search`;
let url = initial_url + "?text=" + space;
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
};
function getData(val) {
setSpace(val.target.value);
setPrint(false);
}
useEffect(() => {
fetchPosts(); // fecthPosts is called each time space changed
}, [search]);
return (
<div className="App">
{
//Displaying on search
print ? (
<>
<h2>{space}</h2>
<div>
{datas.map((field) => (
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
))}
</div>
</>
) : null
}
<input type="text" onChange={getData} />
<button onClick={() => setSearch(!search)}>search</button>
</div>
);
};
export default App;
I initialized shouldFetchData = false. Once the button is clicked, I changed it's value; shouldFetchData = true. Inside useEffect I called fetchPosts() only when shouldFetchData is true.
import React, { useState } from "react";
const App = () => {
const [datas, setDatas] = useState([])
const [shouldFetchData, setShouldFetchData] = useState(false);
const [space, setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val) {
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
}
useEffect(() => {
if(shouldFetchData) {
fecthPosts();
}
}, [space]);
return (
<div className="App">
{
print ?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
)}
</div>
</>
: null
}
<input type="text" onChange={getData} />
<button onClick={() => {
setSpace(true);
setShouldFetchData(true);
}}>search</button>
</div>
)
};
export default App;
I found a few syntax errors in your code, so I hope I did what you intended.
If This is not the proper way to do this or if there exists a better way, please let me know. I'd be happy to learnšŸ¤—.

TypeError: Cannot read property 'filter' of undefined (live searching)

So, i have this code that retrieves movies from api and im trying to implement live searching. I created an if statement to check the input but every time i put the first character in the input field I get the filter undefined error. How can I fix this?
import React, { useState, useEffect } from "react";
const SearchMovie = () => {
const [state, setState] = useState([]);
const [movie, setmovie] = useState("");
const [search, setSearch] = useState("");
const key = "xxxxxxxx";
const url = `http://www.omdbapi.com/?s=${movie}&apikey=${key}`;
useEffect(() => {
getData();
}, [movie]);
const getData = async () => {
const data = await fetch(url);
const response = await data.json();
setState(response.Search);
console.log(response.Search);
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setmovie(search);
setSearch("");
}
if(search.length > 0) {
setState(state.filter((i)=>{
return i.Title.match(search)
}))
}
return (
<div>
<form onSubmit={getSearch} className="search-form">
<input
type="text"
className="search-bar"
value={search}
onChange={updateSearch}
/>
<button className="search-button" type="submit">
Search
</button>
</form>
{(state || []).map((details) => (
<>
<p>{details.Title}</p>
<p>{details.Year}</p>
<img src={details.Poster} alt=""/>
</>
))}
</div>
);
};
export default SearchMovie;
You have call filter on undefined. Because you have setState(response.Search). I think your result search is undefined. Let's check.
const getData = async () => {
const data = await fetch(url);
const response = await data.json();
setState(response.Search ? response.Search : []);
//or
//if (response.Search) setState(response.Search)
console.log(response.Search);
};

Search data from API using ID

I want to fetch data from an API using as a parameter ID of a post from that API.
I created this app:
import React, {useEffect, useState} from 'react';
const GetData = () => {
let [inputVal, setInputVal] = useState('');
let [idApi, setidApi] = useState("");
let [res, setRes] = useState('loading...');
function searchInfo(e) {
setInputVal(e.target.value);
// console.log(inputVal);
}
useEffect(() => {
getInfo();
console.log("re")
}, [idApi])
const getInfo = () => {
const fetchData = async () => {
setidApi(inputVal)
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${idApi}`);
const data = await response.json();
const r = data[0].title;
setRes(r);
console.log(r)
};
fetchData();
};
return (
<div>
<p>{res}</p>
<input onChange={searchInfo} type="text" className='input-search'/>
<button onClick={getInfo}>Fetch</button>
</div>
);
}
;
export default GetData;
<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>
Clicking on the button i want to set the idApi using the input value (inputVal), and to get the data what will correspond with the setted ID. For this i wrote this code: setidApi(inputVal), but it doesn't work. Who can explain how to solve the issue?
You have to change order for setting value then calling api like below:
const getInfo = () => {
const fetchData = async () => {
setidApi(inputVal)
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${idApi}`);
const data = await response.json();
console.log(data)
};
fetchData();
};

Categories

Resources