Use effect wait for Data from previous page - javascript

I'm kinda new to the react framework.
As per my requirement , I want to wait until data arrives and binds to my constants in my useEffect() method.
The data is sent encrypted from the main page and is decrypted as follows :
useEffect(() => {
const DecryptedGroupID = atob(groupID.toString());
const DecryptedFactoryID = atob(factoryID.toString());
setProfitAndLossDetails({
...ProfitAndLossDetails,
groupID: DecryptedGroupID,
factoryID: DecryptedFactoryID
});
}, []);
I want to add a waiter/timer/delay to wait until the data gets bound to my const variables. (atob is a decrypting function).
The groupID is required to generate the factoryIDs for the specific group, hence during page reload since it takes time / delay for the hooks to bind, the factoryIDs won't load at times(however when refreshing it appears sometimes), I think adding a delay and giving it time to bind might fix this issue.

Just add groupID and factoryID as your useEffect dependencies. Hook will be called automatically when they are changed. Inside hook you can check if groupID and factoryID not empty, and call your setter function.
Read more about how this hook work:
https://reactjs.org/docs/hooks-reference.html#useeffect

You need to:
Test in the hook if they are defined or not
Call the effect hook when the values you are depending on change (so the hook doesn't run once when they aren't defined and then never run again) — add them to the dependency array.
Such:
useEffect(() => {
if (!groupID || !factoryID) return;
// Otherwise don't return and use them with their values
}, [groupID, factoryID]);

Related

React Custom Hook function keeps recalling

According to the thread below,
useCustomHook being called on every render - is something wrong with this
It says it is completely normal to keep calling the custom hook function every time React re-renders.
My questions are, if it affects on a performance side when returning an array from this Custom Hook function( Not when fetching API and receiving data ) which contains a lot of values.
If so, how to prevent it ( How to let this Custom Hook function run only once )?
Here is my Custom Hook code, it returns an array which contains around 5000 string values.
function FetchWords(url: string) {
const [data, setData] = useState<string[]>([]);
useEffect(() => {
fetch(url)
.then((words) => words.text())
.then((textedWords) => {
setData(textedWords.replace(/\r\n/g, "\n").split("\n"));
});
}, []);
const expensiveData = useMemo(() => data, [data]);
return expensiveData;
}
export default FetchWords;
My Main js
const wordLists: any[] = useFetch(
"https://raw.githubusercontent.com/charlesreid1/five-letter-words/master/sgb-words.txt"
);
CustomHooks should start with word use...
You don't need useMemo in your hook, simply return data state.
Your hook makes the fetch call only once, so no problem there as the effect has empty dependency, so it runs once after first render.
The hook stores the array of 5000 entries once in data state and returns the same reference each time your custom hook is called during component re-renders. There is no copy operation, so you don't need to worry about that.
If you only want to fetch 100 entries for example, then your backend needs to provide that api.
Hope this resolves your queries as it is not very clear what is your doubt.
If you are worried about bringing all this data at the same time, you can indicate from the backend that they send you a certain number of records and from the frontend you can manage them with the pagination.
the use of useMemo is superfluous.
the useEffect that you are using will only be rendered ONCE, that is, it will only call the 5,000 registers that you mention only once

React - Updating parameters for function calls by taking data from one component and passing it to the next is lagging by one step

While passing a parameter to a cloud function, it always passes the one previous state. I am trying to fetch the image which is currently rendered on a canvas and pass it to a cloud function in another component. Even though I'm using useState and useEffect I am unable to get the immediate canvas state. It always passes the image that was one prior to current state.
I assume this is happening because the value is captured then the component having the cloud function renders. And subsequent change to the canvas would require a re-render of the cloud function component in order for it to capture the latest value?
I am getting the base64 value from the canvas of one component like this:
let imageToSave = new Image();
imageToSave.src = canvas.current.toDataURL('image/png', 1.0)
return imageToSave.src
}
I wish to pass it as a parameter in a cloud function call in another component. For which I am doing this:
const [finalImage, setFinalImage] = useState(null)
const { fetch:cloudFetch } = useMoralisCloudFunction(
"handlemint",
{
autoFetch: false,
userAddress: userAddress,
image: finalImage
}
Now I have tried all kinds of ways to setFinalImage(props.saveImage()) to pass the base64 into image parameter before my function call. I've tried useEffect, I have aaded setFinalImage inside a function and tried to update the value BEFORE I call the cloud function...
async function updateFinalImage(image) {
setFinalImage(image)
}
const cloudCall = async ()=>{
const image = await props.saveImage()
await updateFinalImage(image)
await cloudFetch({
onSuccess: (data)=>console.log(data)
})
}
I have tried both regular and async for all this. No matter what I do, the value passed into my image parameter is always one step behind . So if I have a dog picture on canvas, the null value gets passed in. Then when I change dog to cat pic on canvas, the dog (previous picture) gets passed in.
My saveImage function is not lagging. If I console log it in child component it gives me whatever latest value is on the canvas. Somehow while passing into the cloudCall with useState update, always the previous value goes and not the current updated value. I have tried to circumvent the asynchronous nature of useState by putting it inside the function but nothing works. What am I doing wrong?
My question is how do I fetch the base64 from parent component's function saveImage() and pass it in the cloud function as image: finalImage in a way that it takes the LATEST update always when I call the cloud function. Right now trying useState to update the value of image is ALWAYS sending the previous value.
Edit: I found a similar answer here
The key point that applies to my case is this:
Also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created.
However even though I have tried to trigger a re-render using 2 useEffects (one to update the value obtained and another to re-render component on obtaining value), I am still unable to solve this.
SOLVED IT! after 3 days of racking my head!
function saveImage() {
let imageToSave = new Image();
imageToSave.src = canvas.current.toDataURL('image/png', 1.0)
imageToSave.onload= () => {
return imageToSave.src
}
}
I added the return call inside an onload event. This way it ONLY returns AFTER the new (latest) image has loaded on canvas and not prematurely. Doing this solved my problem of premature return, which was the reason my base64 for was always one render behind what was ACTUALLY on canvas.

Caching observables causing problem with mergeMap

I have a caching method in a container:
get(): Observable<T[]> {
if (!this.get$) {
this.get$ = merge(
this.behaviorSubject.asObservable(),
this._config.get().pipe(shareReplay(1), tap(x => this.behaviorSubject.next(x))));
}
return this.get$;
}
This works fine with normal observables, however when I cache the bellow in a myContainer2 (e.g using cached observable's result to create another cached observable) method like:
// get is assigned to _config.get in the above function
const myContainer2 = new Container({get: () => myContainer1.get().pipe(mergeMap(res1 => getObs2(res1))});
// please note, the end goal is to resolve the first observable on the first subscription
// and not when caching it in the above method (using cold observables)
myContainer2.get().subscribe(...) // getObs2 gets called
myContainer2.get().subscribe(...) // getObs2 gets called again
myContainer2.get().subscribe(...) // getObs2 gets called for a third time, and so on
every time when the second cache is subscribed to getObs2 gets called (it caches nothing).
I suspect my implementation of get is faulty, since I am merging an behavior subject (which emits at the beginning), but I cant think of any other way to implement it (in order to use cold observables).
Please note that if I use normal observable instead of myContainer.get() everything works as expected.
Do you know where the problem lies?
Using a declarative approach, you can handle caching as follows:
// Declare the Observable that retrieves the set of
// configuration data and shares it.
config$ = this._config.get().pipe(shareReplay(1));
When subscribed to config$, the above code will automatically go get the configuration if it's not already been retrieved or return the retrieved configuration.
I'm not clear on what the BehaviorSubject code is for in your example. If it was to hold the emitted config data, it's not necessary as the config$ will provide it.

Insert Vue.js JSON object value directly into a Javascript setInterval function

How can I input a Vue.js object for the current item into another Javascript function on the page?
I have a slide HTML page that only displays the current HTML ID
Each slide I have has a time in seconds pulled in from my JSON, the timer object. I need to put this number in seconds into a SetInterval function to load programmatically the next slide.
var seconds = parseInt(document.querySelector("#value").innerText) * 1000;
setInterval(function () {
document.querySelector("a.o-controls2").click();
}, seconds)
You can see the example in action here.
I have tried loading the expression value but get a null result in the console. So perhaps this has something to do with the page load and how Vue renders the page?
I have also tried the following, by loading in the Vue object but this didn't work either:
var seconds = (this.timer - 1) 1000;
setInterval(function () { document.querySelector("a.o-controls2").click();
}, seconds)
the easiest way (IMHO) to access the data after some change (in this case, after fetch) is to use $emit and $on
this.items = items.reverse();
this.$nextTick(() => this.$emit('dataLoaded', this.items));
you could also use it without the nextTick, but then you'd get the data back before the dom is updated, so your js would fail.
this.items = items.reverse();
this.$emit('dataLoaded', this.items);
then listen to the change using...
app.$on('dataLoaded', items => {
console.log('items', items)
document.querySelector("a.o-controls2"); // now accessible
// do stuff
})
here is a fiddle: https://jsfiddle.net/y32hnzug/1/
it doesn't implement the click timing though since that's outside the scope of the question
The null response is the return from querySelector(); it's returning null because the external script is running before Vue has rendered the application. When the script runs, there is no element with an id of "value" in the document.
The easiest fix might be to move that code into the mounted() lifecycle hook of the app; possibly within a nextTick() to make sure that all child components have also rendered.

Can I call APIs in componentWillMount in React?

I'm working on react for last 1 year. The convention which we follow is make an API call in componentDidMount, fetch the data and setState after the data has come. This will ensure that the component has mounted and setting state will cause a re-render the component but I want to know why we can't setState in componentWillMount or constructor
The official documentation says that :
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method will
not trigger a re-rendering. Avoid introducing any side-effects or
subscriptions in this method.
it says setting state in this method will not trigger a re-rendering, which I don't want while making an API call. If I'm able to get the data and able to set in the state (assuming API calls are really fast) in componentWillMount or in constructor and data is present in the first render, why would I want a re-render at all?
and if the API call is slow, then setState will be async and componentWillMount has already returned then I'll be able to setState and a re-render should occur.
As a whole, I'm pretty much confused why we shouldn't make API calls in constructor or componentWillMount. Can somebody really help me understand how react works in such case?
1. componentWillMount and re-rendering
Compare this two componentWillMount methods.
One causes additional re-render, one does not
componentWillMount () {
// This will not cause additional re-render
this.setState({ name: 'Andrej '});
}
componentWillMount () {
fetch('http://whatever/profile').then(() => {
// This in the other hand will cause additional rerender,
// since fetch is async and state is set after request completes.
this.setState({ name: 'Andrej '});
})
}
.
.
.
2. Where to invoke API calls?
componentWillMount () {
// Is triggered on server and on client as well.
// Server won't wait for completion though, nor will be able to trigger re-render
// for client.
fetch('...')
}
componentDidMount () {
// Is triggered on client, but never on server.
// This is a good place to invoke API calls.
fetch('...')
}
If you are rendering on server and your component does need data for rendering, you should fetch (and wait for completion) outside of component and pass data thru props and render component to string afterwards.
ComponentWillMount
Now that the props and state are set, we finally enter the realm of Life Cycle methods
That means React expects state to be available as render function will be called next and code can break if any mentioned state variable is missing which may occur in case of ajax.
Constructor
This is the place where you define.
So Calling an ajax will not update the values of any state as ajax is async and constructor will not wait for response. Ideally, you should use constructor to set default/initial values.
Ideally these functions should be pure function, only depending on parameters. Bringing ajax brings side effect to function.
Yes, functions depend on state and using this.setState can bring you such issues (You have set value in state but value is missing in state in next called function).
This makes code fragile. If your API is really fast, you can pass this value as an argument and in your component, check if this arg is available. If yes, initialise you state with it. If not, set it to default. Also, in success function of ajax, you can check for ref of this component. If it exist, component is rendered and you can call its state using setState or any setter(preferred) function.
Also remember, when you say API calls are really fast, your server and processing may be at optimum speed, but you can never be sure with network.
If you need just data only at first run and if you are ok with that. You can setState synchronously via calling a callback.
for eg:
componentWillMount(){
this.setState({
sessionId: sessionId,
}, () => {
if (this.state.hasMoreItems = true) {
this.loadItems() // do what you like here synchronously
}
});
}

Categories

Resources