Next.js, make POST request on button click (SSG) - reactjs

I'm doing an ssg website, it doesn't have backend, but it does start to have some features that fetch some data from another server. I checked the SWR and it works, the issue is I need to make a post request on button click, and it gets me an error:
hooks can only be called inside of the body of a function component
What I see is that I can create a function component, set up a call in it and just mount this component on button click, it works, but I'm having doubts about this approach.
This is probably done to work with get request, but I make a post.
ExportComponent renders on a page or in another component.
function ExportComponent() {
const [exportCalled, setExportCalled] = useState(false)
const exportCall = () => {
setExportCalled(true)
}
if (exportCalled) {
return (
<CallExport></CallExport>
)
}
return (
<Button
onClick={ exportCall() }
>
Export
</Button>
);
}
function CallExport() {
// api post call
const { data, isLoading } = exportProject();
if (isLoading) {
return <CircularProgress />;
}
return (
// call to api is done, make something with data
<Button>Open</Button>
)
}
export function exportProject() {
const params = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
};
const exportFetcher = (url) => fetch(url, params).then((r) => r.json());
const { data, error } = useSWR('url', exportFetcher);
return {
data,
isLoading: !error && !data,
isError: error
}
}
Is it wrong? Is there a better way?

Related

React ovverride shown component with new component after server response

I would want load new component after server response using React. Its could be a simple operation but something does not working at the moment. My code is the following:
const formSubmit = async e => {
e.preventDefault();
const response = await dataSubmit({
toSend
});
if(response.status === 200){
console.log("status 200");
return(
<>
<ComponentPage />
</>
);
}
else{
console.log("error status");
return(
<>
<ComponentPage />
</>
);
}
}
async function dataSubmit(toSend) {
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(toSend)
})
.then(data => (data.json()))
}
I tried removing e.preventDefault() or using effect but is not working. This component will have to ovverrife atually Form component shown. console.log lines works. How can I solve? Thanks
returning a component as part of formSubmit or a clickHandler won't render the component.
the simplest way i can think of doing is to, make the render() or the default return of this component to be conditional.
have a loading state, set true when the form is submitted and false when you get the response. render your <Component/> only if loading is false.
something like this,
const YourComponent = () => {
const [loading, setLoading] = useState(false);
//set loading when form is submitted.
// unset it when the response is recieved.
return <>
{loading} ? <Loader/> : <Component/>
</>
}
you can extend the same principle to show errors as well.

React Typescript Mutation calling api every time the page is clicked

I have a simple page that calls an api to get some data and display it in a table. I use a function that uses mutation to call the api and return the data and the state of fetching the data. I use the following (pseudo) code to achieve this:
export const Instances = (props: InstanceProps) => {
const history =
useHistory<{
formId: number;
rowData: any;
formInstanceID: number;
}>();
const { isLoading, isError, isSuccess, data, error } = useGetFromApi('api/list', [
`${history.location.state.formId}`,
]);
if (isLoading) {
return (
<div>
Page Loading
</div>
);
}
if (isError) {
return (
<div>
An error occurred
</div>
);
}
if (isSuccess) {
if (data) {
// Map data to an array for the table component
}
}
return (
<div>
Display table showing data
</div>
);
};
The page calls the api successfully and displays the data in the table. The problem I have is when I click anywhere on the page or click the two buttons that are present on the page the mutation is called again and the page freezes while it fetches the data again. It doesn't remember that it's successfully loaded. How do I stop it from calling the api every time I interact with the page?
The code for the mutation function:
import { usePidgetIdentity } from '#workspace/utils-react';
import { useQuery } from 'react-query';
import config from '../shared/config';
export type ApiData = {
data: any[];
};
async function callToApi(url: string, getAccessToken: () => Promise<string>) {
const token = await getAccessToken();
console.log('Starting api fetch');
const response = await fetch(url, {
method: 'GET',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
});
console.log('Data has returned');
if (!response.ok) {
throw Error('An unexpected error occurred');
}
const apidata = await response.json();
return { data: apidata };
}
export function useGetFromApi(url: string, parameters: string[]) {
const { getAccessToken } = usePidgetIdentity();
let apiUrl: string = config.FORMS_API_URL + '/' + url;
parameters.map((parameter) => {
apiUrl += '/' + parameter.trim();
});
console.log('Calling ' + apiUrl);
return useQuery<ApiData, Error>(apiUrl, () => callToApi(apiUrl, getAccessToken));
}

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" />
);
}

react button is called before onPress when using Hooks

I am trying to request an API request using hooks. But my problem is that my function is called before I onPress.
I have an custom API component like this:
const FetchDataPut = (URL) => {
useEffect(() => {
async function fetchData() {
const res = await axios({
method: 'put',
url: URL,
data:{
name:'111',
email:'222',
password:'333',
id:'444',
phone:'555'
}
});
const response = await res;
console.log(response, 'completed')
}
fetchData()
},[])
return null
}
I could see from the console.log that api request has completed. My problem is that my api component is called before I onPress the button.
This is my Button component:
const EditAccount = (props) => {
const Desktop = () => {
const URL = `MyURL...`
return (
<View>
<Button title='edit account' onPress={FetchDataPut(URL)}/>
</View>
)
}
return(
<div>
<Desktop/>
</div>
)
}
If I change my onPress function to an arrow function like this:
onPress={()=>FetchDataPut(URL)} component isn't called before I onPress it. But it will give me an error Invalid hook call. Hooks can only be called inside of the body of a function component
Any Idea how to my the api request when I onPress the Button ?
Any comments or advice would be really helpful. Thanks in advance! :)
I think the way to go is to use a hook rather than a component:
const useFetchDataPut = () => {
const [url, setUrl] = useState("");
useEffect(() => {
if (!url) return;
async function fetchData() {
const res = await axios({
method: "put",
url,
data: {
name: "111",
email: "222",
password: "333",
id: "444",
phone: "555"
}
});
const response = await res;
console.log(response, "completed");
}
fetchData();
}, [url]);
return setUrl;
};
And then call it when you press the button. Also Desktop should be defined outside of the EditAccount component.
const Desktop = () => {
const setUrl = useFetchDataPut();
return (
<View>
<Button
title="edit account"
onPress={() => setUrl("https://...")}
/>
</View>
);
};
const EditAccount = props => {
return (
<div>
<Desktop />
</div>
);
};
Let me know if something is not clear.
There are several issues here.
You’re setting the onPress prop to the result of calling FetchDataPut(URL). The prop should be the function itself, not the result of invoking it. By using an arrow function you’re declaring a new function that, when invoked, calls FetchDataPut.
If you’re invoking it on button press, there’s no need for it to be a hook.
Also, FetchDataPut isn’t a React component.
Declare your data fetching function by itself:
async function fetchData(url) {
return axios({
method: 'put',
url: URL,
data: {
name:'111',
email:'222',
password:'333',
id:'444',
phone:'555'
}
})
}
And then invoke it on button press.
const handler = async function ( ) {
// or just inline the axios request right here
// instead of declaring a separate function for it.
const result = await fetchData(url);
// do something with the result
}
<Button onPress={handler} />
Apologies for the frequent edits. Trying to do this from my phone.

React Native (Hooks) - How do I 'cancel all subscriptions and asynch tasks in a useEffect cleanup funciton'?

After doing some reading, I'm pretty sure this warning is being caused by a fetch request I'm making in my component. Whenever I navigate away from said component (using React Navigation), I believe my fetch request results in an unresolved promise, caused by navigating away and thus my fetch returning a promise to an unmounted component.
const MakePicker = (props) => {
const [data, setData] = useState([{text: 'Option 1'}]);
const [selectorState, setSelectorState] = useState([{selectedOption: null}]);
setDataHandler = (data) => {
setData(data);
};
setSelectorStateHandler = (selectedOption) => {
setSelectorState({selectedOption: selectedOption})
};
useEffect(() => {
async function fetchMakeData (makeId) {
let response = await fetch('https://myAPIurlGoesHere',
{
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
let fetchedData = await response.json();
return fetchedData;
}
fetchMakeData().then(fetchedData => {
let data = [];
for (const item of fetchedData.data) {
let dataObj = {
text: item.name,
value: item.name,
id: item.id
};
data.push(dataObj);
}
setDataHandler(data);
})
});
return (
<Layout style={styles.container}>
<Select
style={styles.select}
data={data}
placeholder='Select Make'
selectedOption={selectorState.selectedOption}
onSelect={setSelectorStateHandler}
/>
</Layout>
)
}
My component makes a Fetch request as soon as it's mounted. I did this on purpose, I'd like the items in my 'MakePicker' to be populated before the user even clicks the drop down. My problem is I don't know how to 'cleanup' or cancel subscriptions and asynchronous tasks. I know my fetch should go inside of the useEffect function, as this is what creates a side effect, and I know I'm supposed to return a function to 'cleanup' after. I just don't know what that means, or what it would look like. Any help would be greatly appreciated!!

Resources