Why is my state not being set in my functional component? - reactjs

I have this small React component, and it doesn't seem to be setting the state of my authToken in the useEffect() call.
Here is my code:
const App = ({ personId, buildingId }) => {
const [authToken, setAuthToken] = useState();
useEffect(() => {
axios.post('/api/auth/' + personId + '/location/' + buildingId, {
headers: {
'Content-Type': 'application/json',
}
}).then((res) => {
setAuthToken(res.data);
})
}, []);
return (
<Editor
init={{
tinydrive_token_provider: function (success, failure) {
success({ token: authToken.token });
}}
/>
)
}
export default App;
Do I need to set it some other way?
Thanks!

Try this:
const App = ({ personId, buildingId }) => {
const handleTokenProvider = useCallback((success, failure) => {
axios.post('/api/auth/' + personId + '/location/' + buildingId, {
headers: {
'Content-Type': 'application/json',
}
}).then((res) => {
if (res && req.data && req.data.token) {
success(res.data.token);
} else {
failure("Authentication failed");
}
}).catch(err => {
failure(err);
});
}, [personId, buildingId]);
return (
<Editor
init={{
tinydrive_token_provider: handleTokenProvider
}}
/>
);
}
export default App;
The code above assumes a few things :
That the property tinydrive_token_provider detects when it's value change and calls the function again when it does. If it does not, then there must be a way to update the token. And if it still does not, then unmounting the component and re-mounting it again will force recreating it.
That you do not need the authentication token somewhere else in the component. If you do, then this solution will not be optimal. There would be ways to set a state (useState) or a reference (useRef) but there should be a way to call success/failure without requiring a component update.
That every time you render the App component, a request will be made, even if you just got the token for the same props. Having a way to store this token in a cache (e.g. localStorage) could greatly improve performance on refresh.
Alternative
If you need to have access to the token elsewhere than the Editor, this would also work :
// this function get be in a separate module!
const fetchAuthToken = (personId, buildingId) => new Promise((resolve, reject) => {
// TODO : check some cache first, and resolve if a
// previous token already exists and is not expired, etc.
axios.post('/api/auth/' + personId + '/location/' + buildingId, {
headers: {
'Content-Type': 'application/json',
}
}).then((res) => {
if (res?.data?.token) {
resolve(res.data.token);
} else {
reject("Authentication failed");
}
}).catch(err => {
reject(err);
});
});
const App = ({ personId, buildingId }) => {
const tokenPromise = useMemo(() =>
fetchAuthToken(personId, buildingId),
[personId, buildingId]
);
// DEBUG ONLY
useEffect(() => {
tokenPromise.then(token => {
console.log('Token for', personId, buildingId, 'is', token);
}, err => {
console.error('Failed to get token for', personId, buildingId);
console.error(err);
});
}, [tokenPromise, personId, buildingId]);
return (
<Editor
init={{
tinydrive_token_provider: (success, failure) => {
tokenPromise.then(success, failure);
}
}}
/>
);
}
export default App;

Related

tanstack react query mutate onSettled to access PUT body data

Dears,
Due to some reason, I need access to the PUT request body after the PUT request settled.
Please check the sandbox example I tried to prepare.
My question is - is it ok to return the PUT params in onMutate and then do some logic in onSettled based on these params, for example selectively setting a loading state to false.
And then, why the PUT params are the 3rd argument of the onSettled function?
p.s. please don't argue about state management, the question is about onSettled usage :)
Best regards,
MJ
import React from "react";
import { useMutation } from "react-query";
const someProps = { prop1: "key1" };
export default function App() {
const [isLoading, setIsLoading] = React.useState(false);
const { mutate } = useMutation({
mutationFn: async (someProps) =>
await fetch("https://httpbin.org/put", {
method: "PUT",
body: JSON.stringify(someProps)
}).then((response) => response.json()),
onSuccess: (responseData) => {
console.log("RESPONSE ON SUCCESS: " + JSON.stringify(responseData));
},
onMutate: (data) => {
setIsLoading(true);
console.log(
"Yes, I have access to props before I send the request: " +
JSON.stringify(data)
);
// I return the data so I can use it in on settled
return data;
},
onSettled: (arg1NotUsed, arg2NotUsed, data) => {
console.log(
"Yes, I have access to props after I receive the response: " +
JSON.stringify(data)
);
if (data) {
setIsLoading(false);
}
}
});
return (
<div>
<p>is loading: {isLoading ? "LOADING" : "IDLE"}</p>
<button onClick={() => mutate(someProps)}>trigger mutation</button>
</div>
);
}
variables are available in onSettled even if you don't return them from onMutate. What onSettled receives is:
onSettled(data, error, variables, context)
where context is what you return from onSettled. In your example, you're using the 3rd parameter, which is not the value returned from onMutate, so you can safely leave that out.
There is also no need to separately track an isLoading boolean, because useMutation does this for you and also returns a loading state.
export default function App() {
const { mutate, isLoading } = useMutation({
mutationFn: async (someProps) =>
await fetch("https://httpbin.org/put", {
method: "PUT",
body: JSON.stringify(someProps)
}).then((response) => response.json()),
onSuccess: (responseData) => {
console.log("RESPONSE ON SUCCESS: " + JSON.stringify(responseData));
},
onSettled: (arg1NotUsed, arg2NotUsed, data) => {
console.log(
"Yes, I have access to props after I receive the response: " +
JSON.stringify(data)
);
}
});
return (
<div>
<p>is loading: {isLoading ? "LOADING" : "IDLE"}</p>
<button onClick={() => mutate(someProps)}>trigger mutation</button>
</div>
);
}
Here's a fork of your sandbox with these changes: https://codesandbox.io/s/usequery-forked-vq8kcr?file=/src/App.js

Embed code onto another page and use their variables

I have created a web app in React with several pages. To make it a bit secure for our department, at least so everyone can't enter the site and see what's going on, there are some code that does some checks, and also creates some variables I can use for each of my pages.
The issue is that this code is the same on every single page.
So basically it looks something like:
const MyApp= () => {
const { instance, accounts, inProgress } = useMsal();
const [accessToken, setAccessToken] = useState<any>(null);
const name = accounts[0] && accounts[0].name;
const email = accounts[0] && accounts[0].username;
const isAuthenticated = useIsAuthenticated();
useEffect(() => {
const request = {
...loginRequest,
account: accounts[0],
};
instance
.acquireTokenSilent(request)
.then((response: any) => {
setAccessToken(response.accessToken);
})
.catch(() => {
instance.acquireTokenPopup(request).then((response: any) => {
setAccessToken(response.accessToken);
});
});
}, [isAuthenticated]);
function POST(path: string, data: any) {
return fetch(`${fetchUrl}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: accessToken,
},
body: JSON.stringify(data),
});
}
<something else>
return (
<>
<div>{name}</div>
<div>{email}</div>
</>
);
};
export default MyApp;
And it's just annoying to write this on every page. So is there some way to create some kind of component, and then embed it into all pages, where I still have the option to use the const variables created ?

Warning: Can't perform a React state update on an unmounted component in React native

I am getting data from axios.But sometime data is coming and some it is not showing data initially.
get-specific-salary-api.js:---
const AllSpecificSalaryAPI = (yearMonthFilter) => {
const [specificAllAPIData, setAllSpecificAPIData] = useState("");
const loginAuthToken = useSelector(
(state) => state.loginAuthTokenReducer.loginAuthToken
);
//NOTE:taking oved input and company selection code for dynamic parameter
const companyValue = useSelector(
(state) => state.changeCompanyReducer.company
);
const companyCode = companyValue[0];
const employeeId = companyValue[1];
useEffect(() => {
axios
.get(GET_ALL_SPECIFIC_SALARY, {
params: {
hev: companyCode,
year_month: yearMonthFilter,
oved: employeeId,
},
headers: {
Authorization: `Bearer ${loginAuthToken}`,
},
})
.then((response) => response.data)
.then((data) => setAllSpecificAPIData(data))
.catch((error) => {
if (error.status === 401) {
//NOTE: handling token expire
return ExpireAlertRestart();
} else {
Alert.alert(error.message);
}
});
}, []);
return {
specificAllAPI: specificAllAPIData,
};
};
export default AllSpecificSalaryAPI;
i am getting warning message for this.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at [native code]:null in dispatchAction
at src/api/get-specific-salary-api.js:32:12 in axios.get.then.then$argument_0
How can i solve this warning message?
In your useEffect, clean your states like so:
const [specificAllAPIData, setAllSpecificAPIData] = useState(null);
useEffect(() => {
return () => {
setAllSpecificAPIData(null);
};
}, []);
Please see a better explanation on how useEffect is working here
You are getting this because the component tries to update the state even when the component has unmounted.
To solve this, you can use an indicator variable that tells about the unmounting of the component. On unmounting, it will intercept the request of state update in between and cancel that.
let hasUnmounted = false;
useEffect(() => {
axios
.get(GET_ALL_SPECIFIC_SALARY, {
params: {
hev: companyCode,
year_month: yearMonthFilter,
oved: employeeId,
},
headers: {
Authorization: `Bearer ${loginAuthToken}`,
},
})
.then((response) => response.data)
.then((data) => {
if (hasUnmounted) return;
setAllSpecificAPIData(data)
})
.catch((error) => {
if (hasUnmounted) return;
if (error.status === 401) {
//NOTE: handling token expire
return ExpireAlertRestart();
} else {
Alert.alert(error.message);
}
});
return () => {
hasUnmounted = true;
}
}, []);
Check this link for implementation: https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/updateErr.js
Note: go to https://ikqdn.csb.app/err in codesandbox's browser

How to fetch api via looped callbacks with React functional components

So I have a 40+ loop that's calling another component to display images. Each image has an ID and with that ID I can get more information about the image like Name and description via another API call.
When DisplayImage gets called I want it to call another callback function that will send out API calls for that image's metadata, store it in a variable and display it as an H1 tag.
return (
<div>
{array.map(index) => {
// Some Other Code That return a TokenID //
<>
{displayImage(tokenId)}
</>
</div>
})
const displayImage = (tokenId) => {
const imageName = GetURI(tokenId)
return (
<div className="token-container">
<h1>{imageName}</h1>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
}
const GetURI = async (tokenId) => {
const res = await fetch("https://api"+tokenId , {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
}).then(data => {
console.log(data)
return data.json();
})
.then(data => {
return (data.name || [])
})
.catch(err => {
console.log(err);
});
}
The data is being displayed on the console but now I'm running into an infinite loop issue that I know UseEffect can solve but I can't quite figure it out. I managed to display the data on the console with UseEffect using the [] attribute but don't know how to display the data. Any help would be amazing. Thank you!
Two things useful to your situation
functions declared outside the component aren't recreated each render
useState and useEffect pairing limits calls to API to only when tokenId changes
// Put this function outside the component
// so it does not need a useCallback
// i.e not reconstructed each render of DisplayImage
const GetURI = async (tokenId) => {
...
});
const DisplayImage = (tokenId) => {
const [imageName, setImageName] = useState()
// limit calls to API to when tokenId changes
// and if eslint complains add GetURI to dependency list
// - but GetURI never changes, so no un-needed calls from it
useEffect(() => {
setImageName(GetURI(tokenId))
}, [tokenId, GetURI])
return (
<div className="token-container">
<h2>{imageName}</h2>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
};
You can also abstract to custom hook useImageName()
const GetURI = async (tokenId) => {
...
});
const useImageName = (tokenId) => {
const [imageName, setImageName] = useState()
useEffect(() => {
setImageName(GetURI(tokenId))
}, [tokenId, GetURI])
return imageName
})
const DisplayImage = (tokenId) => {
const imageName = useImageName(tokenId)
return (
<div className="token-container">
<h2>{imageName}</h2>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
};
BTW in GetURI this
return (data.name || [])
looks like should be
return data.name || ''
Is a different approach ok? I'd put display image into its own component.
const DisplayImage = ({tokenId: {_tokenId}}) => {
const imageName = GetURI(_tokenId)
const GetURI = useCallback(async () => {
await fetch("https://api"+tokenId , {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
}).then(data => {
console.log(data)
return data.json();
})
.then(data => {
return (data.name || [])
})
.catch(err => {
console.log(err);
});
})
});
useEffect(() => {
if (_tokenId) GetURI();
}, [GetURI]);
return (
<div className="token-container">
<h2>{imageName}</h2>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${_tokenId}`} />
</div>
)
};
and then
return (
<div>
{array.map(index) => {
//Some Other Code//
<DisplayImage tokenId={tokenId} />
</div>
})
You should probably cache the response from GetURI(tokenId). No need to ask twice for the same URI when using the same tokenId.
An easy way is using react-query:
Setup in App.js:
// App.js
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
Then use in a DisplayImage component (instead of inline function):
// DisplayImage.js
import { useQuery } from 'react-query'
export function DisplayImage(tokenId) {
const { isLoading, error, data: imageName } = useQuery(['images', tokenId], GetURI(tokenId))
return (
<div className="token-container">
<h1>{isLoading ? 'loading...' : imageName}</h1>
<img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
</div>
)
}
I found the best way to go about it with everyones help on here so thanks!
I put the GetURI function inside the show image component, and had a useEffect method call GetURI every time there was a new token ID, then I set a state variable to whatever was returned.
No loops, no errors 👌
const DisplayImage = (data) => {
const [nftMetadata, setNftMetadata] = useState();
const GetURI = async (data) => {
const nftURI = await data.drizzle.contracts.Contract.methods.tokenURI(data.tokenId).call()
await fetch(nftURI , {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"Access-Control-Allow-Origin": "*"
},
})
.then(data => {
return data.json();
})
.then(data => {
return setNftMetadata(data || []);
})
.catch(err => {
return console.log(err);
});
});
useEffect(() => {
GetURI(data);
}, [data.tokenId])
return (
<div className="token-container">
<h2>{nftMetadata.name}</h2>
<img className="artwork" width="450px" src={`https://ipfs:/whatever/${nftMetadata.image}`} />
</div>
);
};
return (
<div>
{array.map(index) => {
// Some Other Code That returns a TokenID //
<>
<DisplayImage address={drizzle.contractList[0].address} tokenId={tokenId} drizzle={drizzle} drizzleState={drizzleState}/>
</>
</div>
})

Async problem at render time of React function: it will redirect directly instead of waiting for fetch to end

I want a page to render based on token validation. If the token is valid, it renders, if not, redirects.
When I did this using a React Class there was no problem whatsoever and everything works as expected.
Now, due to my need of using a param on the URL route (the token), I need to use Hooks. React Router constrains in this matter in order to use {useParams}. This has brought unexpected async problems. This is the code.
If instead of doing a I render some regular it actually works fine, but I believe it is a lousy approach and would like to know the proper way to handle this so that it redirects if the token validation was incorrect and renders the right component if it was correct. Also, this is the first time I work with React fuctions instead of Components so any other tip for cleaner code will be appreciated.
import React, { useState } from 'react';
import {
useParams, Redirect
} from "react-router-dom";
export default function ResetPassword() {
let { token } = useParams();
const [tokenStatus, setTokenStatus] = useState(false);
const validateToken = () => {
var myHeaders = new Headers();
myHeaders.append("access-token", token);
var requestOptions = {
method: 'POST',
headers: myHeaders,
redirect: 'follow'
};
fetch("http://localhost:4000/verifyemailtoken", requestOptions)
.then(response => response.text())
.then(result => {if (result==="Access Granted")
{
setTokenStatus(true);
}})
.catch(error => console.log('error', error));
}
validateToken();
if (tokenStatus) {
return (
<div className="app">
THE TOKEN WAS VALID
</div>
)
}
else {
return (
<Redirect to="/home/>
)
}
}
It sounds like what you need additional state which would indicate that the check is running prior to showing the the token was valid message or redirecting users to home.
function ResetPassword() {
const { token } = useParams();
const [tokenCheckComplete, setTokenCheckComplete] = React.useState(false);
const [tokenStatus, setTokenStatus] = React.useState(false);
React.useEffect(() => {
var myHeaders = new Headers();
myHeaders.append("access-token", token);
var requestOptions = {
method: "POST",
headers: myHeaders,
redirect: "follow"
};
// reset state when new token is passed
setTokenStatus(false);
setTokenCheckComplete(false);
fetch("http://localhost:4000/verifyemailtoken", requestOptions)
.then(response => response.text())
.then(result => {
if (result === "Access Granted") {
setTokenStatus(true);
}
setTokenCheckComplete(true);
})
.catch(error => {
setTokenStatus(false);
setTokenCheckComplete(true);
});
}, [token]);
if (!tokenCheckComplete) {
return "Loading...";
}
return tokenStatus ? (
<div className="app">THE TOKEN WAS VALID</div>
) : (
<Redirect app="/home" />
);
}

Resources