How to use hooks within function in React Js - javascript

I have a file where I keep all my functions that make api calls, then I import these functions inside components to use where needed. The issue I'm facing is that when api call is made and it returns 'unauthorized user'(because token has expired) I need to redirect users to the login page.
// apiCalls.js file
export async function getData(){
let response = await fetch(url)
let res = response.json()
// here I need to add redirect to /login if res.status.code is 401 for example
}
I tried to create a custom hook (with useNavigate) to use inside the function, but app throws error saying that hooks can't be used inside function. I can check status of the request inside the component(after I import function from apiCalls.js) but it doesn't seem like a correct way of approaching this as I'll have to add check inside every component that may use this function. Any advise is greatly appreciated
EDITED: to add context, I need to redirect user from a function( not functional component), function is exported from apiCalls.js file.

There's a great way to use hooks inside of a function - make the function a hook! The caveat is that this function will also need to follow the rules of hooks; a big one being the one you've just discovered: you should only be calling it inside a component or other hooks. If you're getting an error because of eslint, you generally also have to (and should) prefix this function with use (like useGetData).
export function useGetData(){
const navigation = useNavigation();
const getData = async (url) => {
let response = await fetch(url)
let res = response.json()
if (/* isInvalidStatus */) {
// navigate to '/login'
}
// return your data
}
return { getData }
}
export function MyComponent() {
const { getData } = useGetData();
// call getData() from useEffect or a click handler
// return jsx;
}
Brief explanation in case it helps:.
First we'll rename the function to follow convention, but we'll also have to remove the async keyword (which we address later). We'll add the useNavigation hook.
export function useGetData() {
// or whatever navigator your router provides
const navigation = useNavigation();
}
The hook itself can't be async, but we can expose a function in the hook's return object:
const getData = async (url) => {
// would probably use const instead of let
const response = await fetch(url);
if (response.status === 401 || response.status === 403) {
navigate('/login');
return;
}
return response.json();
}
return { getData }
And now in the component you can grab getData from useGetData and use it however you want; the auth guard logic will be handled for us in the hook, no matter which component we use it in.

Hooks are JavaScript functions, but you need to follow two rules when using them.
Don’t call Hooks inside loops, conditions, or nested functions.
Don’t call Hooks from regular JavaScript functions.
So if you want to use hook inside a function, change that function into hook

Related

How to call React component function from Electron main.js using contextBridge

As the title states, I would like to call a function defined in one of my React components from the Electron main.js file. I know how to do it the other way around, where for example the user clicks a button and it calls a function in main.js and then returns to the React component. I need it to be the opposite, where main.js calls the React function and then returns the result to main.
I cannot simply have all the functionality in main.js since I cannot pass DOM objects to main from React (it has resulted in the error "object cannot be cloned"). So I figure it shouldn't be too hard to just send the request to React, have the React side of my app do the stuff, and then return the resulting string back to main.
I have found a number of other similar posts here where various solutions are offered, but none specifically for this exact problem. #1 exposes ipcRenderer which is a bad security practice I can't afford. #2 and #3 only explain it for vanilla Javascript, not React components. And while #4 does deal with React, it does not deal with Electron, and it is designed for React classes (I'm using functional components).
My resulting attempt looks like this mess:
const [mounted, setMounted] = useState(false)
if(!mounted){
window.reactFunction = myFunction;
}
useEffect(() =>{
setMounted(true)
},[])
window.electron.receive("fromMain", async () => {
return myFunction();
});
async function myFunction()
{
return "result";
}
However, the function instead gets called during rendering and is not called by the contextBridge. Here is my preload.js:
contextBridge.exposeInMainWorld('electron', {
receive: async (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
return await ipcRenderer.on("fromMain", (event, ...args) => func(...args));
}
}
});
For some reason the preload.js script is not executed. And here is main.js:
const result = window.webContents.send('fromMain');
I have also found this discussion where it is made clear the security issues associated with calling ipcRenderer directly and so I would like to use contextBridge (which I'm already using for calling main functions from React anyway). It's also where my preload.js code comes from. But that example doesn't use React either.
Is this even possible? Or am I supposed to go about this problem a different way?
Looks like I made a typing mistake on this line:
return await ipcRenderer.on("fromMain", (event, ...args) => func(...args));
In this line I'm returning the function, so the function itself is not getting called. Remove the "return" and it will work. "Await" doesn't seem to make a difference.
However, once you do call the function, it can't actually return anything. So you need to make another call from renderer back to main, like this:
window.electron.receive("fromMain", async () => {
const returnValue = await myFunction();
window.electron.sendReturnValue(returnValue);
});
async function myFunction()
{
return "result";
}
Unless there is a still better way of doing it, but this solves my issue for now, and hopefully helps others.

How do I use my custom useFetch hook when a button is clicked?

Semi-new to React. I have a useFetch hook that has all of my fetch functionality, and I want to call it to fetch the next page of items when the user clicks a "load more items" button.
Obviously,
<button onClick={() => { useFetch(...) }}>load more items<button> won't work because I'm calling a custom hook inside a callback. What is the best pattern in general for doing this kind of thing? I can call useFetch in the functional component itself but then fetch will happen when the component mounts, which isn't really what I want.
Everything I've read points to the usefulness of making fetching a hook, but it's just made it harder to work with, especially when I have to fetch small chunks frequently and dynamically.
edit: I may have figured it out, I'll post again with the answer if it works and this post doesn't receive an answer
You shouldn't call a hook from an event handler, as you've noticed yourself.
Your useFetch() hook usually shouldn't fetch any data, but rather return a function that you can call to fetch data afterwards.
Your component would then look something like this:
const MyComponent = () => {
const { fetch, data } = useFetch();
return (<div>
<p>{data}</p>
<button onClick={() => fetch()}>Load more items</button>
</div>);
}
Your useFetch hook is then responsible for
Creating a fetch function.
Returning data once fetch has been called and resolved.
Keeping data in state, and updating data if fetch is called again.
Here's what the useFetch hook might look like
function useFetch() {
const [data, setData] = useState();
const fetch = async () => {
const result = await // call to your API, or whereever you get data from
setData(result.data);
};
return { fetch, data };
}
Ah! The hooks in React.
One way to do this would be to define your functions in the useFetch method which are responsible for making the API calls and then return those functions from the hook to be used wherever you want to.
For example, a useFetch hook would look like this
const useFetch = () => {
const makeApiCall = () => {};
return {
makeApiCall
}
}
and then inside your component
const Comp = () => {
const { makeApiCall } = useFetch();
return (
<Button onClick={makeApiCall} />
)
}
You can always pass some relevant info to the hook and return more relevant data from the hook.

Is it possible to use async await with regular function which is not marked async in react?

Please note, below code is not actual code. There`s lot omitted for bravity.
I am using react with typescript and all my components are functional components.
I have following function which is like
export const filterData = (data) => {
// do lots of filtering on data, does not make any api calls.
// only do Array.filter, map etc to remove unwanted data
};
This function is defined in separate utils file and imported in my react component. I am using it as following
//Fetch data using hook from API
const receivedDataFromAPI = useDetails(detailsParams);// Data is already received here. Verified that data is coming from network tab and console log
const cleanedData = allFilter(receivedDataFromAPI);
const allFilter = async (receivedData: IData) => {
const data = await filterData(receivedData);
// other code
}
Earlier I was not using it with async await but then, execution was not waiting for filterData to return result. After doing lots of trial and error, I came to async await and code is working fine now. The question is above pattern ok or I need to make filterData function async?

React Hooks: setState, when does it actually take effect?

So I'm trying to understand React Hooks and how to use them. I can make a fetch inside a component as follows:
var [pages,setPages] = useState();
var [page,setPage] = useState();
async function fetchData() {
await fetch(`https://myBackend/pages`)
.then(response => response.json())
.then(response => {
setPages(response);
console.log(pages[0].title);
})
.then(()=>{
// AT THIS STAGE, pages IS UNDEFINED
setPage(pages.filter(singlepage=> {
return singlepage.url == props.match.params.url;
}))
}
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
console.log(pages[0].title);
return () => {
console.log('unmounting...');
}
},[]);
I call to my backend to get all the pages, which works fine, as if I harcode pages[0].title it will render. But when I'm trying to access specific values in pages within the useEffect hook, for me to assign page, it gives me the error of pages being undefined. My console logging makes this apparent.
I want page as well as pages to be defined on 'component mounting', prior to the page loading. So my question is when does setPages actually set the page? and is it possible for me to assign both within useEffect, and based off each other?
Asynchronous actions take time. An indeterminate amount of time could theoretically pass. So, there's no way to guarantee that your data fetches before mounting your component.
Instead, you need to act as if the data could be undefined and have a state of the application that handles that.
As for getting access to the pages variable immediately after calling setPages, that will also fail because React actually runs the re-render asynchronously.
Even if it ran at the same time, there's a thing called closures in which javascript pulls in all variables around a function when it is created, so that the function always has access to those. React Hooks work by utilizing these closures to only have access to the variables as they were when the component was rendered/re-rendered. The reason this is the case, is because every re-render, all of the functions in your component are re-created which creates a new closure.
So, this means that you'll need to keep these concepts in mind as you work with React.
As for your code, the results solution that Dlucidione set up is one of your best bets, aside from setting up a separate useEffect to update page when pages changes.
const history = useHistory(); //from react-router-dom
useEffect(() => {
async function fetchData() {
return await fetch(`https://myBackend/pages`)
.then((response) => response.json())
.then((response) => {
setPages(response);
})
.catch((err) => setErrors(err));
}
fetchData();
return () => {
console.log('unmounting...');
};
}, []);
useEffect(() => {
const url = props.match.params.url;
if (!pages || !url) return;
// Using Array#find here because I assume based on the name, you want one page rather than an array
const page = pages.find((singlepage) => {
return singlepage.url === url;
});
if (!page) {
// This will change the route if a page isn't found.
history.push('/404');
}
setPage(page);
}, [pages, props.match.params.url, history]);
Redirect and history.push are different in how they work. Redirect is the declarative navigation method, while history.push is the programmatic.
Example usage of history.push:
if (!page) {
// This will change the route if a page isn't found.
history.push('/404');
}
Important note, this can be used anywhere in the code as long as you can pass the history object to there. I've used it within redux thunks before as well.
Example usages of Redirect without it redirecting on mount:
if(!pages && !page){
return <Redirect to="/404"/>
}
Or within some JSX:
<div>
{!pages && !page && <Redirect to="/404"/>}
</div>
Redirect has to be rendered which means its only usable within the return statement of a component.
The way I'm understanding it: the redirect is based on if the
individual page is found in the mounting process, therefore the
redirecting process has to also be in the mounting process to check
before rendering anything to redirect or not.
This is correct. When the Redirect itself mounts or updates, it will redirect if the conditions are correct. If there's a path or from prop on the Redirect (they are aliases of each other), then that limits the Redirect to only work when that path matches. If the path doesn't match, the redirect will do nothing until the path is changed to match (or it unmounts, of course).
Under the hood, Redirect just calls history.push or history.replace in componentDidMount and componentDidUpdate (via the Lifecycle component), so take that as you will.
useEffect(() => {
const fetchData = async () => {
try {
// Make a first request
const result = await axios.get(`firstUrl`);
setPages(result);
// here you can use result or pages to do other operation
setPage(result.filter(singlepage=> {
return singlepage.url == props.match.params.url;
or
setPage(pages.filter(singlepage=> {
return singlepage.url == props.match.params.url;
}))
} catch (e) {
// Handle error here
}
};
fetchData();
}, []);

Issues with asynchronous nature of redux in React?

I'm pulling data into one of my parent components and then using various filter statements which are based on user choices from select boxes. I'm then calling an action which simply stores that filtered data based on the users search into global state so that my child components can access them.
One of my child components is supposed to render the results but what is happening is the results being rendered are lagging one action behind. I've encountered similar issues when using set state and my solution then was to use a callback but I'm not exactly sure how to go about dealing with this issue in this situation with redux.
The wordpress.get is just named import of axios config.
componentDidMount = async () => {
const response = await wordpress.get(
"*********************/api/wp/v2/variants?per_page=100"
);
this.props.fetchData(response);
const data = []
response.data.forEach(ele => {
data.push(ele)
})
this.props.sendFilteredView(data);
};
handleChange = () => {
this.preBuiltFiltering();
};
I've left out pre-built filtering because its long and excessive, all it does is run the filter based on the users choices and then dispatches the this.props.sendFilteredView action with the filtered data set as the argument. The action just returns the payload.
I then am rendering the results of the filter in a child component by accessing the global state (I also tried just passing it directly through props, same issue).
It’s an async function, you’re using a callback after the forEach with data.
So you need to wait forEach been completed.
Try to use await before forEach.
componentDidMount = async () => {
const response = await wordpress.get(
"*********************/api/wp/v2/variants?per_page=100"
);
this.props.fetchData(response);
const data = []
await response.data.forEach(ele => {
data.push(ele)
})
this.props.sendFilteredView(data);
};
handleChange = () => {
this.preBuiltFiltering();
};

Categories

Resources