I am trying to get suggestions on a search field anytime the input field changes. However i keep running into various errors. this is what i've got so far.
export default function AutoComplete() {
const [info, setInfo] = useState([{'username': 'add a search word'},])
const [search, setSearch] = useState()
const handleChange = (e) => {
setSearch(e.target.value)
}
useEffect(() => {
const findItem = async (searchString) => {
const response = await fetch('http://localhost:8000/' + searchString, {
method: 'GET',
headers:{
'content-type': 'application/json',
},
})
let data = await response.json()
data = JSON.stringify(data)
return data
}
if (search){
var newdata = findItem(search)
setInfo(newdata)
}
else {
setInfo([{'username': 'add a search word'}])
}
}, [search])
return(
<div>
<input onChange={(e) => handleChange(e)}></input>
{info.map((suggestion) => {
return (
<div>
{suggestion.username}
</div>
)
})}
</div>)
}
The error i am running into at the moment is 'info.map is not a function' when i try to type in the input field. I decided to do my own implementation after i spent a lot of time running into this same error when i was using the material ui implementation https://mui.com/components/autocomplete/. in their implementation, they have a variable storing the data rather than calling an api and it works well. I can see that my data is being retrieved as it pops on on console.log at the end of the findItem function. Please help!
It seems that your findItem function is returning a JSON string:
data = JSON.stringify(data)
return data
Which is then set directly to the info variable:
setInfo(newdata)
This means that "info" will contain a JSON string rather than an array of JSON objects. Map doesn't work on JSON strings.
For your code to work you need to pass "setInfo" an array of objects; eg:
[
{username: "John"},
{username: "Amy"},
{username: "Peter"}
]
Related
I am making a test e-commerce sight to learn nextjs. I am trying to implement the checkout through stripe and I have it working if all of the information is static. when I make any of the information set to a variable it stops working and tells me that I am not passing any values into my variables. to test this I am making all of my data that needs to be passed, static data except for one which is when I get the error that I am not passing in information properly
obviously I am not sending the data to the api correctly. I think that my code looks just like the guides and docs so I am stuck. any help would be greatly appreciated.
here is the error message that I get:
"Missing required param: line_items[0][price_data][product_data][name]."
even if I change the state variable 'title' to a single value instead of an array, and in the updateState function settitle("title") I still get the same error
here is the front end code where I try to send the data to the api endpoint:
basket is an array of objects containing all of the products that the user has chosen.
const [id, setId] = useState([]);
const [price, setprice] = useState([]);
const [description, setdescription] = useState([]);
const [title, settitle] = useState([]);
const updateState = () => {
basket.forEach(element => {
setId([...id, element.id]);
setdescription([...description, element.description]);
setprice([...price, element.price]);
settitle([...title, basket.title]);
});
console.log(id);
console.log(description);
console.log(price);
console.log(title);
}
//send data to the api
const postData = async () => {
const response = await fetch("/api/checkout_sessions", {
method: "POST",
body: JSON.stringify(
id,
price,
description,
title,
),
});
return response.json();
};
return (
<form action="/api/checkout_sessions" method="POST">
<button
type="submit"
role="link"
className="button"
onClick={() => {
updateState;
postData;
}}
>
proceed to checkout
</button>
</form>
)}
here is the api code where I try to get that data and use it which is not working how I expect:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async function handler(req, res) {
// var priceVariable ='price_1MB8P4FqoomU2P4qrVmtxCvp';
if (req.method === 'POST') {
const items = req.body.id
const amount = req.body.price
const description = req.body.description
const title = req.body.title
try {
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create({
// shipping_options: ["shr_1MBn0HFqoomU2P4qZk4vqOQ3"],
shipping_address_collection: {
allowed_countries: ["US", "CA", "GB"],
},
line_items:[{
price_data: {
unit_amount: 1000,
currency: 'usd',
product_data: {
name: title,
description: "description",
},
},
quantity: 1,
}],
mode: 'payment',
success_url: `${req.headers.origin}/?success=true`,
cancel_url: `${req.headers.origin}/?canceled=true`,
});
res.redirect(303, session.url);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
you can see in the line_items that everything is static except for the one variable that I am testing.
JSON.stringify expects an object (https://www.w3schools.com/js/js_json_stringify.asp)
const postData = async () => {
const response = await fetch("/api/checkout_sessions", {
method: "POST",
body: JSON.stringify({
id,
price,
description,
title,
}),
});
return response.json();
};
And on the api side you may have to parse the body before accessing any properties like so
const body = JSON.parse(req.body)
const title = body.title
(https://www.w3schools.com/Js/js_json_parse.asp)
It's unclear if the array/string mismatch is due to your testing changes, but you'll need to ensure a single string is supplied for name.
Your actual issue is likely here:
onClick={() => {
updateState;
postData;
}}
I'm surprised this is invoking the functions without (), but even if it were your postData() would start before the react state change happened.
I suspect if you initialized title with a value your endpoint would receive that.
const [title, setTitle] = useState("some default title");
You'll need to review how your application is tracking state here, and perhaps calculate that title and pass it through to the request alongside updating the state.
I want to render the list of key items from API response
I get data in a const and can render the specific value like this
const cartRequest = async () => {
const res = await fetch('MYAPI', {
method: 'GET',
body: JSON.stringify()
})
const data = await res.json()
setKey(data.data)
}
<div>
{Array.from(key).map((el) => {
return (
<li>{el.app_name}</li>
)
})}
</div>
but I want to render the list of highlight keys as you see in the image, can anyone give a code example?
I have a form with a text field and a form input which accepts multiple files. OnSubmit the files are sent to Firebase Storage, which sends back a URL for each file. These URLs are then stored in a 'photosURL' array in the form object, which is then posted to MongoDB.
The problem is, every time I post the form object data to Mongo, the photos array is empty, despite the console log showing it to be populated before I call the post-to-Mongo code. This leads me to think the post-to-Mongo code is using the form object value before it has been populated with the photo URLs.
The question is, how do I check that the photo array has been populated before I run the code to push the data to MongoDB? I'm already using a Promise.all to in theory wait for all the files to be sent and the URLs returned, but I can't work out why else the photoURLs array is empty every time data is sent to Mongo.
Here's the code:
const [form, setForm] = useState({
userId: '',
post: '',
createdAt: createdAt,
photoURLs: [],
})
const handleSubmit = (e) => {
e.preventDefault()
newPost ? postData(form) : ...
}
// SEND FILE TO FIREBASE AND GET BACK THE URL
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
return fileRef.put(file).then(() => {
return fileRef.getDownloadURL().then(function (url) {
photoArray.push(url);
setForm(prevState => ({ ...prevState, photos: photoArray }))
});
});
}
// POST FUNCTION
const postData = async (form) => {
setLoading(true)
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
Promise.all(uploadTasks).then(() => {
axios.post('/api/posts', form)
.then(response => {
...
})
.catch(error => {
...
})
})
}
Can anyone see what's going wrong, please?
EDIT: This is a consolel log of the form object, called before the axios.post code (it's showing the photosURL as populated):
createdAt: 1630072305502
photos:
0: "https://firebasestorage.googleapis.com/..."
1: "https://firebasestorage.googleapis.com/..."
post: "sample text"
userId: "1iNGV..."
I think that you are running into a timing issue.
Don't forget that React state updates are asynchronous, as described here.
I suggest to pass your URLs directly instead of going through your component's state:
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
await fileRef.put(file);
const url = await fileRef.getDownloadURL();
return url; // Send back the download URL
}
const postData = async (form) => {
setLoading(true);
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
const photos = await Promise.all(uploadTasks); // Get all URLs here
await axios.post('/api/posts', {...form, photos}); // Send URLs to your server
setLoading(false);
}
If I understood correct, You want to upload files first and when you get your urls array populated then only you want to call postData function?
If that's the case, then you can use useEffect to detect the urls change.
useEffect(() => {
// Here you call your postData function.
postData();
}, [form.photoURLs])
What this will do is, Whenever your form.photoURLs gets populated this useEffect will run and will make the request to the server with proper data.
I am trying to build a conditional dynamic react component where makes an API call based on the user interaction, but if the user types something in the search bar. I want to add search= param the otherwise use /list endpoint without query params. I am using currently Axios , and I would like to know some approach to do the following
const FeedsList = () => {
const [feed, setFeed] = useState([]);
const [currentPageUrl, setCurrentPageUrl] = useState("http://localhost:8001/api/v1/feeds/list/")
const performSearch = () => {
//setLoading(true)
api.get(currentPageUrl).then(res => { // axios call
setLoading(false)
setFeed(res.data.results)
}).catch(function(error){
console.log(error);
});
}
const handleSearch = (e) =>{
console.log(e.target.value)
//performSearch();
}
useEffect(() => {
performSearch()
}, [currentPageUrl]);
if (loading) return "Loading..."
}
export const api = axios.create(
{baseURL : 'http://localhost:8001/api/v1/feeds/list/'}
)
user input
<input type="text" placeholder="Enter keyword" onChange={event => handleSearch(event)}/>
Store user input to state, not URL, and then construct your URL from initial value (list) and user input, if any:
const FeedsList = () => {
const [loading, setLoading] = useState(false);
const [feed, setFeed] = useState([]);
const [searchString, setSearchString] = useState("");
const performSearch = (searchString) => {
setLoading(true);
let url = "http://localhost:8001/api/v1/feeds/list/";
// you might want to escape this value, and sanitize input on the server
if (searchString) url = `${url}?search=${searchString}`;
const cancelTokenSource = axios.CancelToken.source();
return api
.get(url, { cancelToken: cancelTokenSource.token })
.then((res) => {
setLoading(false);
setFeed(res.data.results);
})
.catch(function (error) {
console.log(error);
});
return cancelTokenSource;
};
const handleSearch = (event) => {
setSearchString(event.target.value);
};
useEffect(() => {
let token = performSearch(searchString);
return () => token.cancel();
}, [searchString]);
if (loading) return "Loading...";
};
You might want to debounce or throttle requests, so you will not bombard your server with requests on each keystroke
The Axios api allows for passing a second parameter to the get method which is the config for the request being sent. That config object takes a params property that would be parsed and appended onto the url as a query string. The nice thing is if the params object is empty it will be like not passing anything at all. A fuller example here on their GitHub page.
Passing an empty params object is the same as passing no params object at all in terms of what is requested.
// both lines request url looks like this => https://jsonplaceholder.typicode.com/users
axios.get('https://jsonplaceholder.typicode.com/users')
axios.get('https://jsonplaceholder.typicode.com/users', { params: {} })
To answer your question you could conditionally just create the params based on whether there is a value from the search input, something like the following:
const performSearch = () => {
const config = search === '' ? {
params: {}
} : {
params: { search } // same as { search: search }
}
api.get(currentPageUrl, config).then(res => {
// do something with response
}).catch(function(error){
console.log(error);
});
}
An assumption in the above would be that you stored the search value in state somewhere and add it to your useEffect dependencies list, referencing it in performSearch.
The following is my hook that calls my server and fetches data. after I receive a response from the server, I'm trying to set this response to my state.
const [userProfileData, setUserProfileData] = useState({})
useEffect(() => {
const profileRequestBody = `
query{
GetUserAndProfileData{
name
email
_id
figsList{
_id
figData
createdAt
}
}
}
`
const usertoken = token
axios({
method: 'POST',
url: 'http://localhost:5000/graphql',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${usertoken}`
},
data: {
query: profileRequestBody
}
}).then(response => {
debugger
//check if we recieved any errors in the request
setIsLoading(false)
setUserProfileData(response.data.data.GetUserAndProfileData)
let errors = response.data.errors
console.log(errors)
if (errors) {
throw new Error(errors[0].message)
}
}).catch(err => {
debugger
console.log(err)
})
}, []);
setUserProfileData(response.data.data.GetUserAndProfileData) is throwing the following error A cross-origin error was thrown. React doesn't have access to the actual error object in development. I'm actually setting state there.
The issues is not about
setUserProfileData(response.data.data.GetUserAndProfileData)
when A cross-origin error was thrown. React doesn't have access to the actual error object in development. occur, this is because when you render the data in view are in object format instead of the primitive format (e.g. int, string, float), I can give you a example
Data json format:
[
{
"name":"micky",
"age":29,
"sex":"M",
"families":[
{
"name":"carol",
"age":30,
"sex":"F"
}
]
}
]
Code :
export default function (props) {
const [data, setData] = React.useState([]);
React.useEffect(() => {
FamilyService.getdata().then(res => {
setData(res);
});
}, []);
const getdata = (row) =>{
var familymember = row.families.find(x => x.name.includes("carol"));
return familymember;
}
return (
<div>
{data.map((row, idx) => (
<span>{row.name}</span>
<span>{row.age}</span>
<span>{getdata(row)}</span>
))}
</div>
);
}
In above code
var familymember = row.find(x => x.families.includes("data"));
return familymember; <----- This code will cause error!!!!
Because it is return a object instead of a value, if I want to fix the error, it should use
return familymember.name;