useSwr fetch with filter query params - next.js

I have a component which displays a list of sources. Users will be able to filter this list with search keywords.
I have a state called searchKeyword which holds the input value.
I need to make a request when user clicks on search.
const { data, error } = useSWR(`source-requests/favorite?keyword=${searchKeyword}`);
const submitSearch = (e) => {
e.preventDefault();
mutate(source-requests/favorite?keyword=${searchKeyword});
};
This makes call on every keystroke. How do I prevent this.

I believe what you are looking for is a debounce function.
You could implement your own with something like this:
const debounce = (func, wait) => {
let timeout
return (...args) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
func(args)
}, wait)
}
}
All together it could look something like this:
const [searchKeyword, setSearchKeyword] = useState(null)
const { data, error } = useSWR(
searchKeyword
? `source-requests/favorite?keyword=${searchKeyword}`
: null
);
const submitSearch = debounce(setSearchKeyword, 500);
Alternatively, you could use a more featureful debounce function from a library such as lodash or underscore.

Related

Why is the asnyc/await function not been waited?

I am doing an Image Upload feature with Cloudinary. I'm providing an array which may contains base64coded or uploaded image which is a url :
[
"https://res.cloudinary.com/\[userName\]/image/upload/v167xxxx4/luxxxfsgasxxxxxx7t9.jpg", "https://res.cloudinary.com/doeejabc9/image/upload/v1675361225/rf6adyht6jfx10vuzjva.jpg",
"data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA.......", "data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA......."
]
I'm using this function to upload the "un-uploaded", which returns the all uploaded version:
export async function uploadImage(el: string[]) {
const partition = el.reduce(
(result: string[][], element: string) => {
element.includes("data:image/")
? result[0].push(element)
: result[1].push(element);
return result;
},
[[], []]
);
for (let i = 0; i < partition[0].length; i++) {
const data = new FormData();
data.append("file", partition[0][i]);
data.append("upload_preset", "my_preset_name");
const res = await fetch(
"https://api.cloudinary.com/v1_1/userName/image/upload",
{
method: "POST",
body: data,
}
);
const file = await res.json();
partition[1].push(file.secure_url);
console.log(partition[1]);
}
return partition[1];
}
Then I will use the return value to update the state and call the api to update database:
const uploaded = await uploadImage(el[1])
console.log(uploaded);
setFinalVersionDoc({
...chosenDocument,
[chosenDocument[el[0]]]: uploaded,
});
However, it always updates the useState before the console.log(uploaded). I thought async/await would make sure the value is updated before moving on.
The GitHub repo is attached for better picture. The fragment is under EditModal in the 'component/document' folder:
https://github.com/anthonychan1211/cms
Thanks a lot!
I am hoping to make the upload happen before updating the state.
The function is correct, but you are trying to await the promise inside the callback function of a forEach, but await inside forEach doesn't work.
This doesn't work:
async function handleEdit() {
const entries = Object.entries(chosenDocument);
entries.forEach(async (el) => { // <------ the problem
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
});
[...]
}
If you want to have the same behaviour (forEach runs sequentially), you can use a for const of loop instead.
This works (sequentially)
(execution order guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
for (const el of entries) {
// await the promises 1,2,...,n in sequence
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}
}
This also works (in parallel)
(execution order not guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
await Promise.all(entries.map(async (el) => {
// map returns an array of promises, and await Promise.all() then executes them all at the same time
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}));
[...]
}
If the order in which your files are uploaded doesn't matter, picking the parallel method will be faster/better.

why does this vuelidate function not work within setup, but only outside, within methods?

I'm using vuelidate with the composition API and I don't understand why the v$.validate() works correctly when I put within methods, after setup, but not within setup.
So this works:
setup() {
// inspired from
// https://vuelidate-next.netlify.app/#alternative-syntax-composition-api
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
return {
state,
v$
}
},
methods: {
async submitForm () {
const result = await this.v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
}
but, this doesn't work:
setup() {
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
const submitForm = async () {
// **ERROR : Uncaught (in promise) TypeError: v$.$validate is not a function**
const result = await v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
return {
state,
v$,
submitForm
}
}
That's a bit of a pain, because I use a composable for the axios call where the state is an argument. Would be easier to keep the entire code in one place.
Composition API
useVuelidate returns a computed, so you need to use .value when accessing any of it's properties, like $error, $validate inside the setup function.
In the template it is unwrapped for you.

React Native state console.log

I am using firebase for my app and the data i read i want to put that in state to use it in different places.
it kinda works but when i want to console.log the state it updates like 30 times a second, am i doing something wrong?
this is my code
const db = firebase.firestore();
const [PBS1Detail, setPBS1Detail] = useState();
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
return setPBS1Detail(data);
}
console.log(PBS1Detail)
i already tried to store it in a variable instead of state but thats not working for me, i can't get the variable from the function global
i am a noob i get it xd
You don't need a return statement when setting state. Also, it looks like you're performing some async function which means that when your component renders for the first time, PBS1Detail will be undefined since the db is a pending Promise.
You could/should put your async functions in useEffect hook:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
}, [])
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
setPBS1Detail(data);
}
Finally, your renderTracks function doesn't seem correct as it appears you're looping over docs and resetting your state each time.
Instead, maybe consider having an array for PBS1Detail
const [PBS1Detail, setPBS1Detail] = useState([]);
Then modify your async call:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push(renderTracks(doc))
}
)
setPBS1Detail(results)
});
}, [])
const renderTracks = (doc) => {
return doc.data().data[0].Module;
}
This way you're only setting state once and thus avoiding unnecessary re-renders and you're saving all of your docs instead of overwriting them.

Assigning Field Value of Firestore Document to Const on Cloud Functions

I would like to assign the value of a field from a document to a constant to use it across several functions.
const stripeAccountId = firestore.doc('orgs/' + subscription.orgId).get()
.then( org => {
return org.data().stripeAccountId
})
The firestore.doc('orgs/' + subscription.orgId).get().then(...) method returns a promise. More info: https://scotch.io/tutorials/javascript-promises-for-dummies
Promises are async and you need to assign the stripeAccountId inside the arrow function specified inside the then.
I don't know where you will use it, but the stripeAccountId will only be filled after the promise is resolved.
const stripeAccountId = null;
firestore.doc('orgs/' + subscription.orgId).get().then(org => {
stripeAccountId = org.data().stripeAccountId;
})
console.log(stripeAccountId); // null
const sufficientTimeInMillisToResolveThePromise = 10000;
setTimeout(() => {
console.log(stripeAccountId); // some-id
}, sufficientTimeInMillisToResolveThePromise);

React native function return nothing

I'm trying to do something simple here - connect to a sqlite database and return the number of records. In the code below I can output the len variable with console.log, but nothing gets returned. Am I missing something obvious? Thanks.
const db = SQLite.openDatabase({ name: 'favorites.db' });
export default class PlayerScreen extends React.Component {
fetch(){
console.log('fetching data from database');
var query = "SELECT * FROM items";
var params = [];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
var len = results.rows.length;
return len;
}, function(){
console.log('Profile: Something went wrong');
});
});
}
render() {
return <Text>{this.fetch()}</Text>
}
}
A lot of things are wrong here. Fetch is an asynchronous concept and React rendering is synchronous. You cannot use fetch or anything asynchronously inside the render method.
So, what should you do instead?
First, do the fetch in the componentDidMount of the component.
Also set an initial state in componentWillMount
componentWillMount() {
this.state = { length: 0 };
}
componentDidMount() {
// I use call here to make sure this.fetch gets the this context
this.fetch.call(this);
}
Secondly, attach the results to the innerState of the component in your fetch method
fetch() {
var query = "SELECT * FROM items";
var params = [];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
var len = results.rows.length;
// this.setState will trigger a re-render of the component
this.setState({ length: len });
}, function(){
console.log('Profile: Something went wrong');
});
});
}
Now, in the render method, you can use this.state.length to render the value.
render() {
return <Text>{this.state.length}</Text>
}
Hope this helps you out
Just putting this here for any other poor souls who this may help since this thread was the top result for me:
SQL isn't case sensitive, but React is. All my variables were lowercase, but my SQL tables used uppercase. So "SELECT key" actually returned "Key".
Therefore, rewriting the SQL query as "SELECT key as key" fixed it for me, since the first "key" isn't case sensitive, but the second one is.
Alternatively, just change the database headers to whatever case you'll be using in your program.

Resources