useReactVar in apolloLink cause cannot update component while rendering a diffrend component - javascript

i use apollo client and react in project
i want appear loading indicator while network request, so i set react variables and change it inside apollo link
but it cause cannot update component(Indicator) while rendering a diffrend component(component that call useQuery hook)
loadingLink.ts
import { ApolloLink } from '#apollo/client';
import { loadingVar } from 'gql/store/reactiveVariables';
export const loadingLink = new ApolloLink((operation, forward) => {
loadingVar(true);
return forward(operation).map(data => {
loadingVar(false);
return data;
});
});
loadingIndicator.ts
import React from 'react';
import { useReactiveVar } from '#apollo/client';
import { loadingVar } from 'gql/store/reactiveVariables';
import { Indicator } from './Indicator';
const LoadingIndicator: React.FC = () => {
const loading = useReactiveVar(loadingVar);
if (!loading) return null;
return <Indicator />;
};
export default LoadingIndicator;
error message
error message
BatchList.tsx
const BatchList = () => {
const {data, error} = useQuery(~~~);
if(error) return null;
if(loading || !data) return null;
return ~~~~
}
in documentation, they say should use useEffect hook but inside ApolloLink i can't use useEffect hook
How can i fix it

Related

React Native: Error: Invalid hook call. Hooks can only be called inside of the body of a function component

in my react native app I have started playing with custom hooks, I created a hook to retrive user coordinates, however when my useGeolocation hook is called (inside handleUpdateLocation method) I always get the following warning:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
Actually, all my components are functional components...
Here is my component:
//import * as React from 'react';
import { View, Text, StyleSheet,TouchableOpacity, Platform,PermissionsAndroid, BackHandler, ScrollView, TextInput, ActivityIndicator ,SafeAreaView} from 'react-native';
import React, { useState, useEffect } from 'react'
import { observer } from 'mobx-react'
import useGeolocation from '#hooks/useGeolocation.js';
export default OrderCard14 = observer((props) =>
{
const handleUpdateLocation = async (gender) =>
{
const { coordinates, city } = useGeolocation(true);
};
return(
<View style={{ flex:1,backgroundColor:'white',alignItems:'center',padding:0 }}>
</View>
);
});
And my simplified hook(removed some functions):
//import AsyncStorage from '#react-native-async-storage/async-storage';
import { AsyncStorage ,Platform, Alert, PermissionsAndroid } from 'react-native';
import _ from 'lodash';
import env from '#env/vars';
import http from '#env/axiosin';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
const useGeolocation = (getCityName = false) => {
const root = useStore();
const [coordinates, setCoordinates] = useState(false);
const [city, setCity] = useState(false);
const requestLocationPermissions = async () =>
{
console.log('getting new coordinates');
if (Platform.OS === 'ios')
{
const auth = await Geolocation.requestAuthorization("whenInUse");
if(auth === "granted")
{
//root.mapStore.setLocationEnabled(true);
this.locationEnabled = true;
let coords = await getMe(getCityName);
return coords;
/*
const todayId = moment().isoWeekday();
if(todayId != root.userStore.user.lastLoginDayId)
{
getMe();
}
*/
}
else
{
//Alert.alert('PLEASE ENABLE LOCATION');
root.mapStore.setLocationEnabled(false);
//this.locationEnabled = false;
}
}
if (Platform.OS === 'android')
{
let granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
if (granted === PermissionsAndroid.RESULTS.GRANTED)
{
// do something if granted...
//this.loactionEnabled = true;
root.mapStore.setLocationEnabled(true);
let coords = await getMe();
return coords;
/*
const todayId = moment().isoWeekday();
if(todayId != root.userStore.user.lastLoginDayId)
{
getMe();
}
*/
}
else
{
//Alert.alert('KULO');
root.mapStore.setLocationEnabled(false);
//this.locationEnabled = false;
}
}
}
useEffect(() => {
requestLocationPermissions();
}, []);
console.log('returning new coordinates with hook: '+coordinates);
return { coordinates, city };
};
export default useGeolocation;
What's exactly the problem?
Invalid hook call. Hooks can only be called inside of the body of a function component.
I think they should add to the list of possible/common reasons that:
You might be conditionally invoking the hook call on any given render, or not invoking it at all.
Hooks not only need to be inside the body of a function component, they need to be at the level of the body of the function component. Always called on every render, always in the same order. You have one that's inside a function:
const handleUpdateLocation = async (gender) =>
{
const { coordinates, city } = useGeolocation(true);
};
Which is invalid. Instead, move the hook call to the component level. You can still use the resulting values inside the function:
const { coordinates, city } = useGeolocation(true);
const handleUpdateLocation = async (gender) =>
{
// here you can still use coordinates and city
};
In general, it's a good rule of thumb to invoke all of the hooks that you'll need as the very first thing you do in any given component.

Server Error Error: Invalid hook call. Hooks can only be called inside of the body of a function component in _app.js

I am a newbie in React and Next JS, I want to set initial auth user data on initial load from the __app.js. But using dispatch throwing error "Invalid hook call". I know according to docs calling hooks in render function is wrong. but I am looking for an alternate solution to this.
How I can set auth data one-time so that will be available for all the pages and components.
I am including my code below.
/contexts/app.js
import { useReducer, useContext, createContext } from 'react'
const AppStateContext = createContext()
const AppDispatchContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case 'SET_AUTH': {
return state = action.payload
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
return (
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
export const useAuth = () => useContext(AppStateContext)
export const useDispatchAuth = () => useContext(AppDispatchContext)
/_app.js
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import App from 'next/app'
import Layout from '../components/Layout'
import { mutate } from 'swr'
import { getUser } from '../requests/userApi'
import { AppProvider, useDispatchAuth } from '../contexts/app'
class MyApp extends App {
render() {
const dispatchAuth = useDispatchAuth()
const { Component, pageProps, props } = this.props
// Set initial user data
const setInitialUserData = async () => {
if (props.isServer) {
const initialData = {
loading: false,
loggedIn: (props.user) ? true : false,
user: props.user
}
const auth = await mutate('api-user', initialData, false)
dispatchAuth({
type: 'SET_AUTH',
payload: auth
})
}
}
//----------------------
// Set initial user data
setInitialUserData()
//----------------------
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}
}
MyApp.getInitialProps = async (appContext) => {
let isServer = (appContext.ctx.req) ? true : false
let user = null
let userTypes = {}
// Get user server side
if (isServer) {
await getUser()
.then(response => {
let data = response.data
if (data.status == true) {
// Set user
user = data.data.user
userTypes = data.data.user_types
//---------
}
})
.catch(error => {
//
})
}
//---------------------
return {
props: {
user,
userTypes,
isServer
}
}
}
export default MyApp
I believe this is the intended use of the useEffect hook with an empty array as its second argument:
https://reactjs.org/docs/hooks-effect.html
import {useEffect} from 'react'
class MyApp extends App {
useEffect(()=> {
setInitialUserData()
},[])
render() {
...
}
}

Too many re-renders. React limits the number of renders to prevent an infinite loop when passing states?

I am trying to access state from one component to another
FetchCompo.js
// import React from "react";
import React, { useState, useEffect } from "react";
//more imports
const FetchUserItems= () => {
//some state
const [userFirstName, setUserFirstName] = useState("");
const [userItem, setUserItem] = useState([]);
let userName = //somecode
setUserFirstName(userName);
let userItemsData= userData.MyArray.items;
if (userItemsData.length === 0) {
const emptyItems = [
{
//obj data
},
];
setUserItem(emptyItems );
} else {
//someData
setUserItem(userItemsData);
}
return { userFirstName, userItem};
};
export default FetchCompo;
I wanted to use userFirstName, userItem in the another Test.js component.
// import React from "react";
import React, { useState, useEffect } from "react";
import FetchCompofrom "../myFunctions/FetchCompo";
//more imports
const Test = () => {
//Wanted to use userFirstName in Test.js component
const { userFirstName, userItem } = FetchCompofrom();
return (
<div>{userFirstName}</div>
)
}
when I am trying to get the userFirstName, userItem in the Test.js component then getting error of Too many renders
looking for a solution how i can access these state userFirstName, userItem form one component to another.
You're actually importing the React Component not the FetchUserItems helper function...
import FetchCompofrom "../myFunctions/FetchCompo";
But you could do something like...
const [userFirstName, setUserFirstName] = useState('');
const [userItem, setUserItem] = useState([]);
const FetchUserItems = () => {
/**
* Make it plain helper function for fetching userItems
* Do-not set-state here...
*/
return { userFirstName, userItem };
};
export const FetchUserItems;
/** In your component ... say in useEffect */
const result = FetchUserItems();
/** setState here in case of result */
In Test.js
import { FetchUserItems } "../myFunctions/FetchCompo";
Your are using setUserFirstName(userName) in FechUserItems, outside useEffect or normal function, this will provoque the component to re-render indefinitely because setting the states provoques re-rendering.
I would suggest to make FetchUserItems a normal function, because you are not rendering anything in it. You could use only Test comp for it.
The Test comp would be something like this:
// import React from "react";
import React, { useState, useEffect } from "react";
import FetchCompofrom "../myFunctions/FetchCompo";
//more imports
const Test = () => {
const [userFirstName, setUserFirstName] = useState("");
const [userItem, setUserItem] = useState([]);
useEffect(() => fetchUserFirstName, [])
const fetchUserFirstName = () => {
// your code here and
// setUserFirstName in the end
}
//Wanted to use userFirstName in Test.js component
const { userFirstName, userItem } = FetchCompofrom();
return (
<div>{userFirstName}</div>
)
}

React Hooks/Context & Elastictic UI. Problem with fetched data (REST) in function Component

I'm quite new to React Hooks/Context so I'd appreciate some help. Please don' t jump on me with your sharp teeth. I Checked other solutions and some ways i've done this before but can't seem to get it here with the 'pick from the list' way.
SUMMARY
I need to get the municipios list of names inside of my const 'allMunicipios'(array of objects) inside of my Search.js and then display a card with some data from the chosen municipio.
TASK
Get the data from eltiempo-net REST API.
Use Combobox async element from Elastic UI to choose from list of municipios.
Display Card (from elastic UI too) with some info of chosen municipio.
It has to be done with function components / hooks. No classes.
I'd please appreciate any help.
WHAT I'VE DONE
I've created my reducer, context and types files in a context folder to fecth all data with those and then access data from the component.
I've created my Search.js file. Then imported Search.js in App.js.
I've accesed the REST API and now have it in my Search.js
PROBLEM
Somehow I'm not beeing able to iterate through the data i got.
Basically i need to push the municipios.NOMBRE from api to the array const allMunicipios in my search.js component. But when i console log it it gives me undefined. Can;t figure out why.
I'll share down here the relevant code/components. Thanks a lot for whoever takes the time.
municipiosReducer.js
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
export default (state, action) => {
switch (action.type) {
case SEARCH_MUNICIPIOS:
return {
...state,
municipios: action.payload,
};
case GET_MUNICIPIO:
return {
...state,
municipio: action.payload,
};
case CLEAR_MUNICIPIOS:
return {
...state,
municipios: [],
};
case GET_WEATHER: {
return {
...state,
weather: action.payload,
};
}
default:
return state;
}
};
municipiosContext.js
import { createContext } from "react";
const municipiosContext = createContext();
export default municipiosContext;
MunicipiosState.js
import React, { createContext, useReducer, Component } from "react";
import axios from "axios";
import MunicipiosContext from "./municipiosContext";
import MunicipiosReducer from "./municipiosReducer";
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
const MunicipiosState = (props) => {
const initialState = {
municipios: [],
municipio: {},
};
const [state, dispatch] = useReducer(MunicipiosReducer, initialState);
//Search municipios
//In arrow functions 'async' goes before the parameter.
const searchMunicipios = async () => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios`
// 08 means barcelona province. This should give me the list of all its municipios
);
dispatch({
type: SEARCH_MUNICIPIOS,
payload: res.data.municipios,
});
};
//Get Municipio
const getMunicipio = async (municipio) => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios/${municipio.CODIGOINE}`
//CODIGOINE is in this REST API kind of the ID for each municipio.
//I intent to use this later to get the weather conditions from each municipio.
);
dispatch({ type: GET_MUNICIPIO, payload: res.municipio });
};
const dataMunicipiosArray = [searchMunicipios];
//Clear Municipios
const clearMunicipios = () => {
dispatch({ type: CLEAR_MUNICIPIOS });
};
return (
<MunicipiosContext.Provider
value={{
municipios: state.municipios,
municipio: state.municipio,
searchMunicipios,
getMunicipio,
clearMunicipios,
dataMunicipiosArray,
}}
>
{props.children}
</MunicipiosContext.Provider>
);
};
export default MunicipiosState;
Search.js
import "#elastic/eui/dist/eui_theme_light.css";
import "#babel/polyfill";
import MunicipiosContext from "../contexts/municipiosContext";
import MunicipiosState from "../contexts/MunicipiosState";
import { EuiComboBox, EuiText } from "#elastic/eui";
import React, { useState, useEffect, useCallback, useContext } from "react";
const Search = () => {
const municipiosContext = useContext(MunicipiosContext);
const { searchMunicipios, municipios } = MunicipiosState;
useEffect(() => {
return municipiosContext.searchMunicipios();
}, []);
const municipiosFromContext = municipiosContext.municipios;
const bringOneMunicipio = municipiosContext.municipios[0];
let municipiosNames = municipiosFromContext.map((municipio) => {
return { label: `${municipio.NOMBRE}` };
});
console.log(`municipiosFromContext`, municipiosFromContext);
console.log(`const bringOneMunicipio:`, bringOneMunicipio);
console.log(`municipiosNames:`, municipiosNames);
const allMunicipios = [
{ label: "santcugat" },
{ label: "BARCELONETA" },
{ label: "BARCE" },
];
const [selectedOptions, setSelected] = useState([]);
const [isLoading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
let searchTimeout;
const onChange = (selectedOptions) => {
setSelected(selectedOptions);
};
// combo-box
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, []);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
return (
<div>
<EuiComboBox
placeholder="Search asynchronously"
async
options={options}
selectedOptions={selectedOptions}
isLoading={isLoading}
onChange={onChange}
onSearchChange={onSearchChange}
/>
<button>Lista de municipios</button>
</div>
);
};
export default Search;
also the
Home.js
import React, { useState } from "react";
import { EuiComboBox, EuiText } from "#elastic/eui";
// import { DisplayToggles } from "../form_controls/display_toggles";
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import Search from "./Search";
import MunicipioCard from "./MunicipioCard";
const Home = () => {
return (
<div>
<EuiText grow={false}>
<h1>Clima en la provincia de Barcelona</h1>
<h2>Por favor seleccione un municipio</h2>
</EuiText>
<Search />
<MunicipioCard />
</div>
);
};
export default Home;
App.js
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import { EuiText } from "#elastic/eui";
import React from "react";
import Home from "./components/Home";
import MunicipiosState from "./contexts/MunicipiosState";
import "./App.css";
function App() {
return (
<MunicipiosState>
<div className="App">
<EuiText>
<h1>App Component h1</h1>
</EuiText>
<Home />
</div>
</MunicipiosState>
);
}
export default App;
You are using forEach and assigning the returned value to a variable, however forEach doesn't return anything. You should instead use map
let municipiosNames = municipiosFromContext.map((municipio) => {
return `label: ${municipio.NOMBRE}`;
});
As per your comment:
you data is loaded asynchronously, so it won't be available on first render and since functional components depend on closures, you onSearchChange function takes the value from the closure at the time of creation and even if you have a setTimeout within it the updated value won't reflect
The solution here is to add municipiosFromContext as a dependency to useEffect
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, [municipiosFromContext]);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);

Custom hook error: Hooks can only be called inside of the body of a function component

I am trying to develop a custom hook which seems to be pretty easy but I am getting an error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
This is my hook:
import React, { useState, useEffect } from 'react';
const useInfiniteScroll = (isLastPage: boolean, fetchFn: any) => {
const [pageCount, setPageCount] = useState(0);
const triggerFetchEvents = (): void => {
let response;
setPageCount(() => {
if (!isLastPage) {
response = fetchFn(pageCount + 1, 5, 'latest');
}
return pageCount + 1;
});
return response;
};
useEffect(() => {
triggerFetchEvents();
}, []);
return pageCount;
};
export default useInfiniteScroll;
And the component here I am calling it:
import React, { FC } from 'react';
import { connect } from 'react-redux';
import { fetchEvents } from '../../shared/actions/eventActions';
import { AppState } from '../../shared/types/genericTypes';
import EventModel from '../../shared/models/Event.model';
import EventListPage from '../../components/events/EventListPage';
import useInfiniteScroll from '../../shared/services/triggerInfiniteScroll';
type Props = {
fetchEvents?: any;
isLastPage: boolean;
eventsList?: EventModel[];
};
const mapState: any = (state: AppState, props: Props): Props => ({
eventsList: state.eventReducers.eventsList,
isLastPage: state.eventReducers.isLastPage,
...props
});
const actionCreators = {
fetchEvents
};
export const EventsScene: FC<Props> = props => {
const { eventsList, fetchEvents, isLastPage } = props;
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// const [pageCount, setPageCount] = useState(0);
// const triggerFetchEvents = (): void => {
// let response;
// setPageCount(() => {
// if (!isLastPage) {
// response = fetchEvents(pageCount + 1, 1, 'latest');
// }
// return pageCount + 1;
// });
// return response;
// };
// useEffect(() => {
// triggerFetchEvents();
// }, []);
if (!eventsList || !eventsList.length) return null;
return (
<EventListPage
eventsList={eventsList}
isLastPage={isLastPage}
triggerFetchEvents={useIn}
/>
);
};
export default connect(
mapState,
actionCreators
)(EventsScene);
I left the commented code there to show you that if I uncomment the code and remove useInfiniteScroll then it works properly.
What could I be missing?
UPDATE:
This is EventListPage component
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import EventModel from '../../shared/models/Event.model';
import { formatDate } from '../../shared/services/date';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Card from 'react-bootstrap/Card';
type Props = {
eventsList?: EventModel[];
isLastPage: boolean;
triggerFetchEvents: any;
};
export const EventListPage: React.FC<Props> = props => {
const { eventsList, triggerFetchEvents, isLastPage } = props;
const [isFetching, setIsFetching] = useState(false);
const fetchMoreEvents = (): Promise<void> =>
triggerFetchEvents().then(() => {
setIsFetching(false);
});
const handleScroll = (): void => {
if (
document.documentElement.offsetHeight -
(window.innerHeight + document.documentElement.scrollTop) >
1 ||
isFetching
) {
return;
}
return setIsFetching(true);
};
useEffect(() => {
if (isFetching) return;
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
if (!isFetching) return;
if (!isLastPage) fetchMoreEvents();
}, [isFetching]);
if (!eventsList) return null;
return (
<Container className='article-list mt-5'>
///...
</Container>
);
};
export default EventListPage;
In EventsScene, change useInfiniteScroll to be invoked directly at the function body top-level (not sure why you are creating this indirection in the first place):
// before
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// after
useInfiniteScroll(isLastPage, fetchEvents)
React expects Hook calls to only happen at the top-level as it relies on the order of Hooks to be always the same. If you wrap the Hook in a function, you can potentially invoke this function in many code locations disturbing the Hooks' order.
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state. Link

Categories