Implementing live search using Next js - javascript

I'm new to Next js and im trying to implement a live search input field, I'm not sure if I have done things right if im violating some principles of next by not using getServerProps for this matter, what I have currently done is simply call an API and save the result in a simple state
const handleInputChange = (event) => {
const query = event.target.value;
setQuery(query);
if (event.target.value.length < 3) {
setResults([]);
return;
}
setLoading(true);
fetchSearchResults(query);
};
const fetchSearchResults = async (paginate = "", query) => {
if (query === "") {
setResults([]);
}
const searchUrl = [uri];
await axios
.get(searchUrl)
.then((res) => setResults(res.data.results))
.catch((error) => setMessage("No data found"));
};
Is there a better way to do such thing in next js(production ready)? Thanks
edit:
I found a way that could probably solve this by using getStaticProps with revalidate, however this is a component in the header that's on all pages and not a page, getStaticProps won't work here, is there a way around this?
To be clear I have a list of products, and the header contains a search bar that would function as an input for live search of these products

Related

React, component not re-rendering after change in an array state (not the same as others)

I'm trying to make a page that gets picture from a server and once all pictures are downloaded display them, but for some reason the page doesn't re-render when I update the state.
I've seen the other answers to this question that you have to pass a fresh array to the setImages function and not an updated version of the previous array, I'm doing that but it still doesn't work.
(the interesting thing is that if I put a console.log in an useEffect it does log the text when the array is re-rendered, but the page does not show the updated information)
If anyone can help out would be greatly appreciated!
Here is my code.
export function Profile() {
const user = JSON.parse(window.localStorage.getItem("user"));
const [imgs, setImages] = useState([]);
const [num, setNum] = useState(0);
const [finish, setFinish] = useState(false);
const getImages = async () => {
if (finish) return;
let imgarr = [];
let temp = num;
let filename = "";
let local = false;
while(temp < num+30) {
fetch("/get-my-images?id=" + user.id + "&logged=" + user.loggonToken + "&num=" + temp)
.then(response => {
if(response.status !== 200) {
setFinish(true);
temp = num+30;
local = true;
}
filename = response.headers.get("File-Name");
return response.blob()
})
.then(function(imageBlob) {
if(local) return;
const imageObjectURL = URL.createObjectURL(imageBlob);
imgarr[temp - num] = <img name={filename} alt="shot" className="img" src={imageObjectURL} key={temp} />
temp++;
});
}
setNum(temp)
setImages(prev => [...prev, ...imgarr]);
}
async function handleClick() {
await getImages();
}
return (
<div>
<div className="img-container">
{imgs.map(i => {
return (
i.props.name && <div className="img-card">
<div className="img-tag-container" onClick={(e) => handleView(i.props.name)}>{i}</div>
<div className="img-info">
<h3 className="title" onClick={() => handleView(i.props.name)}>{i.props.name.substr(i.props.name.lastIndexOf("\\")+1)}<span>{i.props.isFlagged ? "Flagged" : ""}</span></h3>
</div>
</div>
)
})}
</div>
<div className="btn-container"><button className="load-btn" disabled={finish} onClick={handleClick}>{imgs.length === 0 ? "Load Images" : "Load More"}</button></div>
</div>
)
}
I think your method of creating the new array is correct. You are passing an updater callback to the useState() updater function which returns a concatenation of the previous images and the new images, which should return a fresh array.
When using collection-based state variables, I highly recommend setting the key property of rendered children. Have you tried assigning a unique key to <div className="img-card">?. It appears that i.props.name is unique enough to work as a key.
Keys are how React associates individual items in a collection to their corresponding rendered DOM elements. They are especially important if you modify that collection. Whenever there's an issue with rendering collections, I always make sure the keys are valid and unique. Even if adding a key doesn't fix your issue, I would still highly recommend keeping it for performance reasons.
It is related to Array characteristics of javascript.
And the reason of the console log is related with console log print moment.
So it should be shown later updated for you.
There are several approaches.
const getImages = async () => {
... ...
setNum(temp)
const newImage = [...prev, ...imgarr];
setImages(prev => newImage);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => JOSN.parse(JSON.object([...prev, ...imgarr]);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => [...prev, ...imgarr].slice(0));
}
Maybe it could work.
Hope it will be helpful for you.
Ok the problem for me was the server was not sending a proper filename header so it was always null so the condition i.props.name was never true... lol sorry for the confusion.
So the moral of this story is, always make sure that it's not something else in your code that causes the bad behavior before starting to look for other solutions...

Handle error in axios.get and data received in react/js

I'm struggling to figure out how to prevent my app from crashing after fetching data from an API. Here's what I have done so far:
User searches for a word and based on that word, the program goes to a link with the searched phrase, then fetches and stores data in a const
var baseUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${search}&apikey=****`;
${search} = whatever the user enters in the searchbar
then baseUrl is the webite used to get all the data from API
useEffect(() => {
axios.get(baseUrl)
.then(res => {
setChart(res.data);
// console.log(res.data)
})
.catch(error => {
console.log("there was an error: " + error);
})
}, [baseUrl]);
useEffect(()=>{}, [chart]);
then the program loops thru the API and stores DATE and PRICE for each entry in const stockPrices and stockDates.
const stockPrices = useMemo(() => chart && Object.values(chart['Time Series (Daily)']).map(i => i['1. open']).reverse(), [chart]);
const stockDates = useMemo(() => chart && Object.keys(chart['Time Series (Daily)']).map(x => x.replace(/\d{4}-/, "")).reverse(), [chart]);
However, sometimes if user enter a search for which there's no existing link.. the app crashes, as it's trying to loop and display data that doesn't exist.
I'm not really sure how to handle this.
In the search component, I added this little "if" statement to stop it doing anything if the search is empty ( because no such link exists ):
const handleSearch = (e) => {
e.preventDefault();
if (e.target.value !== ``) {
setSearch(e.target.value.toUpperCase())
}};
However, this only solves a small part of the problem. If the app tries to fetch data from an invalid link it simply crashes.
when the app crashes - in the console it says
"Cannot convert undefined or null to object" which is reported from the line where const stockPrices and const stockDates are sitting on.
How can I stop the app from fetching data if a link doesn't exist - how to handle this bug ?
just for context the data stored in those variables is then passed to render a chart with prices (Y-axis) and dates(X-axis) so it needs to at least have some sort of replacement..
if(typeof stockDates === 'undefined') {
return ('undefined');
} else if(stockDates=== null){
return ('null');
}
I tried doing this to replace bad fetch with 'null' || 'undefined' but it's still crashing.
Please help.
IN SHORT: App crashes if it's trying to fetch data from a link that doesn't exist ( based on input from searchbar )
I'm not sure what error you're facing with the search problem.
The other one's the error you get when you pass undefined to Object.keys or Object.values function.
I'm gonna guess the API returns some data for invalid links so chart is not undefined. In the code, you're checking to make sure chart is not undefined. But most likely, chart['Time Series (Daily)'] is undefined.
I don't know enough about your requirements to suggest a fix. But you could add an additional check and make it so...
const stockPrices = useMemo(() => chart && chart['Time Series (Daily)'] && Object.values(chart['Time Series (Daily)']).map(i => i['1. open']).reverse(), [chart]);
const stockDates = useMemo(() => chart && chart['Time Series (Daily)'] && Object.keys(chart['Time Series (Daily)']).map(x => x.replace(/\d{4}-/, "")).reverse(), [chart]);
But I think it'd be better to fix the fetch code.
axios.get(baseUrl)
.then(res => {
if (res.data?.['Time Series (Daily)']) {
setChart(res.data);
}
else {
setChart(undefined);
//maybe set some error states so you can display the appropriate message?
}
})

Getting previous documents using firestore pagination

I am using Firestore database to store my crypto trades. Since there are a lot of them, I have to load them using the .limit(numberOfTrades) query.
My query: const tradesRef = firebase.firestore().collection("trades").limit(15);
Inside my useEffect:
useEffect(() => {
tradesRef
.where("type", "==", "fiveMinutes")
.get()
.then((collections) => {
const tradesData = collections.docs.map((trade) => trade.data());
const lastDoc = collections.docs[collections.docs.length - 1];
setTrades(tradesData);
setLastTrades(lastDoc);
});
setDataLoading(false);
}, [filter]);
However, I do need pagination in order to load the next set of trades. The pagination of next is already implemented and fairly simple. This is the function I am using for next page:
const fetchMore = () => {
tradesRef
.startAfter(lastTrades)
.get()
.then((collections) => {
const tradesData = collections.docs.map((trade) => trade.data());
const lastDoc = collections.docs[collections.docs.length - 1];
setTrades(tradesData);
setLastTrades(lastDoc);
});
}
Now I am trying to figure out how to implement a previous page query that gets the previous 12 trades. I have researched and implemented a few queries but I am not even close to solving the issue. Any help would be appreciated.
If you want to paginate backwards, you need to use a combination of:
.endBefore(firstItemOnNextPage)
.limitToLast(12)
So firstItemOnNextPage is the first item on page N+1, where you're paginating backwards from. And you're then asking for the last 12 items before that anchor item.

Struggling to pass props through navigation, I believe it has something to do with my AysncyStorage set multiple

I'm trying to pass some values into another screen, it worked the first time when I tried it with one value, using async storage set for a single item, however, now I am trying it with multiple and it keeps crashing every time I press the item I want to get the data from.
Storing the data when I press on an item from a FlatList
fetchOnPressOpacity = async item => {
this.state.totalCalories += item.food.nutrients.ENERC_KCAL;
this.state.totalFat += item.food.nutrients.FAT;
this.state.totalCarbs += item.food.nutrients.CHOCDF;
this.state.totalProtein += item.food.nutrients.PROCNT;
const firstPair = ["totalCalories", JSON.stringify(this.state.totalCalories)];
const secondPair = ["totalCarbs", JSON.stringify(this.state.totalCarbs)];
const thirdPair = ["totalProtein", JSON.stringify(this.state.totalProtein)];
const fourthPair = ["totalFat", JSON.stringify(this.state.totalFat)];
try {
this.setState({});
await AsyncStorage.multiSet(firstPair, secondPair, thirdPair, fourthPair);
} catch (error) {
console.log(error);
}
};
getData() method, I am not too sure how to store the data:
getData = async () => {
try {
const values = await AsyncStorage.multiGet([
"totalCalories",
"totalCarbs",
"totalProtein",
"totalFat"
]);
} catch (e) {
// read error
}
console.log(values);
};
So, right now my main problem is that the application crashes when I press an item.
I get the below error, but do not think this is the issue.
VirtualizedList: missing keys for items, make sure to specify a key or
id property on each item or provide a custom keyExtractor.
I am also able to write to the console the value before the app crashes.
Could you please advise me?
Simple solution
var items = [['key1', 'value1'], ['key2', 'value2']]
AsyncStorage.setItem("DATA_KEY", JSON.stringify(items))
// or
AsyncStorage.multiSet(items, () => {
//to do something
});
For your code
var items = [firstPair, secondPair, thirdPair, fourthPair];
AsyncStorage.setItem("DATA_KEY", JSON.stringify(items))
Get data
AsyncStorage.multiGet(["key1", "key2"]).then(response => {
//to do something
})
Not really a fix to your code but if it's just to pass data to another screen, you could consider to pass data with navigation.
like:
const { navigation } = this.props;
navigation.navigate('YourNextScreen',
{
totalCalories: this.state.totalCalories,
totalCarbs: this.state.totalCarbs,
totalProtein: this.state.totalProtein,
totalFat: this.state.totalFat,
});
and retrieve them in:
const {
totalCalories,
totalCarbs,
totalProtein,
totalFat
} = this.props.route.params;
in case you don't want to specifically save those data for later...
https://reactnavigation.org/docs/params/

Can I treat items found through a Promise.all as a firebase collection?

I am stuck in what I thought was a very simple use case: I have a list of client ids in an array. All I want to do is fetch all those clients and "watch" them (using the .onSnapshot).
To fetch the client objects, it is nice and simple, I simply go through the array and get each client by their id. The code looks something like this:
const accessibleClients = ['client1', 'client2', 'client3']
const clients = await Promise.all(
accessibleClients.map(async clientId => {
return db
.collection('clients')
.doc(clientId)
.get()
})
)
If I just needed the list of clients, it would be fine, but I need to perform the .onSnapshot on it to see changes of the clients I am displaying. Is this possible to do? How can I get around this issue?
I am working with AngularFire so it is a bit different. But i also had the problem that i need to listen to unrelated documents which can not be queried.
I solved this with an object which contains all the snapshot listeners. This allows you to unsubscribe from individual client snapshots or from all snapshot if you do not need it anymore.
const accessibleClients = ['client1', 'client2', 'client3'];
const clientSnapshotObject = {};
const clientDataArray = [];
accessibleClients.forEach(clientId => {
clientSnapshotArray[clientId] = {
db.collection('clients').doc(clientId).onSnapshot(doc => {
const client = clientDataArray.find(client => doc.id === client.clientId);
if (client) {
const index = clientDataArray.findIndex(client => doc.id === client.clientId);
clientDataArray.splice(index, 1 , doc.data())
} else {
clientDataArray.push(doc.data());
}
})
};
})
With the clientIds of the accessibleClients array, i create an object of DocumentSnapshots with the clientId as property key.
The snapshot callback function pushes the specific client data into the clientDataArray. If a snapshot changes the callback function replaces the old data with the new data.
I do not know your exact data model but i hope this code helps with your problem.

Categories

Resources