React Apollo useQuery - how it works - reactjs

Trying to understand how useQuery executes in background. When the Booklist component is rendered first , booklist function is called and usequery hook being called it returns loading set to true and starts executing the graphql request in the background. When it retrieves response from server it updates the properties and the component gets rerendered again , the function BookList is being called again from the begining. What happens when useQuery being called again does it place another request or takes the data from cache? How does it work.
Pasted the code below
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import { useQuery } from "#apollo/react-hooks";
import gql from "graphql-tag";
function BookList() {
console.log('Inside booklist')
const { loading, error, data } = useQuery(gql`
{
countries {
full_name_english
full_name_locale
}
}
`);
console.log('Inside booklist1')
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
console.log('iam here22');
console.log(data);
// return (<div>completed</div>);
return data.countries.map(({ full_name_english, full_name_locale },index) => (
<div key={index}>
<p>
{ full_name_english}: {full_name_locale}
</p>
</div>
));
}
export default BookList;
App.js
function App() {
return (
<ApolloProvider client={client}>
<div id="main">
<h3> TEST APP</h3>
<MyTest />
<BookList/>
</div>
</ApolloProvider>
);
}
export default App;

useQuery is a custom hook provided by apollo and it uses react hooks internally.
Now the way react hooks work is that when the first time they are rendered they use the values passed as arguments to them and store the result in memory
Now whenever the component renders is future, the arguments from the hooks are not used but the value from cache is returned. Any update to the value using the setter methods is being applied to the in memory results
Now useQuery also works on the same principle, the query passed to it is only used once and on future renders the value from cache is returned
You can check in the react docs about lazy initial state

Related

How to set a components state from another component?

Here I have a weather app which uses an API in it's own component to fetch weather data. I'm at the point now where I'm trying to append the retrieved data values to the DOM/webpage but am having trouble. I have declared state in my Header component (working with only the Temp right now until it works) but can't figure how to manipulate it.
Things I've tried and the outcomes: I have tried putting the state into the top level as well as the API function (so it was all in App.js) but this didn't work because I couldn't call the nested function and if I make it global, it doesn't recognize the state variable. I also tried creating a function that explicitly changes state and using /importing that but I run into the same issue.
I have also theorized about maybe instead of trying to send the state to other components, to send the data to the component that holds the state (header) but I'm not sure on this since it's to my understanding that data should only live in one place? I appreciate any help.
APP.js
`
import logo from './logo.svg';
import './App.css';
import Header from './components/header'
import Body from './components/body'
import Footer from './components/footer'
import { useState } from 'react';
function App() {
return (
<div className="container">
<Header />
<Body />
<Footer />
</div>
);
}
export default App;
api.js
import {Header} from '../components/header'
export async function FetchAPI(location) {
try {
let result = await fetch ('http://api.openweathermap.org/data/2.5/weather?q=' + location +'&APPID=bd3dd8d1151b1e784fcf021aa29927c5',
{mode: 'cors'});
let final = await result.json()
return console.log(final.cod)
}
catch(err) {
alert(err)
}
}
export async function processData(data) {
console.log(data)
}
Header.js
import {processData} from "../api/api"
import {useState} from 'react'
export function Header({tempValue, locationValue}) {
const handleTemp = (newValue) => { setTemp(newValue) }
const [tempDOM, setTemp] = useState(2)
return (
<header className="header">
<div>
<h1 value={setTemp}> </h1>
<h2></h2>
</div>
<div>
<h1>Time</h1>
<h2>Windspeed</h2>
</div>
</header>
);
}
export default Header;
`
I have tried using state and changing that using a function as well as nesting the state and function that renders the data together in a top level component.
While not necessary for this example, I would suggest you check out react stores. My personal favorite is MobX - they can make managing your data a lot easier and reduce the amount of states and things you need to pass through to each components.
Now for your code here are a few notes:
You don't seem to call FetchAPI from anywhere. You should either call it on page load (in App.js) or call it inside your Header component when the location changes
Your FetchAPI also seems to return a console.log() not an actual value. I would suggest you return final.cod or whatever it is that you need returned. Perhaps even the entire json (return final)
Inside your h1 tag, you are setting the value to a set method of the state - this won't work. in useState, if you save it to an array, the first value (tempDOM) is the value of the state, and the second value is a method that is used to change the state value. So instead, as a comment has already suggested, use <h1>{tempDOM}</h1>. This will display the initial value you set (2) and nothing else since you are not changing the state value via setTemp() method
This is what I would expect the code to look like (haven't tested):
import './App.css';
import Header from './components/header'
import Body from './components/body'
import Footer from './components/footer'
function App() {
return (
<div className="container">
<Header location='London'/>
<Body />
<Footer />
</div>
);
}
export default App;
import {FetchAPI} from "../api/api"
import {useState, useEffect} from 'react'
export function Header({location}) {
const [apiData, setApiData] = useState({});
useEffect(()=>{
async function getApiData() {
const data = await FetchAPI(location);
setApiData(data);
}
getApiData();
// including location in useEffect dependency array,
// which means this effect will be called every time location property changes
}, [location]);
return (
<header className="header">
<div>
{
// I don't know what your API model looks like.
// adjust the property that you are accessing based on your knowledge
}
<h1>{apiData.temp}</h1>
<h2></h2>
</div>
<div>
<h1>Time</h1>
<h2>Windspeed</h2>
</div>
</header>
);
}
export default Header;
export async function FetchAPI(location) {
try {
const result = await fetch ('http://api.openweathermap.org/data/2.5/weather?q=' + location +'&APPID=bd3dd8d1151b1e784fcf021aa29927c5',
{mode: 'cors'});
const final = await result.json()
return final
}
catch(err) {
alert(err)
}
}
As mentioned, I haven't tested this, but this is more along the lines of what I would expect a working application to look like.
More on useEffect

Calling ApolloClient GraphQl request inside componentDidMount method

I am using ApolloClient GraphQl query inside react class to fetch data from server:
import React, {Component} from 'react';
import {useCompanyLogo} from '../../queries/companyLogo';
class Logo extends Component {
constructor() {
super();
this.state = {logo: ""};
}
componentDidMount() {
const {error, loading, data} = useCompanyLogo();
if(loading) return <div>spinner</div>
if(error) return <div>error!</div>
const imageSource = data.companyLogo[0].image.urls[0];
this.setState({logo: imageSource});
}
render() {
return (
<div className="logo-area">
<img src={"http://computer-313:5000" + this.state.logo} alt="Businex-Logo" style={{width:"80px"}} />
</div>
);
}
}
export default Logo;
And the query is as below:
import {useQuery, gql} from "#apollo/client";
var COMPANY_LOGO = gql`
query CompanyLogo {
companyLogo {
image {
urls(first: 1)
}
}
}
`;
export const useCompanyLogo = () => {
const {error, data, loading} = useQuery(COMPANY_LOGO);
console.log(error, data, loading);
return {
error,
data,
loading
}
}
Everything works good when I use function instead of class But when I run this code I get the following error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
According to the React.js documentation you cannot use Hooks inside of Class Components.
You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.
You can try to use high order components and be able to pass the hooks into your Class Component that way.

React JS coponent not rendering using map function

I hava a component called videoRow i try to render this component using dummy values now i get data from a useEffect Hook i have to use that data to render my component but when i try to do so it dont show anything. I even try console log to check weather i get my data or not it print my data on console means my useEffect is working But when i try this data on my videoRow component it not show anything
import React, { useState, useEffect } from "react";
import "../css/searchPage.css";
import TuneSharpIcon from "#mui/icons-material/TuneSharp";
import ChannelRow from "./ChannelRow";
import VideoRow from "./VideoRow";
import { selectInput } from "../features/inputSlice";
import { useSelector } from "react-redux";
import Axios from "axios";
function SearchPage() {
const getQuery = useSelector(selectInput);
const API_URL = `https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=4&key=APIKEY&type=video&q=${getQuery.input}`;
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
let request = await Axios.get(API_URL);
setData(request);
}
fetchData();
}, [API_URL]);
console.log(data);
return (
<div className="searchPage">
<div className="filter">
<TuneSharpIcon></TuneSharpIcon>
<h2>FILTERS</h2>
</div>
<hr></hr>
<ChannelRow
image="https://images.indianexpress.com/2022/01/Republic-Day_1200_AP2022.jpg"
channelName="Dummy"
verified
subs="670k"
noOfVideos={567}
desc="You can find awesome programming lessons here! Also, expect programming tips and tricks that will take your coding skills to the ..."
></ChannelRow>
<hr></hr>
{data?.data?.items?.forEach((item) => {
console.log(item.snippet.title);
console.log(item?.snippet.thumbnails.high.url)
console.log(item?.snippet.publishedAt)
console.log(item?.snippet.description)
console.log(item?.snippet.channelTitle)
return(<VideoRow
image={item?.snippet.thumbnails.high.url}
channelName={item?.channelTitle}
timestamp={item?.snippet.publishedAt}
title={item?.snippet.title}
desc={item?.snippet.description}
views="1.4M"
subs="1.4M"
></VideoRow>)
})}
</div>
);
}
export default SearchPage;
Change data?.data?.items?.forEach to data?.data?.items?.map. forEach returns nothing. So, even if you return the component from the callback, forEach will just ignore it. But, map will return all transformed results as an array.
You can read more about lists in react here.

Reactjs redux store changes but in component's props doesn't change

I'm tryin to show navigation depends on changes of categoryURL from
redux store and changing the state in other components. Redux changes the store and it works fine. But in my
component "this.props.categoryUrl" doesn't reflect on value. Can't
find out why?
import React, {Component} from 'react';
import NavigationItems from './NavigationItems/NavigationItems';
import classes from './Navigation.module.css';
import {connect} from 'react-redux';
const mapStateToProps = state => {
console.log(state)
return {
categoryURL: state.categoryUrl
};
};
class navigation extends Component {
componentDidMount() {
console.log(this.props.categoryUrl);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('NAVIGATION!', this.props.categoryUrl);
}
render() {
let nav = null;
if (this.props.categoryUrl) {
nav = (
<div className={classes.Navigation}>
<NavigationItems/>
</div>
)
}
return (
<>
{nav}
</>
)
}
}
export default connect(mapStateToProps, null)(navigation);
In "normal" React it is needed to use <Navigation/> (Capital letter at the beginning) instead of <navigation/>. Also, If <Navigation/> is being used by another React component then it might be needed to add code that will be executed inside <Navigation/> to refresh the component where you are using <Navigation/> (some kind of callback passed to <Navigation/>). It is this the way or move all the <Navigation/>'s code to the component where you are using <Navigation/>. This is how I solved this kind of problem.

React.js - how to pass event handlers to deeply nested component without props drilling?

I have the structure of components (nested) that seems like this:
Container
ComponentA
ComponentB
ComponentC(want to handle event here with state that lives on container)
Do I need to pass as props all the way from Container, ComponentA, ComponentB and finally ComponentC to have this handler? Or is there another way like using Context API?
I'm finding a bit hard to handle events with react.js vs vue.js/angular.js because of this.
I would recommend using either Context API (as you mentioned) or Higher Order Components (HoC)
Context Api is your data center. You put all the data and click events that your application needs here and then with "Consumer" method you fetch them in any component regardless of how nested it is. Here is a basic example:
context.js //in your src folder.
import React, { Component, createContext } from "react";
import { storeProducts } from "./data"; //imported the data from data.js
const ProductContext = createContext(); //created context object
class ProductProvider extends Component {
state = {
products: storeProducts,
};
render() {
return (
<ProductContext.Provider
//we pass the data via value prop. anything here is accessible
value={{
...this.state,
addToCart: this.addToCart //I wont use this in the example because it would
be very long code, I wanna show you that, we pass data and event handlers here!
}}
>
// allows all the components access the data provided here
{this.props.children},
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Now we set up our data center with .Consumer and .Provider methods so we can access
here via "ProductConsumer" in our components. Let's say you want to display all your products in your home page.
ProductList.js
import React, { Component } from "react";
import Product from "./Product";
import { ProductConsumer } from "../context";
class ProductList extends Component {
render() {
return (
<React.Fragment>
<div className="container">
<div className="row">
<ProductConsumer>
//we fetch data here, pass the value as an argument of the function
{value => {
return value.products.map(product => {
return <Product key={product.id} />;
});
}}
</ProductConsumer>
</div>
</div>
</React.Fragment>
);
}
}
export default ProductList;
This is the logic behind the Context Api. It sounds scary but if you know the logic it is very simple. Instead of creating your data and events handlers inside of each component and prop drilling which is a big headache, just put data and your event handlers here and orchestrate them.
I hope it helps.

Resources