useMutation always returns data undefined - javascript

this my code
React
const EXCHANGE_RATES = gql`
mutation {
signUp(lastName: "Amasia")
}
`;
const HandlerRequest = () => {
const [loading, { data }] = useMutation(EXCHANGE_RATES);
console.log('apollo', data);
return <p>apollo</p>;
};
Schema
gql`
extend type Mutation {
signUp(
lastName: String!
): String!
}
`;
Resolvers
Mutation: {
signUp: async (
_,
{ lastName}
) => {
try {
console.log(lastName)
return lastName;
} catch (error) {
return 'error';
}
},
},
useMutation always returns data undefined but at graphql playground are working.what am i doing netak ?.

useMutation should return a function to call to execute the mutation. Generally you would hook this up to a button or something. It seems a little odd to use a mutation that executes automatically when the component loads.
const HandlerRequest = () => {
const [getExchangeRates, { data }] = useMutation(EXCHANGE_RATES);
// Load data if not already loaded.
if (!data) { getExchangeRates(); }
console.log('apollo', data);
return <p>apollo</p>;
};
Using the above you'll likely see two console.logs.
The initial component render, should be undefined
When getExchangeRates returns and data updates the component will be rerendered and the log will be called again.

Use the onCompleted callback and remember to pass the data object:
const [loading, { data }] = useMutation(EXCHANGE_RATES, {
onCompleted: (data) => {
console.log(data);
}
});

Your usage of useMutation is not quite correct. Here is the correct usage.
You also need to execute the mutation. data will be undefined until then.
const SomeComponent = () => {
const [doExchangeRates, { data, loading, error }] = useMutation(
EXCHANGE_RATES,
onCompleted: (data) => { console.log({data}); }
);
return <p onClick={doExchangeRates} >apollo</p>;
};
You can see full docs on useMutation here.

Related

useSWRInfinite with pagination and mutate features

I'm using useSWR to fetch data from client side in nextjs.
What I am doing and trying to achieve
I am using useSWRInfinite for the pagination feature and trying to update comments like state with bound mutate function with optimisticData option since I wanted to refresh the data immediately.(client-side perspective)
-> https://swr.vercel.app/docs/mutation#optimistic-updates and then get a new updated comment from axios and replace it with a previous comment that should be updated.
Expected
The data from useSWRInfinite should be updated right away since I am using optimisticData option until the API call is done and I could've set revalidate option to true but an async function in the mutate returns updated data with the response from axios. I didn't need it.
Actual behaviour
Even though I am passing optimisticData to the mutate, It doesn't update the data immediately. It keeps waiting until The API call is done and then gets updated.
What I've tried
I have tried using just normal useSWR function without the pagination feature and it worked well as I expected.
const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite<CommentType[]>(
(index) => `/api/comment?postId=${postId}&currentPage=${index + 1}`,
fetcher,
{ revalidateFirstPage: false }
);
const likeCommentHandler = async (commentId: string, dislike: boolean) => {
const optimisticData = data?.map((comments) => {
return comments.map((comment) => {
if (comment.id === commentId) {
if (dislike) {
--comment._count.likedBy;
comment.likedByIds = comment.likedByIds.filter(
(likeById) => likeById !== session!.user.id
);
} else {
comment.likedByIds.push(session!.user.id);
++comment._count.likedBy;
}
return { ...comment };
} else {
return { ...comment };
}
});
});
mutate(
async (data) => {
const { data: result } = await axios.post("/api/likeComment", {
commentId: commentId,
userId: session?.user.id,
dislike,
});
const newData = data?.map((comments) => {
return comments.map((comment) => {
if (comment.id === result.comment.id) {
return result.comment;
} else {
return comment;
}
});
});
return newData;
},
{ optimisticData, revalidate: false, populateCache: true }
);
};

How to use custom react query hook twice in the same component?

I have a custom hook like so for getting data using useQuery. The hook works fine, no problem there.
const getData = async (url) => {
try{
return await axios(url)
} catch(error){
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData', () => getData(url), {onSuccess})
}
However, if I call this hook twice in my component it will only fetch data from the first call even with a different URL. (Ignore the comments typo, that's intentional)
The call in my component:
const { data: commentss, isLoading: commentsIsLoading } = useGetData(`/comments/${params.id}`)
const { data: forumPost, isLoading: forumPostIsLoading } = useGetData(`/forum_posts/${params.id}`)
When I console.log forumPost in this case, it is the array of comments and not the forum post even though I am passing in a different endpoint.
How can I use this hook twice to get different data? Is it possible? I know I can just call parallel queries but I would like to use my hook if possible.
Since useQuery caches based on the queryKey, use the URL in that name
const getData = async(url) => {
try {
return await axios(url)
} catch (error) {
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData' + url, () => getData(url), {
onSuccess
})
}
//........
const {
data: commentss,
isLoading: commentsIsLoading
} = useGetData(`/comments/${params.id}`)
const {
data: forumPost,
isLoading: forumPostIsLoading
} = useGetData(`/forum_posts/${params.id}`)

nested websocket function always returning undefined

I'm using websockets from this library: https://www.npmjs.com/package/websocket
This is a function in React.ts 17 that successfully retrieves the data from the server, but fails to return the values of the function itself.
const recieveMessages = () => {
client.onmessage = (message: any) => {
const dataFromServer = JSON.parse(message.data)
console.log(dataFromServer) //This successfully logs the data
return dataFromServer //This is always returned as undefined
}
//I've tried a few versions with the return statement here without any success.
}
How can I make the recieveMessages function return the data from the client.onmessage function?
Update: I'm trying to seperate all the logic into a seperate React hook(useWebSocket) which currently looks like this:
import { w3cwebsocket } from 'websocket'
export const useWebSocket = () => {
const client = new w3cwebsocket('ws://127.0.0.1:8000')
const connectToServer = () => {
client.onopen = () => { console.log('Websocket client connected') }
}
const recieveMessages = () => {
client.onmessage = (message: any) => {
const dataFromServer = JSON.parse(message.data)
console.log('reply from server: ', dataFromServer)
return dataFromServer
}
}
const sendMessage = (message: any) => {
client.send(JSON.stringify('lol'))
}
const onCloseServer = (event: CloseEvent) => {
console.log(event)
}
return {
connectToServer,
recieveMessages,
sendMessage,
onCloseServer
}
}
I'm then trying to run the function inside the useEffect in a separate component like this:
The desire is to set the following data inside the local state of this component.
useEffect(() => {
recieveMessages()
setState(recieveMessages()) //This is always undefined
}, [])
This calls for a useState hook inside your useWebSocket hook.
Whenever client.onmessage receives a message, store that message inside of a state and update it every time another message is received.
Then return the state from the hook.
import { useState } from 'react'
import { w3cwebsocket } from 'websocket'
export const useWebSocket = () => {
const [receivedMessage, setReceivedMessage] = useState('')
const client = new w3cwebsocket('ws://127.0.0.1:8000')
client.onmessage = (message: any) => {
const dataFromServer = JSON.parse(message.data)
setReceivedMessage(dataFromServer)
}
const sendMessage = (message: any) => {
client.send(JSON.stringify(message))
}
return {
receivedMessage,
sendMessage,
}
}
Then implement it like so. The receivedMessage value is the state that will be updated and can be monitered with a useEffect hook to do something whenever a message has been received.
const { receivedMessage, sendMessage } = useWebSocket()
useEffect(() => {
if (receivedMessage !== '') {
// Do something whenever the received message is changed.
sendMessage('Received you loud and clear')
}
}, [receivedMessage])

converting class to hooks getting Property 'then' does not exist on type '(dispatch: any) => Promise<void>'.ts(2339)

I'm new to react, here I have two same codes, one is with classes that work, and another is converted from that same class into hooks.
in hooks version, my 'then' is giving an error
Property 'then' does not exist on type '(dispatch: any) =>
Promise'.ts(2339)
have I made some mistake with conversion?
why it is not giving the same error in class while both are the same?
also console.log("Fetched model", realGraph.model); should give an object but it is giving undefined(in-class version it works), but if I put this console outside of loadGraph function then it gives an object, why it's not giving an object inside loadGraph function?
any ideas and suggestions?
class:
import { getGraph, getFloorplan, changeActiveCamera } from '../redux/actions';
const mapStateToProps = (state) => {
return {
currentSite: state.selection.currentSite,
currentCamera: state.selection.currentCamera,
};
};
function mapDispatchToProps(dispatch) {
return {
getGraph: (site) => dispatch(getGraph(site)),
getFloorplan: (site) => dispatch(getFloorplan(site)),
changeActiveCamera: (site, id) => dispatch(changeActiveCamera(site, id)),
};
}
loadGraph() {
if (this.props.currentSite) {
this.props.getFloorplan(this.props.currentSite.identif).then(() => {
console.log('Fetched floorplan');
this.props.getGraph(this.props.currentSite.identif).then(() => {
console.log('Fetched model', this.props.realGraph.model);
// new camera-related node & link status
if (this.props.currentCamera) {
this.props.changeActiveCamera(
this.props.currentSite.identif,
this.props.currentCamera.identif
);
}
});
});
}
}
converted from class to hooks:
Hooks:
const dispatch = useDispatch();
const realGraph = useSelector((state) => state.graphArticles.graph);
const currentSite = useSelector((state) => state.selection.currentSite);
const currentCamera = useSelector((state) => state.selection.currentCamera);
const dispatchGetFloorplan = (site) => dispatch(getFloorplan(site));
const dispatchGetGraph = (site) => dispatch(getGraph(site));
const dispatchChangeActiveCamera = (site, id) =>
dispatch(changeActiveCamera(site, id));
const loadGraph = () => {
if (currentSite) {
dispatchGetFloorplan(currentSite.identif).then(() => {
console.log('Fetched floorplan');
dispatchGetGraph(currentSite.identif).then(() => {
console.log('Fetched model', realGraph.model);
// new camera-related node & link status
if (currentCamera) {
dispatchChangeActiveCamera(
currentSite.identif,
currentCamera.identif
);
}
});
});
}
};
my action related to those:
export function getGraph(site) {
return getData(`api/graph/${site}`, GET_GRAPHS);
}
export function getFloorplan(site) {
return getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
}
On first glance, there are several things I would change in the code you provided.
First, don't use any wrapper factories over your dispatch functions. Use dispatch(action()) directly where you need it component. You aren't gaining anything by creating wrapper functions.
Second, it would be advisable to use some sort of middleware, like Redux Thunk, to handle async Redux actions (like fetching something from the API).
The actions you provided are just "dumb" functions, which are not returning promises so you can't expect it to be "then"-able in your target component.
I also advise the async/await syntax since it is much more readable.
Third, you need to leverage the Hooks reactive API with the useEffect hook.
So first try to define getFloorPlan and getGraph as async actions using the redux-thunk syntax.
export const getGraphAsync = (site) => async (dispatch) => {
try {
const data = await getData(`api/graph/${site}`, GET_GRAPHS);
dispatch(saveGraphData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
export const getFloorplanAsync = (site) => async (dispatch) => {
try {
const data = await getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
dispatch(saveImageData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
I am making an assumption that you correctly configured your store.js to use the thunk middleware.
And then refactor the rest of the component (following some best practices):
const someHookComponent = () => {
// ...
const dispatch = useDispatch();
const currentSite = useSelector((state) =>
state.selection.currentSite);
const currentCamera = useSelector((state) =>
state.selection.currentCamera);
const loadGraph = async () => {
if (currentSite) {
await dispatch(getFloorPlanAsync(currentSite.identif));
console.log('Fetched floorplan');
await dispatch(getGraphAsync(currentSite.identif));
console.log('Fetched model', realGraph.model); /* where is
realGraph coming from? */
/* Why is it important that these 2 dispatches follow one
another when there is no data being passed from one to the
other, or being used later in the component... */
});
});
}
};
useEffect(() => {
// new camera-related node & link status
if (currentCamera) {
dispatch(changeActiveCamera(
currentSite.identif,
currentCamera.identif
));
}
}, [currentSite?.identif, currentCamera?.identif]) /* null chaining is optional here */
// ...
}
I am guessing that loadGraph gets called by some onClick event somewhere down the line like this:
onClick={loadGraph}
If it is called inside useEffect, define the deps (variables used inside loadGraph):
useDeepCompareEffect(() => {
// ... some logic
loadGraph()
}, [currentSite, realGraph])
If you put your currentSite and currentCamera objects directly into the useEffect list of deps then you need to do a deep comparison "by hand".
In that case it's best to create a custom hook like useDeepCompareEffect which will do the heavy lifting of running deep comparisons of reference types under the hood (with the help of some library like lodash for example).
If you want to use or console.log the latest value of realGraph (reference type), you need to use the useEffect hook with a deep comparison again (or just extract the target primitive directly into the deps list and use vanilla useEffect) :
useDeepCompareEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph]) // reference type
// or
useEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph.someProperty]) // primitive

Async does wait for data to be returned in a redux-thunk function

I've being trying populate my redux store with data that comes from my mongo-db realm database.
Whenever I run the function below it will execute fine but the problem is data will be delayed and ends up not reaching my redux store.
My thunk function:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
return await Realm.open(config).then(async (projectRealm) => {
let syncItems = await projectRealm.objects("Item");
await syncItems.addListener((x, changes) => {
x.map(async (b) => {
console.log(b);
return b;
});
});
});
} catch (error) {
console.log(error);
throw error;
}
}
);
and my redux reducer:
extraReducers: (builder) => {
builder.addCase(addItemsTest.fulfilled, (state, { payload }: any) => {
try {
console.log("from add Items");
console.log(payload);
state.push(payload);
} catch (error) {
console.log(error)
}
});
}
Expected Results:
My redux store should have these data once addItemsTest return something:
[{
itemCode: 1,
itemDescription: 'Soccer Ball',
itemPrice: '35',
partition: 'partitionValue',
},
{
itemCode: 2,
itemDescription: 'Base Ball',
itemPrice: '60',
partition: 'partitionValue',
}
]
Actual Results:
Mixed Syntaxes
You are combining await/async and Promise.then() syntax in a very confusing way. It is not an error to mix the two syntaxes, but I do not recommend it. Stick to just await/async
Void Return
Your action actually does not return any value right now because your inner then function doesn't return anything. The only return is inside of the then is in the x.map callback. await syncItems is the returned value for the mapper, not for your function.
Right now, here's what your thunk does:
open a connection
get items from realm
add a listener to those items which logs the changes
returns a Promise which resolves to void
Solution
I believe what you want is this:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
const projectRealm = await Realm.open(config);
const syncItems = await projectRealm.objects("Item");
console.log(syncItems);
return syncItems;
} catch (error) {
console.log(error);
throw error;
}
}
);
Without the logging, it can be simplified to:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
const projectRealm = await Realm.open(config);
return await projectRealm.objects("Item");
}
);
You don't need to catch errors because the createAsyncThunk will handle errors by dispatching an error action.
Edit: Listening To Changes
It seems that your intention is to sync your redux store with changes in your Realm collection. So you want to add a listener to the collection that calls dispatch with some action to process the changes.
Here I am assuming that this action takes an array with all of the items in your collection. Something like this:
const processItems = createAction("processItems", (items: Item[]) => ({
payload: items
}));
Replacing the entire array in your state is the easiest approach. It will lead to some unnecessary re-renders when you replace item objects with identical versions, but that's not a big deal.
Alternatively, you could pass specific properties of the changes such as insertions and handle them in your reducer on a case-by-case basis.
In order to add a listener that dispatches processItems, we need access to two variables: the realm config and the redux dispatch. You can do this in your component or by calling an "init" action. I don't think there's really much difference. You could do something in your reducer in response to the "init" action if you wanted.
Here's a function to add the listener. The Realm.Results object is "array-like" but not exactly an array so we use [...x] to cast it to an array.
FYI this function may throw errors. This is good if using in createAsyncThunk, but in a component we would want to catch those errors.
const loadCollection = async (config: Realm.Configuration, dispatch: Dispatch): Promise<void> => {
const projectRealm = await Realm.open(config);
const collection = await projectRealm.objects<Item>("Item");
collection.addListener((x, changes) => {
dispatch(processItems([...x]));
});
}
Adding the listener through an intermediate addListener action creator:
export const addListener = createAsyncThunk(
"init",
async (config: Realm.Configuration, { dispatch }) => {
return await loadCollection(config, dispatch);
}
);
// is config a prop or an imported global variable?
const InitComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
dispatch(addListener(config));
}, [config, dispatch]);
/* ... */
}
Adding the listener directly:
const EffectComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
// async action in a useEffect need to be defined and then called
const addListener = async () => {
try {
loadCollection(config, dispatch);
} catch (e) {
console.error(e);
}
}
addListener();
}, [config, dispatch]);
/* ... */
}

Categories