Apollo Client useQuery not getting data because of cache - reactjs

I'm using Apollo client GraphQL with React Native,
I have a query that launches but data stays undefined, I get data only when I change fetch policy from network-only to cache-only, but then if I logout and login the problem persists and I get nothing.
This is how the component works
// in MyComponent.tsx
export default function MyComponent(): JSX.Element {
const [flashCards, setFlashCards] = useState<any>([]);
const { loading, error, data, refetch } = useQuery<
{
getAllFlashcards: {
id: string;
flashcard: Array<{ id: string; title: string }>;
};
},
{ classroomId: string }
>(GET_ALL_FLASH_CARDS, {
fetchPolicy : "network-only", //<--------- 1. if i comment this
fetchPolicy: "cache-only", //<------- 2. then uncomment this then i get data
variables: {
classroomId: classroomId,
},
});
if (loading && data) {
console.log("loading and adata");
}
if (loading && !data) {
console.log("loading and no data");
}
if (error) {
console.log(error);
}
useEffect(() => {
if (data) {
setFlashCards(data.getAllFlashcards);
}
}, [data]);
return(<>...<>)
}
I followed Apollo client docs when implementing the authentication by clearing the store when I signin and signout, but still... the problem persist
// in App.tsx
export default function App() {
const [classroomId, setClassroomId] = useState<any>("");
const [state, dispatch] = React.useReducer(
(prevState: any, action: any) => {
switch (action.type) {
case "SIGN_IN":
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case "SIGN_OUT":
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);
//passed to the application using a context provider.
const auth = React.useMemo(
() => ({
signIn: async (data: any) => {
await client.resetStore();
await SecureStore.setItemAsync("userToken", data.token);
dispatch({ type: "SIGN_IN", data });
},
signOut: async() => {
await client.resetStore();
await SecureStore.deleteItemAsync("userToken")
dispatch({ type: "SIGN_OUT" })
}
}),
[]
);
Why fetched data is undefined but visible only when I change fetch policy even though I am using fetchPolicy : "network-only" ?, your help is appreciated, thank you.

Related

Redux-Toolkit dispatch in useEffect hook results in an infinite loop

Problem
I use useMuation from react-query to do post requests and get the user info from JSON and then try to store it to my redux store using useEffect according to the status given by react-query useMutation hook which is success. The problem rises in this status. all info is successfully stored in the redux store as you can see in the picture, but it causes infinite loop.
I tried to put an empty dependency array and even put userData?.data?.data?.user?.name and userData?.data?.token instead of userData but still the same.
Error
Store
userSlice.ts
import { createSlice, configureStore, PayloadAction } from "#reduxjs/toolkit";
type initialState = {
user: string;
dashboardIndex: number;
theme: string;
token: string;
isLoggedIn: boolean;
};
const initialState: initialState = {
user: "",
dashboardIndex: 0,
theme: "themeLight",
token: "",
isLoggedIn: false,
};
const userSlice = createSlice({
name: "user",
initialState: initialState,
reducers: {
updateUser(state, action: PayloadAction<string>) {
state.user = action.payload;
},
updateDashboardIndex(state, action: PayloadAction<number>) {
state.dashboardIndex = action.payload;
},
updateTheme(state, action: PayloadAction<string>) {
state.theme = action.payload;
},
updateToken(state, action: PayloadAction<string>) {
state.token = action.payload;
},
updateIsLoggedIn(state, action: PayloadAction<boolean>) {
state.isLoggedIn = action.payload;
},
reset: () => initialState,
},
});
// ...
Login.tsx
const LoginComponents = () => {
let navigate = useNavigate();
const [loginObject, setLoginOject] = useState<loginObjectType>({
email: "",
password: "",
});
const {
mutate,
error,
isError,
isSuccess,
data: userData,
} = useQueryMutationInvalidateHooksPost("api/v1/users/login");
const dispatch = useAppDispatch();
...
// Signin process handler
useEffect(() => {
console.log("hi");
if (isSuccess) {
if (userData) {
dispatch(usersActions.updateUser(userData?.data?.data?.user?.name));
dispatch(usersActions.updateToken(userData?.data?.token));
dispatch(usersActions.updateIsLoggedIn(!!userData?.data?.token));
alert(
`Succeeded in login. Welcome ${userData?.data?.data?.user?.name}!`
);
navigate("/home");
}
}
if (isError) {
if (error instanceof AxiosError) {
alert(error?.response?.data?.message);
}
}
}, [navigate, error, isSuccess, isError, userData, dispatch]);
// Button functions
const submitHandler = async (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.preventDefault();
if (!loginObject?.email || !loginObject?.password) {
alert("Please input all required fields.");
} else if (loginObject?.email && loginObject.password) {
// fetching data
const temp = {
...loginObject,
};
mutate(temp);
}
};
I would suggest to use useEffect hook only for initializations and not as a handler.
To react on mutation success or error use the mutation as follows.
mutate(temp, {
onSuccess: (data, variables, context) => {
// add your success handling logic here
},
onError: (error, variables, context) => {
// Add your error handling logic here
},
onSettled: (data, error, variables, context) => {
// Code that must run, irrelevant of success or error, should be added here.
},
})
This way you will get rid of your Signin process handler useeffect which will remove your looping logic.

React strict mode causes fetch abort

i made a custom hook for fetching data the problem is when i use <React.StrictMode> the fetch singal for aborting gets fire but some how it works if i remove strict mode
this is the fetch hook
import { useEffect, useReducer } from 'react';
import { ApiResponse } from '../interfaces/ApiResponse';
const initialState: ApiResponse = {
loading: false,
data: null,
error: null,
};
type Action =
| { type: 'start' }
| { type: 'error'; payload: Error }
| { type: 'success'; payload: JSON };
const reducer = (state: ApiResponse, action: Action) => {
switch (action.type) {
case 'start':
return {
loading: true,
data: null,
error: null,
};
case 'success':
return {
loading: false,
data: action.payload,
error: null,
};
case 'error':
return {
loading: false,
data: null,
error: action.payload,
};
default:
return state;
}
};
export const useFetch = (url: string): ApiResponse => {
const [response, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const controller: AbortController = new AbortController();
const signal: AbortSignal = controller.signal;
const fetchData = async () => {
dispatch({ type: 'start' });
try {
const response: Response = await fetch(url, { signal: signal });
if (response.ok) {
const json = await response.json();
dispatch({
type: 'success',
payload: json,
});
} else {
dispatch({
type: 'error',
payload: new Error(response.statusText),
});
}
} catch (error: any) {
dispatch({
type: 'error',
payload: new Error(error),
});
}
};
fetchData();
return () => {
controller.abort();
};
}, [url]);
return response;
};
when i call this hook in one of my components like this:
const Grid = () => {
const response = useFetch(`${BASE_API_URL}/games`);
useEffect(() => {
console.log(response);
}, [response]);
return (
<div className='grid__wrapper'>
<div className='grid__content'>
{response.loading && <h4>Loading...</h4>}
<h4>helo</h4>
</div>
</div>
);
};
export default Grid;
the response.loading is never set to true and i can see an abort error in the logs but if i remove strict mode it works fine

React Hooks: useEffect, useReducer, createContext and localStorage; TypeError: Cannot read property of undefined

I am using local storage in my Map app for some persistent storage; also using React.createContext coupled with useReducer to share and update state amongst components.
State in local storage, and in the app when consoled are updating and present, i.e. hash-generated id, paths from Cloudinary for images.
But when I click on the map to add a marker i get:
TypeError: Cannot read property 'markers' of undefined
That is strange because of what I see in the console, and in local-storage.
My thinking is I have wired things incorrectly.
My UserContext component:
var initialState = {
avatar: '/static/uploads/profile-avatars/placeholder.jpg',
id: null,
isRoutingVisible: false,
removeRoutingMachine: false,
isLengthOfMarkersLessThanTwo: true,
markers: [],
currentMap: {}
};
var UserContext = React.createContext();
function UserProvider({ children }) {
function userReducer(state, { type, payload }) {
switch (type) {
case 'setUserId': {
return { ...state, ...{ id: payload.id } };
}
case 'setAvatar': {
return {
...state,
...{ avatar: payload.avatar }
};
}
case 'setIsRoutingVisible': {
return {
...state,
...{ isRoutingVisible: payload.isRoutingVisible }
};
}
case 'isLengthOfMarkersLessThanTwoFalse': {
return {
...state,
...{
isLengthOfMarkersLessThanTwo: payload.isLengthOfMarkersLessThanTwo
}
};
}
case 'addMarker': {
user.isLengthOfMarkersLessThanTwo
? {
...state,
markers: user.markers.concat(payload.marker)
}
: null;
break;
}
default: {
throw new Error(`Unhandled action type: ${type}`);
}
}
}
const [user, setUser] = useState(() => getLocalStorage('user', initialState));
var [state, dispatch] = useReducer(userReducer, user);
const [isAvatarUploading, setIsAvatarUploading] = useState(true);
useEffect(() => {
setLocalStorage('user', state);
}, [state]);
useEffect(() => {
console.log('state', state);
if (state.markers.length === 2) {
dispatch({
type: 'isLengthOfMarkersLessThanTwoFalse',
payload: { isLengthOfMarkersLessThanTwo: false }
});
}
}, [JSON.stringify(state.markers)]);
useEffect(() => {
console.log('state', state);
if (state.id) {
getUserAvatar()
.then(userAvatar => {
console.log('userAvatar ', userAvatar);
setIsAvatarUploading(false);
dispatch({
type: 'setAvatar',
payload: { avatar: userAvatar }
});
})
.catch(err => console.log('error thrown from getUserAvatar', err));
} else {
console.log('No user yet!');
}
}, [state.id]);
return (
<UserContext.Provider
value={{
userId: state.id,
userAvatar: state.avatar,
dispatch: dispatch,
isAvatarUploading: state.isAvatarUploading,
userImages: state.images,
userMarkers: state.markers,
userMap: state.currentMap,
removeRoutingMachine: state.removeRoutingMachine,
isRoutingVisibile: state.isRoutingVisible
}}
>
{children}
</UserContext.Provider>
);
}
export default UserContext;
export { UserProvider };
I thought I needed to have a custom hook, to use to pass the old state and use it to watch changes and update it.
var newUserState = initialState => {
const [state, setState] = useState(initialState);
var setter = useCallback(() => setState(state => !state), [setState]);
return [state, setter];
};
var [newUserState, setter] = newUserState(state)
Any help would be appreaciated!
In your addMarker case, you probably want to access the local state variable rather than the global user.
Also, from what I understood of this other question's answer you'd rather define your reducer outside of your component, because React will trigger some unwanted update when you redefine the reducer fucntion.
Whether any of these will solve your issue, I can't say...

Redux state's nested object is available but unaccessible within component

enter code hereI'm providing Redux Global State to my whole react app through a Provider wrapper in my app.js file.
I've no problem accessing any other piece of state other than "Current Profile".
Here is the component:
import React, { Fragment, useEffect } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { loadTargetProfiles, loadCurrentProfile } from "../../actions/profile";
const Friends = ({
loadCurrentProfile,
loadTargetProfiles,
profile: { currentProfile, targetProfiles, targetProfilesAreLoading },
}) => {
useEffect(() => {
loadTargetProfiles();
loadCurrentProfile();
}, []);
console.log(currentProfile);
return (
...
};
Friends.propTypes = {
loadTargetProfiles: PropTypes.func.isRequired,
loadCurrentProfile: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
profile: state.profile,
});
export default connect(mapStateToProps, {
loadCurrentProfile,
loadTargetProfiles,
})(Friends);
here is the loadCurrentProfile action responsible for providing the currentProfile piece of state.
export const loadCurrentProfile = () => async (dispatch) => {
try {
const res = await api.get("/profile/current");
dispatch({
type: LOAD_CURRENT_PROFILE,
payload: res.data,
});
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status },
});
}
};
here is the relevant part of the Reducer
const initialState = {
currentProfile: null,
targetProfile: null,
targetProfiles: [],
currentProfileIsLoading: true,
targetProfileIsLoading: true,
targetProfilesAreLoading: true,
error: {},
};
//
// Export Reducer
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case LOAD_CURRENT_PROFILE:
return {
...state,
currentProfile: payload,
currentProfileIsLoading: false,
};
here is the API that's getting hit:
router.get("/current", auth, async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.user.id,
}).populate("user", ["_id", "username", "registerdate"]);
if (!profile) {
return res
.status(400)
.json({ msg: "There is no profile for this user." });
}
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error...");
}
});
here is the console
here is the currentProfile piece of state expanded:
when i try to reach into the currentProfile piece of state, for example
const Friends = ({
loadCurrentProfile,
loadTargetProfiles,
profile: { currentProfile: {
avatar
}, targetProfiles, targetProfilesAreLoading },
}) => {
useEffect(() => {
loadTargetProfiles();
loadCurrentProfile();
}, []);
console.log(avatar);
It give me the error:
TypeError: Cannot read property 'avatar' of null
here is Redux Dev Tools screenshot:
Fix:
I (temporarily) got rid of the error by accessing the inner state after declaring the function.
const Friends= ({
profile: { currentProfileIsLoading },
currentProfile,
}) => {
currentProfile && console.log(currentProfile.avatar);
and it works for now but it certainly isn't the most elegant solution. Is there a way to add this guard in the function declaration in order to set the state in one place?
Issue: Both targetProfiles and targetProfilesAreLoading are truthy values in your state, but currentProfile is null until the GET resolves. You can't access the avatar property of a null object.
You can provide some default argument value for profile, this only works really though if profile is undefined, null counts as a defined value.
const Friends = ({
loadCurrentProfile,
loadTargetProfiles,
profile: {
currentProfile = {},
targetProfiles,
targetProfilesAreLoading
},
}) => {
useEffect(() => {
loadTargetProfiles();
loadCurrentProfile();
}, []);
console.log(currentProfile);
return (
...
};
You can also use a guard on the possibly undefined/null object
currentProfile && currentProfile.avatar
Another alternative is to use a state selector library like reselect that allows you to pull/augment/derive/etc... state values that get passed as props. This also allows you to set default/fallback values for state. It pairs with redux nicely.

Redux: Should i clear state on unmount

Having a strange bug/issue with redux. I have a component in an app that displays data in a table. this table is used across numerous routes and i'm passing in a url for the end point.
When i click between the routes they work fine but some fields in the table have a button to open a slide out menu. when i do the redux actions is dispatched and it fires it for all routes i have been to and not the one i'm on.
Action
export const clearTableData = () => dispatch => {
dispatch({
type: TYPES.CLEAR_TABLE_DATA,
});
};
export const getTableData = (url, limit, skip, where, sort, current) => async dispatch => {
try {
dispatch({ type: TYPES.FETCH_TABLE_DATA_LOADING });
const response = await axios.post(url, {
limit,
skip,
where,
sort
});
await dispatch({
type: TYPES.FETCH_TABLE_DATA,
payload: {
url: url,
data: response.data,
limit: limit,
skip: skip,
where: where,
sort: sort,
pagination: {
total: response.data.meta.total,
current: current,
pageSizeOptions: ["10", "20", "50", "100"],
showSizeChanger: true,
showQuickJumper: true,
position: "both"
}
}
});
dispatch({ type: TYPES.FETCH_TABLE_DATA_FINISHED });
} catch (err) {
dispatch({ type: TYPES.INSERT_ERROR, payload: err.response });
}
};
Reducer
import * as TYPES from '../actions/types';
export default (state = { loading: true, data: [], pagination: [] }, action) => {
switch (action.type) {
case TYPES.FETCH_TABLE_DATA:
return { ...state, ...action.payload };
case TYPES.FETCH_TABLE_DATA_LOADING:
return { ...state, loading: true };
case TYPES.FETCH_TABLE_DATA_FINISHED:
return { ...state, loading: false };
case TYPES.CLEAR_TABLE_DATA:
return {};
default:
return state;
}
};
component
componentDidMount() {
this.fetch();
websocket(this.props.websocketRoute, this.props.websocketEvent, this.fetch);
}
fetch = () => {
// Fetch from redux store
this.props.getTableData(
this.props.apiUrl,
this.state.limit,
this.state.skip,
{ ...this.filters, ...this.props.defaultWhere },
`${this.state.sortField} ${this.state.sortOrder}`,
this.state.current)
}
const mapStateToProps = ({ tableData }) => ({
tableData,
});
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData }, dispatch)
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchableTable);
Websocket
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
export const websocket = (websocketRoute, websocketEvent, callback) => {
if (websocketRoute) {
let io;
if (socketIOClient.sails) {
io = socketIOClient;
} else {
io = sailsIOClient(socketIOClient);
}
io.sails.transports = ['websocket'];
io.sails.reconnection = true;
io.sails.url = process.env.REACT_APP_WEBSOCKECTS_URL
io.socket.on('connect', () => {
io.socket.get(websocketRoute, (data, jwres) => {
console.log("connect data sss", data)
console.log("connect jwres sss", jwres)
});
});
io.socket.on(websocketEvent, (data, jwres) => {
console.log("websocket", callback)
callback();
})
io.socket.on('disconnect', () => {
io.socket._raw.io._reconnection = true;
});
}
}
So for e.g if i'm on a route for cars i'll pass in api/cars as url, and for trucks api/trucks. if i've been to both these pages they get fired.
should i be doing something to unmount and reset state to blank?
edit to add render
render() {
const { filters, columns, expandedRowRender, rowClassName, style } = this.props;
return (
<Table
bordered
columns={columns}
rowKey={record => record.id}
dataSource={this.props.tableData.data.items}
pagination={this.props.tableData.pagination}
loading={this.props.tableData.loading}
onChange={this.handleTableChange}
expandedRowRender={expandedRowRender}
rowClassName={rowClassName} />
);
Basic idea is, define a new action type in reducer file to clear the table data, and before unmount dispatch that action.
In Component:
componentDidMount() {
this.fetch();
}
componentWillUnmount() {
this.props.clearTableData();
}
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData, clearTableData }, dispatch)
)
Action:
export const clearTableData = () => {
return { type: TYPES.CLEAR_TABLE_DATA };
};
Reducer:
case TYPES.CLEAR_TABLE_DATA: {
// reset the table data here, and return
}

Resources