Optimizing Custom React Hooks - data fetching - react-hooks

I'm very new with React Hooks and I understand that I can use React Hooks inside a function component. I'm used to use Redux instead of Hooks. What I want to achieve is code reuse, where like Redux, you can have actions stored in 1 files and export them, and use them in many class based components. For example, in many components, I need to have a list of available Departments, which I can get easily by initiating with action then read the results in the reducers/store.
Back to reality in Hooks world, I can use useState, useEffect, etc inside every functional component, but that would be very bad practice in terms of code reuse. So I try to create a custom hooks, where I just call the hooks, and I got the result and the component can refresh.
So with this simple create-react-app, I have a custom hooks saved in a file hooks.js like this :
export const useGW = (pcode) => {
const [response, setResponse] = useState({});
console.log("useGW()");
useEffect(() => {
async function fetchGW() {
console.log("fetchGW()");
var options = {
method: 'GET',
headers: { 'content-type': 'application/json', "XSP": pcode },
url: '=====EDITED======'
};
let res = await axios(options)
setResponse(res.data);
}
fetchGW();
},[])
return response
}
and from the App.js I just import it and call it :
function App() {
const response = useGW('dev');
console.log('oGW : ', response);
the problem is, it prints out :
useGW()
oGW : {empty object}
useGW()
oGW : {empty object}
fetchGW()
oGW : {with correct response}
useGW()
oGW : {with correct response}
I don't understand why it is so not efficient that it re-renders 4x ?
How do I make it efficient ?
I've tried to play with [] of useEffect, but the re-render amount is the same.
Thank you in advanced

Related

fetch weatherapi.com with async await react hooks

Can anyone help me why my code doesn't work to fetch API?
I have to build a weather app from several components, must build it structured.
My plan is to have one service component that I have API service in there. then I have to make 3 more components, search component to handle the city search, weatherToday component to show today weather, and weatherForecast component to show five days forecast.
And I have to fetch the API with async an await. Here is the code that I tried(just now I have the code in my App.js just to try if my fetch work)
import { useState, useEffect } from "react";
function App() {
const [data, setData] = useState();
const fetchData = async () => {
await fetch(
`http://api.weatherapi.com/v1/forecast.json?key=1d172d3904e246849d3183628230802&q=Stockholm&days=6&aqi=no&alerts=no`
)
.then((response) => {
return response.json();
})
.then((data) => {
setData(data);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<>
<h3>{data.location.name}</h3>
<p>{data.current.temp_c}</p>
<p>{data.location.localtime}</p>
</>
);
}
export default App;
First of all, welcome to React and StackOverflow!
There's a few issues here:
The main issue is CORS. You can't call this API from your browser - it's meant to be called from a server (backend). I highly recommend using Next.js since you like React, it uses that as it's framework - but it allows you to have Server Components, essentially an Express backend, so that you can perform this API call - then retrieve that data using this client component just to display the data (not to fetch it).
Another issue (but not the problem here) is reusing the data variable in the local scope of then((data) => is not good when you have data defined higher up in the component scope for your state. Use then((d) => instead.
I created a Next.js 13 sandbox for you with this working API call to get you started:
https://codesandbox.io/p/sandbox/broken-field-4nsp1p
In Next.js 13, you can use the app folder, where every component is a Server Component by default. Then you can create Client Component, like the one you have above - you simply have to add use client to the very top of the file, that's it.
Since Next.js 13 is very new (the app folder and concept of Server Components very new and bound to change), you would want to potentially just stick with the pages folder.
In there, you'll see the client component which calls the api folder's getWeather API call.
Finally, you shared your private key with the public. You need to destroy and regenerate that key now:
From the WeatherAPI.com Docs:
Authentication
API access to the data is protected by an API key. If at anytime, you
find the API key has become vulnerable, please regenerate the key
using Regenerate button next to the API key.
https://www.weatherapi.com/docs/
If don't want to use Next.js - then you'll need to use some sort of backend, like Firebase Functions or Google Cloud Functions, etc. Next.js is probably the easiest thing to adapt if you like React though!
Learn about Next.js 13 & /app folder:
https://beta.nextjs.org/docs/getting-started
Learn about Next.js 12 & /pages folder:
https://nextjs.org/docs
Implementing Fetch via Next.js 13
app/head.tsx
export default function Head() {
return (
<>
<title>Weather App</title>
<meta content="width=device-width, initial-scale=1" name="viewport" />
</>
);
}
app.layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>{children}</body>
</html>
);
}
app/page.tsx
const App = async () => {
console.log("App.js");
const results = await fetch(
`http://api.weatherapi.com/v1/forecast.json?key=1d172d3904e246849d3183628230802&q=Stockholm&days=6&aqi=no&alerts=no`
);
const json = await results.json();
console.log("json", json);
return (
<>
<h3>{json.location.name}</h3>
<p>{json.location.temp_c}</p>
<p>{json.location.localtime}</p>
</>
);
};
export default App;
When visiting either / or /weather, you will see the same results, since the example is implemented in both v12 and v13:
--- OR --- Implementing Fetch via Next.js 12
pages/weather.tsx
async function getData() {
const res = await fetch("/api/getWeather");
console.log("res", res);
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
// Recommendation: handle errors
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error("Failed to fetch data");
}
const json = await res.json();
console.log({ json });
return json;
}
export default async function Page() {
const data = await getData();
console.log("data", data);
return (
<main>
<h3>{data.location.name}</h3>
<p>{data.current.temp_c}</p>
<p>{data.location.localtime}</p>
</main>
);
}
pages/api/getWeather.ts
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const results = await fetch(
`http://api.weatherapi.com/v1/forecast.json?key=1d172d3904e246849d3183628230802&q=Stockholm&days=6&aqi=no&alerts=no`
);
const json = await results.json();
console.log("json", json);
res.status(200).send(json);
}
Remember to revoke your exposed secret API key.
I hope this helps you start building your app. Good luck!

redux test Actions must be plain objects. Use custom middleware for async actions

I am not using redux-thunk. this keeps error-ing and I am not sure how to fix it. The examples I see online use redux-thunk which I am not using
my repo is here and the file I am trying to test is in tests\actions\...
My action that is being called in the test
import axios from "axios";
var CancelToken = axios.CancelToken;
let fetch_cancel;
export const FETCH_CATEGORIES = "fetch_categories";
export async function fetchCategories() {
fetch_cancel && fetch_cancel();
const request = await axios.get(
`https://d1i9eedhsgvpdh.cloudfront.net/production-plentific-static/api-cache/find-a-pro/api/v1/categories/all.json`,
{
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
fetch_cancel = c;
})
}
);
return {
type: FETCH_CATEGORIES,
payload: request
};
}
The error message means that your actions must be plain objects. For example:
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
If you make an async request, you can't just return an object, because you need to wait for the request to finish. If you return too early, you return a Promise.
However, I cannot reproduce your error in your sandbox.

How to call an apollo client query from a redux action

If I'm using redux and the apollo client in my app, what's the best way to trigger a query from an action outside of a component.
For example, if I have a standard app, with redux and apollo client configured, how should I trigger a "refresh" list. I can trigger a function on the component itself which has the gql, but how would I do it from an action which would be more in line with flux.
import React, { Component, PropTypes } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { connect } from 'react-redux';
import { refreshProfile } from './actions';
class Profile extends Component { ... }
Profile.propTypes = {
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
user: PropTypes.object,
}).isRequired,
};
const UserQuery = gql`
query getUser {
user {
id
name
}
}
`;
const ProfileWithData = graphql(UserQuery)(Profile);
const ProfileWithDataAndState = connect(
(state) => ({ user: state.user })),
)(ProfileWithData);
And, say I want to trigger an action to refresh that user data? Since the logic is in the component itself, I'm not sure how I would trigger that gql query from the action itself.
I would need to use the ApolloClient in my actions.js. e.g.
import ApolloClient, { createNetworkInterface } from 'apollo-client';
const networkInterface = createNetworkInterface({
uri: config.graphCoolUri,
});
const client = new ApolloClient({
networkInterface,
dataIdFromObject: r => r.id,
});
const { data } = await client.query({
query: UserQuery
});
I see your needs, as I was just in your place couple of days ago.
The sad news is: if you want to use actions with graphQL, then you shouldn't be using apollo, just use graphQL directly. This is a very good article to walk you through - getting started with Redux and GraphQL. Why? Because Apollo uses a function called qraphql(query) which calls its own action.
How both Redux and Apollo work in a very simplistic way.
Redux: (User dispatches an action) ActionCreator --> Action --> Middleware --> reducer --> store --> bind data to user props. And we control each state manually.
Apollo: (User passes the query/mutation to graphql(query)) all hidden (action --> store) then binds data to user props.
You can say that Apollo replaces Redux if you are using graphql, because it has a better integration with react and graphQL.
In the meantime, as Apollo is still developing, you might need redux for redux-form and so on. If you are used to some redux libraries, which you might consider to continue using redux besides Apollo, you can still bind their stores and add costumed middleware that probably apply to both, but you probably won't be fetching data using Redux actions through Apollo.
I know it feels like you are loosing redux, but you are getting all advantages with more async requests and caching taking care of with Apollo.
and if you need a place to start react-redux-apollo.

Wait for an ajax request to complete in React?

Below is my react code I want that firstly the ajax code should execute then the rest of the code should execute.
expected output in console:
inside ajax
outside ajax
current output in console :
outside ajax
inside ajax
import React from 'react';
import request from 'superagent'
const UserItems = () => {
request.get('http://localhost:4000/user/1/items.json')
.then((res, err) => {
if (err) {
console.log("errror found")
}
var data = JSON.parse(res.text)
console.log("inside ajax")
console.log(data)
})
console.log("outside ajax")
console.log(data)
};
export default UserItems;
Any suggestion !!!
As hainguyen points out, ajax is typically asynchronous so the code afterwards will run until the request is complete, at which time the inner function is executed. So the outer console logs will almost certainly run first in your code. While there are ways around this as hainguyen points out, most recommend against it. Ajax is something which takes time, and therefore your code structure should reflect that. If you ever find yourself wanting to run code while the ajax request is in process, you might dislike a synchronous structure. My "I wait for no one" log shows the power of an asynchronous approach since that logic will run quickly while you would normally be waiting on the request without being able to do anything.
Rather than make it synchronous why not use functions to handle the asynchronous behavior better like wrapping whatever you want to run after the inside console log in a function: (I called it outside()) This will output "inside ajax", "outside ajax". This way you can create dependencies on your ajax return and still have the option for running stuff in the meantime.
import React from 'react';
import request from 'superagent';
const UserItems = () => {
request.get('http://localhost:4000/user/1/items.json')
.then((res, err) => {
if (err) {
console.log("errror found");
}
var data = JSON.parse(res.text);
console.log("inside ajax");
console.log(data);
outside();
});
function outside(){
console.log("outside ajax");
console.log(data);
}
console.log("I wait for no one, run me as quick as possible!");
};
export default UserItems;
I don't know about request library but ajax is async by default. If you want ajax perform sync request, you should do something like this:
function getRemote() {
return $.ajax({
type: "GET",
url: remote_url,
async: false
}).responseText;
}
Important line: async: false

How do I load inital set of data with ajax call in React redux?

I have a problem with redux trying to load initial data with an asynchronous call to my backend API that returns a JSON. Right now, I'm trying to load a bunch of different articles, but I have no idea how to load it asynchronously. Since it is an initial set of data, should I load it synchronously? If yes, then how would I acheive a synchronous call to my API? If not, how would I go about solving this problem asynchronously?
Right now, I have static json data, data/articles.js that creates a default state in store.js.
Thanks
You should use a redux-thunk middleware, which allows you to dispatch async actions and a fetch library (for example) for downloading your initial data.
So:
1) create an action which fetch your data, example:
export function fetchData() {
const options = {
method: 'GET',
headers: {
'Authorization': 'Client-ID xx' // if theres any needed
}
}
return (dispatch) => {
return fetch('yourUrl.json', options)
.then(response => response.json())
.then(data => dispatch(receiveYourData(data)))
.catch(err => console.log(err));
}
}
receiveYourData is a action which will place your data in your state, example:
export function receiveYourData (payload = []) {
return {
type: RECEIVE_DATA,
payload: payload
}
}
Of course you have to write action handler, which after dispatching an action, will place your data in your state.
If you have your setup (similar to above), you should dispatch fetchData in your componentDidMount lifecycle method (its one of the option of course :) ).
If you dont know how to do particular parts, you can refer to this Example.
Also official async example may be helpful :)
I also had this problem. It turned out that you have to add a lot of code for this simple task. So I simplified this process and created a package for async loading of initial state in redux - redux-async-initial-state.
You can check out examples and in your case in the end your store creator will look like this:
// Load initial state function
const loadStore = () => {
return Promise(resolve => {
fetch('/data/articles.js')
.then(response => response.json())
.then(resolve);
});
}
const storeCreator = applyMiddleware(asyncInitialState.middleware(loadStore));
const store = storeCreator(reducer);

Resources