Cannot convert undefined or null to object : Next.js || React - arrays

Currently receiving a error stating Cannot convert undefined or null to object
The data the app is looking for comes from initial props.
I assume on the initial check no data is present, so it throws that error.
Would this be solved with an async/await ?
The initial posts_mentions is defaulted as an empty object
Here is the error image:
Here is the current code snippet
const { posts_mentions: postsMentions } = useData();
const data = Object.keys(postsMentions).map(label => {
return {
name: shortName(label),
posts: postsMentions[label].posts,
mentions: postsMentions[label].mentions
}
})

async function something(){
const { posts_mentions: postsMentions } = await useData();
const data = Object.keys(postsMentions).map(label => {
return {
name: shortName(label),
posts: postsMentions[label].posts,
mentions: postsMentions[label].mentions
}
})
}

Yes. Try adding await before useData(). And if this is all part of bigger function than mark it as async. How does the useData() look? Is something async in it?

Related

uswSWRInfinite shows stale data when mutating with local data

I use uswSWRInfinite to paginate data by a cursor (the id of the last loaded item). When I edit or delete items on page 2+ (for some reason, this doesn't happen on page 1), and I mutate by passing modified data, after revalidation, I see the old data again.
This does not happen if I either 1. mutate without local data (only revalidation) or 2. disable revalidation (only mutate with local data). Both together cause the bug.
Here's the relevant code:
useSWRInfinite setup:
const {
data: repliesPages,
size: repliesPagesSize,
setSize: setRepliesPagesSize,
isLoading: repliesLoading,
error: repliesLoadingError,
mutate: mutateRepliesPages,
} = useSWRInfinite(
getPageKey,
([commentId, lastReplyId]) => BlogApi.getRepliesForComment(commentId, lastReplyId));
The update/delete callbacks:
replies?.map(reply => (
<CommentBody
comment={reply}
onReplyCreated={addLocalReply}
key={reply._id}
onCommentUpdated={(updatedReply) => {
const updatedRepliesPages = repliesPages?.map(page => {
const updatedReplies = page.comments.map(existingReply => existingReply._id === updatedReply._id ? updatedReply : existingReply);
const updatedPage: GetCommentsResponse = { ...page, comments: updatedReplies };
return updatedPage;
});
mutateRepliesPages(updatedRepliesPages); // this works properly if I don't pass data or set revalidate : false
}}
onCommentDeleted={() => {
const updatedRepliesPages = repliesPages?.map(page => {
const updatedReplies = page.comments.filter(existingReply => existingReply._id !== reply._id);
const updatedPage: GetCommentsResponse = { ...page, comments: updatedReplies };
return updatedPage;
});
mutateRepliesPages(updatedRepliesPages); // this works properly if I don't pass data or set revalidate : false
}}
/>
));
The callbacks are triggered after we got the updated item back from the server:
async function onSubmit({ text }: { text: string }) {
if (!text) return;
try {
const updatedComment = await BlogApi.updateComment(comment._id, text);
onCommentUpdated(updatedComment);
} catch (error) {
console.error(error);
alert(error);
}
}
async function deleteComment() {
try {
setDeleteInProgress(true);
await BlogApi.deleteComment(comment._id);
onCommentDeleted();
} catch (error) {
console.error(error);
alert(error);
} finally {
setDeleteInProgress(false);
}
}
Here's a recording of the problem happening:
The behavior I expect, is that SWR shows the updated data after revalidation.
You need to fire a request to update the data on the server (BlogApi).
mutate() will update the data on the client side, but not on the server. You're updating the data locally, then the data revalidates (refetches), which replaces the local data with the server data, undoing your updates.
Add the appropriate request (probably POST) to your code. You can add it immediately before mutateRepliesPage(), or you can include it as part of the function passed to mutate's second argument like in this example.

Why is React Native AsyncStorage not updating state on mount?

When I try to load the state from AsyncStorage for the screen I just navigated to, I am getting this error:
TypeError: undefined is not an object (evaluating 'weights[numExercise].map') It is trying to use the initial state that the screen initializes the state with, but I want the state to be loaded with the data that I specifically try to load it with on mount, within my useEffect hook.
const WorkoutScreen = ({ navigation, route }) => {
const [workoutName, setWorkoutName] = useState("");
const [exercisesArr, setExercisesArr] = useState([""]);
// Each array inside the arrays (weights & reps), represents an exercise's sets.
const [weights, setWeights] = useState([[""]]);
const [reps, setReps] = useState([[""]]);
const [restTimers, setRestTimers] = useState([""]);
useEffect(() => {
try {
console.log("loading workoutscreen data for:", route.params.name);
const unparsedWorkoutData = await AsyncStorage.getItem(route.params.name);
if (unparsedWorkoutData !== null) {
// We have data!
const workoutData = JSON.parse(unparsedWorkoutData);
setWorkoutName(route.params.name.toString());
setExercisesArr(workoutData[0]);
setWeights(workoutData[1]);
setReps(workoutData[2]);
setRestTimers(workoutData[3]);
}
} catch (error) {
// Error retrieving data
console.log("ERROR LOADING DATA:", error);
}
}, []);
Then further down the line in a component it realizes the error because, again, it's using the initialized state for the weights state.
Return (
{weights[numExercise].map((weight, i) => {
return (
<SetComponent
key={i}
numSet={i}
numExercise={numExercise}
prevWeights={prevWeights}
weights={weights}
setWeights={setWeights}
prevReps={prevReps}
reps={reps}
setReps={setReps}
isDoneArr={isDoneArr}
setIsDoneArr={setIsDoneArr}
/>
);
})}
);
I've made sure that the data is being stored, loaded, and used correctly, so (I think) I've narrowed it down to be something asynchronous; whether it's the setting of the state or loading from storage, I don't know and I can't find a solution. I am new to React Native and would love some suggestions, thank you!
It turns out that using multiple states was causing an issue, I'm assuming because it's asynchronous. So instead I used one state that held an object of states, like so:
const [states, setStates] = useState({
workoutName: "",
exercisesArr: [""],
weights: [[""]],
reps: [[""]],
restTimers: [""],
isDoneArr: [[false]],
originalWorkoutName: "",
});
The data was loaded as such:
const loadWorkoutData = async () => {
try {
console.log("loading workoutscreen data for:", route.params.name);
const unparsedWorkoutData = await AsyncStorage.getItem(route.params.name);
if (unparsedWorkoutData !== null) {
// We have data!
const workoutData = JSON.parse(unparsedWorkoutData);
setStates({
workoutName: route.params.name,
exercisesArr: workoutData[0],
weights: workoutData[1],
reps: workoutData[2],
restTimers: workoutData[3],
isDoneArr: workoutData[4],
originalWorkoutName: route.params.name,
});
}
} catch (error) {
// Error retrieving data
console.log("ERROR LOADING DATA:", error);
}
};

Saving an ID value from an API to a User with GraphQL

I'm working on a video game website where a user can save a game to a list. How this is supposed to work is when the user clicks "Complete Game", the ID of the game is saved to a state that holds the value. The value is then passed into the mutation, then the mutation runs, saving the ID of the game to the users list of completed games. However, all I'm seeing in the console is this:
"GraphQLError: Variable \"$addGame\" got invalid value { gameId: 740, name: \"Halo: Combat Evolved\",
The above error continues, listing the entirety of the API response, instead of just the gameId.
I was able to successfully add the game to the list in the explorer with the following mutation:
mutation completeGame($addGame: AddNewGame!) {
completeGame(addGame: $addGame) {
_id
completedGameCount
completedGames {
gameId
}
}
}
with the following variable:
{
"addGame": {"gameId": 740}
}
How can I trim down what is being passed into the mutation to just be the gameId?
Below is the entirety of the page, except the return statement at the bottom.
const [selectedGame, setSelectedGame] = useState([]);
const [savedGameIds, setSavedGameIds] = useState(getSavedGameIds());
const [completeGame, { error }] = useMutation(COMPLETE_GAME);
const { id: gameId } = useParams();
useEffect(() => {
return () => saveGameIds(savedGameIds);
});
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
platformId: game.platforms,
genres: game.genres,
genreId: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
const handleCompleteGame = async (gameId) => {
const gameToComplete = selectedGame.find((game) => game.gameId === gameId);
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
console.log(data);
setSavedGameIds([...savedGameIds, gameToComplete]);
} catch (err) {
console.error(err);
}
};
With the mutation working in the explorer when I'm able to explicitly define the variable, I am led to believe that the issue is not with the resolver or the typedef, so I'm going to omit those from this post because I don't want it to get too long.
However, I'd be happy to attach any extra code (resolver, typeDef, getSavedGameIds function, etc) if it would allow anyone to assist. The issue (I think) lies in getting my response to match the syntax I used in the explorer, which means trimming down everything except the gameId.
I specifically am extremely suspicious of this line
const gameToComplete = selectedGame.find((game) => game.gameId === gameId)
but I have fiddled around with that for awhile to no avail.
Thank you to anyone who is able to help!
It sounds like you're trying to pass more into your mutation then your schema is defined to allow. In this part:
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
You're spreading gameToComplete here which means everything in the gameToComplete object is going to be sent as a variable. If your schema is setup to just expect gameId to be passed in, but your error message is showing that name is also being passed in, you just need to adjust your variables to exclude everything you can't accept. Try:
const { data } = await completeGame({
variables: { addGame: { gameId } },
});

Fetch complex data from API, create a array of objects and iterate this

I'm trying to make a array of complex objects from this:
https://demo.ckan.org/api/3/action/package_show?id=sample-dataset-1
I want use this array of objects to create components using map()
I used http://json2ts.com to make the type for the response:
// ... other types like Tag
export type DatasetInfoResult = {
license_title: string
relationships_as_object: string[]
maintainer_email: string
tags: Tag[]
//... all the params
}
export type DatasetInfoResponse = {
help: string
success: boolean
result: DatasetInfoResult
}
My code:
datasetIDs is a array of strings. This contain the dataset names, like sample-dataset-1
getDataset fetch the API adress, and is ok
export async function getAllDatasets() {
const datasetsIDs = (await listDatasets())
let allDatasets: DatasetInfoResult[]
datasetsIDs.map( async id => {
allDatasets.push( await getDataset(id) )
})
return allDatasets
Error message: TypeError: Cannot read properties of undefined (reading 'push')
Trying another way:
export async function getAllDatasets() {
const datasetsIDs = (await listDatasets())
let allDatasets: DatasetInfoResult[]
for (let i = 0; i <= datasetsIDs.length; i++ ) {
const resp = ( await getDataset( datasetsIDs[i]) )
allDatasets = [...allDatasets, resp]
// allDatasets.push(resp)
}
return allDatasets
Error message: allDatasets is not iterable
How can I make this? What is a better way to get many data and use this?
In the first example you're trying to push to undefined because allDatasets wasn't initialised with a value.
let allDatasets: DatasetInfoResult[] // this is undefined, so you can't push to it
datasetsIDs.map( async id => {
allDatasets.push( await getDataset(id) )
})
You could try
let allDatasets: DatasetInfoResult[] = []
datasetsIDs.map( async id => {
allDatasets.push( await getDataset(id) )
})

Redcuers returning undefined in reactJS

I have a problem with my dispatch.
Somehow it returns undefined even tho when I loged value of the input i want to pass I got the correct value.
Here is my reducer.
case actionTypes.ADD_COMMENT:
return {
...state,
posts : [
...posts,
{
comment : action.payload.comment,
}
]
}
export const addComment = (payload : any) => {
return {
type: actionTypes.ADD_COMMENT,
payload
}
}
And here is how i m passing the value to the dispatch.
if (commentValue) {
let commentVal = commentValue.value
console.log('--------commentVal', commentVal);
dispatch(actions.addComment({commentVal}))
}
Any suggestions?
It should be
dispatch(action.addComment({comment: commentVal}))
since you are accessing the comment property on the payload in your reducer

Resources