Using observables in react-query - reactjs

I am building out an app and planning to use rxjs for Observables. However, I like react-query cache functionality and would like to merge them together to get it working.
I am facing an issue where the error code is not clear to me.
Please advice.
import React, { FunctionComponent } from 'react';
import { render } from 'react-dom';
import { useQueryStream } from './useQueryStream';
import API from './apiService';
import './style.css';
import { Observable, of } from 'rxjs';
import { catchError, take } from 'rxjs/operators';
const Hello: FunctionComponent = () => {
const fetchPokemon = <T>(): Observable<T[]> => {
const data = API.get<T[]>('pokemon?limit=100&offset=0').pipe(
take(1),
catchError(err => of(console.log(err)))
) as Observable<T[]>;
console.log(data)
};
const result = useQueryStream('data', fetchPokemon);
return <>{JSON.stringify(result, null, 2)}</>;
};
export default Hello;
I am also extending react-query to accept an observable and subscribe to it and send the data.
import { QueryFunction, useQuery, useQueryClient } from 'react-query';
import { Observable, combineLatest } from 'rxjs';
import { useEffect } from 'react';
import { UseQueryOptions } from 'react-query/types/react/types';
export const useQueryStream = (
queryKey: string,
fetcher: QueryFunction,
config?: UseQueryOptions
) => {
const queryClient = useQueryClient();
const queryResult = useQuery(queryKey, fetcher, config);
useEffect(() => {
if (queryResult.data instanceof Observable) {
queryResult.data.subscribe({
next: data => {
queryClient.setQueryData(queryKey, (currentQueryData: any) => {
currentQueryData && combineLatest(currentQueryData, data);
});
},
error: error => {
console.error(error);
}
});
}
}, [queryKey, queryResult.data]);
console.log('queryResult', queryResult);
return queryResult;
};
This is my stackblitz link: https://stackblitz.com/edit/react-ts-3tqgbx?file=Hello.tsx
Please advice on how I fix the error and how I get the data back.

As this is a .tsx file, you cannot use the generic <T> as you have because it will be interpreted as an opening component/element tag:
You can use regular function syntax instead:
function fetchPokemon<T>(): Observable<T[]> {
...
}
However, if you really want to use an arrow function, you can use:
const fetchPokemon = <T,>(): Observable<T[]> => {
...
}
...or:
const fetchPokemon = <T extends {}>(): Observable<T[]> => {
...
}

Related

Why this dependency array caused inifnite loop?

I'm managing a context for the classes of my school so that i don't have to fetch on every page, for this i write this custom component and wrap my entire app in it.I whenever i fetch the classes i store them in the redux store so that i can use it again and again.
And before fetch i check the store if the classes exist there i don't hit the API instead get the classes from there. in my dependency array i put navigate, dispatch and Classes because react was complaining about that. but this cause infinite loop
here is my code
// ** React Imports
import { createContext, useEffect, useState } from 'react';
import { useDispatch, connect } from 'react-redux';
// ** Config
import { toast } from 'react-hot-toast';
// ** custom imports
import { useNavigate } from 'react-router-dom';
import { CLASSES_API_HANDLER } from 'src/redux/actions/classes/classes';
// ** Defaults
const defaultProvider = {
classes: [],
setClasses: () => null,
loading: true,
setLoading: () => null,
};
export const ClassesContext = createContext(defaultProvider);
const ClassesProvider = ({ children, CLASSES }) => {
const dispatch = useDispatch();
const navigate=useNavigate()
const [classes, setClasses] = useState(defaultProvider.classes);
const [loading, setLoading] = useState(defaultProvider.loading);
useEffect(() => {
const getClasses = async () => {
if (CLASSES.length > 0) {
setClasses(CLASSES);
setLoading(false);
return;
}
try {
const Data = await dispatch(CLASSES_API_HANDLER());
setClasses(Data.result);
setLoading(false);
} catch (ex) {
navigate("/error")
console.log(ex);
}
}
getClasses()
}, [CLASSES,navigate,dispatch]);
const values = {
classes:classes,
setClasses:setClasses,
loading:loading,
setLoading:setLoading,
};
return (
<ClassesContext.Provider value={values}>
{children}
</ClassesContext.Provider>
);
};
function mapStateToProps(state) {
return { CLASSES: state.CLASSES };
}
export default connect(mapStateToProps)(ClassesProvider);
I'm trying to fix the infinite loop

How to navigate between screens in react without usage of libraries like react router

I want to know if is possible to navigate between screens, using like a context api, or something else, where I can get the "navigateTo" function in any component without passing by props. And of course, without the cycle dependency problem.
Example with the cycle dependency problem
NavigateContext.tsx:
import React, { createContext, useMemo, useReducer } from 'react'
import { Home } from './pages/Home'
interface NavigateProps {
navigateTo: (screenName: string) => void
}
export const navigateContext = createContext({} as NavigateProps)
const reducer = (state: () => JSX.Element, action: { type: string }) => {
switch (action.type) {
case 'home':
return Home
default:
throw new Error('Page not found')
}
}
export function NavigateContextProvider() {
const [Screen, dispatch] = useReducer(reducer, Home)
const value = useMemo(() => {
return {
navigateTo: (screenName: string) => {
dispatch({ type: screenName })
},
}
}, [])
return (
<navigateContext.Provider value={value}>
<Screen />
</navigateContext.Provider>
)
}
Home.tsx:
import React, { useContext, useEffect } from 'react'
import { Flex, Text } from '#chakra-ui/react'
import { navigateContext } from '../NavigateContext'
export function Home() {
const { navigateTo } = useContext(navigateContext)
useEffect(() => {
setTimeout(() => {
navigateTo('home')
}, 2000)
}, [])
return (
<Flex>
<Text>Home</Text>
</Flex>
)
}
Yes, this is possible, but you'll need to maintain the list of string view names independently from your mapping of them to their associated components in order to avoid circular dependencies (what you call "the cycle dependency problem" in your question):
Note, I created this in the TS Playground (which doesn't support modules AFAIK), so I annotated module names in comments. You can separate them into individual files to test/experiment.
TS Playground
import {
default as React,
createContext,
useContext,
useEffect,
useState,
type Dispatch,
type ReactElement,
type SetStateAction,
} from 'react';
////////// views.ts
// Every time you add/remove a view in your app, you'll need to update this array:
export const views = ['home', 'about'] as const;
export type View = typeof views[number];
export type ViewContext = {
setView: Dispatch<SetStateAction<View>>;
};
export const viewContext = createContext({} as ViewContext);
////////// Home.ts
// import { viewContext } from './views';
export function Home (): ReactElement {
const {setView} = useContext(viewContext);
useEffect(() => void setTimeout(() => setView('home'), 2000), []);
return (<div>Home</div>);
}
////////// About.ts
// import { viewContext } from './views';
export function About (): ReactElement {
const {setView} = useContext(viewContext);
return (
<div>
<div>About</div>
<button onClick={() => setView('home')}>Go Home</button>
</div>
);
}
////////// ContextProvider.tsx
// import {viewContext, type View} from './views';
// import {Home} from './Home';
// import {About} from './About';
// import {Etc} from './Etc';
// Every time you add/remove a view in your app, you'll need to update this object:
const viewMap: Record<View, () => ReactElement> = {
home: Home,
about: About,
// etc: Etc,
};
function ViewProvider () {
const [view, setView] = useState<View>('home');
const CurrentView = viewMap[view];
return (
<viewContext.Provider value={{setView}}>
<CurrentView />
</viewContext.Provider>
);
}

TypeError: customers.map is not a function

I have this problem, can anyone help me?
TypeError: customers.map is not a function.
I've always used it that way and I've never had any problems.
Its about data integration.
Basically is that, please anyone can help me?
import React, { useState, useEffect } from "react";
import { List, Card } from "antd";
import { data } from "../../../mocks/customers";
import { DeleteCustomerButton } from "#components/atoms/DeleteCustomerButton";
import { CustomersEditButton } from "#components/atoms/CustomersEditButton";
import { useContext } from "../../../contexts/context";
const { Meta } = Card;
const CustomersCardList: React.FC = () => {
const customers: any = useContext();
return (
<div>
{customers.map((customer, key) => { })}</div>)
}
//context.tsx
import * as React from 'react';
import axios from 'axios';
export const AccountContext = React.createContext({});
export const useContext = () => React.useContext(AccountContext);
interface AccounterContextProviderProps {
value: any
};
export const AccounterContextProvider: React.FC<AccounterContextProviderProps> = ({ children, value }) => {
const [customers, setCustomers] = React.useState<any>([]);
React.useEffect(() => {
const getCustomers = async () => {
const result = await axios.get("http://localhost:3333/customers");
setCustomers(result.data);
}
getCustomers();
}, []);
console.log(customers);
return (
<AccountContext.Provider value={{ ...value, customers }}>
{children}
</AccountContext.Provider>
)
};
Any can be anything not only array, so it will not have a map method. Use const customers:any[] = useContext() instead

this.props.dispatch is not a function. Not able to dispatch mapdispatchtoprops

I've been trying to dispatch a function that will call an async parse cloud function. It worked well in my other projects when i used them in functions. But this is the first time i'm using them in a component and when i call the dispatch from map dispatch to props, I get this error. Please help me out.
ProfileHeader.js
import React, { Component } from 'react';
import Cover_Image from './Cover_Image.jpg';
import Profile_Pic from './Profile_Pic.svg';
import './ProfileHeader.css';
import { connect } from 'react-redux';
import { fetchUserProfile } from '../../Redux/UserProfile-Redux/UserProfileActionMethods';
class ProfileHeader extends Component {
componentDidMount() {
this.props.fetchUserProfile()
}
render() {
return (
<div className="profile-header-layout"></div>
)
}
}
const mapStatetoProps = (state) => {
return {
profile: state.UserProfile
}
}
const mapDispatchtoProps = (dispatch) => {
return {
fetchUserProfile: () => { dispatch(fetchUserProfile()) }, dispatch,
}
}
export default connect(mapDispatchtoProps, mapStatetoProps)(ProfileHeader)
The action Method:
import Parse from 'parse/dist/parse.min.js';
import { FETCH_USERPROFILE_FAILURE, FETCH_USERPROFILE_REQUEST, FETCH_USERPROFILE_SUCCESS } from './UserProfileActions';
const params = { username: "prvnngrj" }
export const fetchUserProfileRequest = () => {
return {
type: FETCH_USERPROFILE_REQUEST
}
}
export const fetchUserProfileSuccess = (userprofiles) => {
return {
type: FETCH_USERPROFILE_SUCCESS,
payload: userprofiles
}
}
export const fetchUserProfileFailure = (error) => {
return {
type: FETCH_USERPROFILE_FAILURE,
payload: error
}
}
export const fetchUserProfile = () => {
return async dispatch => {
dispatch(fetchUserProfileRequest)
try {
const responsedata = await Parse.Cloud.run("GetUserProfileForUsername", params);
const userprofiles = responsedata;
dispatch(fetchUserProfileSuccess(userprofiles))
}
catch (error) {
const errorMessage = error.message
dispatch(fetchUserProfileFailure(errorMessage))
}
}
}
Please ignore parts of code which do not make it relevant, its straight from the project
You mixed up the order of your arguments, so this.props.dispatch is actually your state!
You need to change
export default connect(mapDispatchtoProps, mapStatetoProps)(ProfileHeader)
to:
export default connect(mapStatetoProps, mapDispatchtoProps)(ProfileHeader)
If you can switch to function components and the useSelector/useDispatch hooks you should. This is the current recommended approach and it's easier to use.

React: Getting Initial State in Class based compopnents

I have a react-native, redux app, and after upgrading I've started getting some warnings about lifecycle hooks. My code looks like below:
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectPosts} from '../redux/selectors/postSelector';
import { getPosts } from '../redux/actions/postActions';
class BasicScreen extends React.Component {
state = {
data: [],
myItems: [],
};
componentWillMount() {
this.getPosts();
}
componentDidMount() {
this.checkforItems();
}
getPosts = async () => {
// Call to a redux action
await this.props.getPosts();
};
checkforItems = async () => {
// myItems in initial state are set from data in
AsyncStorage.getItem('MyItems').then(item => {
if (item) {
this.setState({
myItems: JSON.parse(item),
});
} else {
console.log('No data.');
}
});
};
componentWillReceiveProps(nextProps) {
// Data comes from the redux action.
if (
nextProps.data &&
!this.state.data.length &&
nextProps.data.length !== 0
) {
this.setState({
data: nextProps.data,
});
}
}
render() {
return (
<View>/* A detailed view */</View>
)
}
}
const mapStateToProps = createStructuredSelector({
data: selectPosts,
});
const mapDispatchToProps = dispatch => ({
dispatch,
getPosts: () => dispatch(getPosts()),
});
export default connect(mapStateToProps, mapDispatchToProps)(BasicScreen);
To summarize, I was calling a redux action (this.getPosts()) from componentWillMount(), and then updating the state by props received in componentWillReceiveProps. Now both these are deprecated, and I am getting warnings that these are deprecated.
Apart from this, I am also setting some initial state by pulling some data from storage (this.checkforItems()). This gives me another warning - Cannot update a component from inside the function body of a different component.
To me it looks like the solution lies in converting this into a functional component, however, I'm stuck at how I will call my initial redux action to set the initial state.
UPDATE:
I converted this into a functional component, and the code looks as follows:
import React, { Fragment, useState, useEffect } from 'react';
import { connect } from 'react-redux';
import AsyncStorage from '#react-native-community/async-storage';
import { StyleSheet,
ScrollView,
View,
} from 'react-native';
import {
Text,
Right,
} from 'native-base';
import { createStructuredSelector } from 'reselect';
import {
makeSelectPosts,
} from '../redux/selectors/postSelector';
import { getPosts } from '../redux/actions/postActions';
const BasicScreen = ({ data, getPosts }) => {
const [myData, setData] = useState([]);
const [myItems, setItems] = useState([]);
const checkForItems = () => {
var storageItems = AsyncStorage.getItem("MyItems").then((item) => {
if (item) {
return JSON.parse(item);
}
});
setItems(storageItems);
};
useEffect(() => {
async function getItems() {
await getPosts(); // Redux action to get posts
await checkForItems(); // calling function to get data from storage
setData(data);
}
getItems();
}, [data]);
return (
<View>
<>
<Text>{JSON.stringify(myItems)}</Text>
<Text>{JSON.stringify(myData)}</Text>
</>
</View>
);
}
const mapStateToProps = createStructuredSelector({
data: makeSelectPosts,
});
const mapDispatchToProps = dispatch => ({
dispatch,
getPosts: () => dispatch(getPosts()),
});
export default connect(mapStateToProps, mapDispatchToProps)(BasicScreen);
It works, but the problem is that the first Text - {JSON.stringify(myItems)} - it is rerendering continuously. This data is actually got using checkForItems(). I wanted the useEffect to be called again only when the data updates, but instead something else is happening.
Also, I noticed that setData is not being called correctly. The data becomes available through the prop (data), but not from the state (myData). myData just returns empty array.

Resources