React state not giving correct value on useEffect() - javascript

I'm trying to build a factorization algorithm using react. I would like to add results to LocalStorage based on results from factorization. However, LocalStorage sets previous results not current ones.
I think this is happening because useEffect runs on every new [number] (=user input) and not based on [results]. However, I need useEffect to run on new user input submition because that's when factorization has to be triggered.
How could I make localStorage set correct results after that factorization has completed (on the finally block if possible) ?
const [results, setResults] = useState({
default: [],
detailed: [],
isPrime: '',
});
const { number } = props
useEffect(() => {
handleWorker(number);
//accessing results here will give previous results
}, [number]);
const handleWorker = number => {
try {
const worker = new Worker(new URL('facto.js', import.meta.url));
worker.postMessage({ number, algorithm });
worker.onmessage = ({ data: { facto } }) => {
setResults({ ...facto });
//the worker 'streams live' results and updates them on setResults
};
} catch(error) {
console.log(error.message)
} finally {
localStorage.setItem(number, results)
//here, results is not current state but previous one
}
};
Please note that everything else works fine
Thanks

You are getting the previous value because localStorage.setItem is executed before setResults updates the state. Yo can do some refactor to make it work:
const [results, setResults] = useState({
default: [],
detailed: [],
isPrime: '',
});
const { number } = props;
const workerRef = useRef(new Worker(new URL('facto.js', import.meta.url)));
const worker = workerRef.current;
useEffect(()=> {
//-> create the listener just once
worker.onmessage = ({ data: { facto } }) => {
setResults({ ...facto });
};
}, []);
useEffect(() => {
//-> send message if number changed
worker.postMessage({ number, algorithm });
}, [number]);
useEffect(() => {
//-> update localStorage if results changed
localStorage.setItem(number, results)
}, [results]);

Here is what you need (probably):
const [results, setResults] = useState({ /* YOUR STATE */ });
const { number } = props
const handleWorker = useCallback((number) => {
// DO WHATEVER YOU NEED TO DO HERE
},[]); // IF YOU DEPEND ON ANY MUTABLE VARIABLES, ADD THEM TO THE DEPENDENCY ARRAY
useEffect(() => {
// DO WHATEVER YOU NEED TO DO HERE
// INSTEAD OF localStorage.setItem(number, results)
// DO localStorage.setItem(number, { ...facto })
}, [number,handleWorker]);
Let me know if it works.

I think you can pass in the result state as dependent for the useEffect, so whenever the value of the result change , useEffect runs
useEffect(() => { //code goes here}, [results]);
Let say results also depends on a function handleWorker
useEffect(() => { //code goes here},
[results,handleWorker]);

Related

React-query setQueryData not re-rendering component

I've been dealing for a while with this problem and still can't tackle it.
I'm using React-query as a server state management library and I'm trying to get my UI state synchronized with my server state when a mutations occurs. Since I can use the mutation response to avoid a new API call, I'm using the setQueryData feature that React-query gives us.
The problem is that the old-data is being correctly modified (I can see it in the react-query DevTools) when a mutation is successful, but the component using it isn't being re-rendered, making my UI State not synchronized with my Server state (well, at least the user can't see the update).
Let me show some code and hope someone can give me some insights.
Component using the query:
const Detail = ({ orderId }) => {
const { workGroups } = useWorkGroups();
const navigate = useNavigate();
const queryClient = useQueryClient();
const orderQueries = queryClient.getQueryData(["orders"]);
const queryOrder = orderQueries?.find((ord) => ord.id === orderId);
// more code
Component mutating the query:
const Deliver = ({
setIsModalOpened,
artisan,
index,
queryQuantity,
queryOrder,
}) => {
const [quantity, setQuantity] = useState(() => queryQuantity);
const { mutate: confirmOrderDelivered } = useMutateOrderDeliveredByArtisan(
queryOrder.id
);
const onSubmit = () => {
confirmOrderDelivered(
{
id: queryOrder.artisan_production_orders[index].id,
artisan: artisan.user,
items: [
{
quantity_delivered: quantity,
},
],
},
{
onSuccess: setIsModalOpened(false),
}
);
};
// more code
Now the mutation function (ik it's a lot of logic but I dont' want to refetch the data using invalidateQueries since we're dealing with users with a really bad internet connection). Ofc you don't need to understand each step of the fn but what it basically does is update the old queried data. In the beginning I thought it was a mutation reference problem since React using a strict comparison under the hood but I also checked it and It doesn't look like it's the problem. :
{
onSuccess: (data) => {
queryClient.setQueryData(["orders"], (oldQueryData) => {
let oldQueryDataCopy = [...oldQueryData];
const index = oldQueryDataCopy.findIndex(
(oldData) => oldData.id === orderId
);
let artisanProdOrders =
oldQueryDataCopy[index].artisan_production_orders;
let artisanProductionOrderIdx = artisanProdOrders.findIndex(
(artProdOrd) => artProdOrd.id === data.id
);
artisanProdOrders[artisanProductionOrderIdx] = {
...artisanProdOrders[artisanProductionOrderIdx],
items: data.items,
};
const totalDelivered = artisanProdOrders.reduce((acc, el) => {
const delivered = el.items[0].quantity_delivered;
return acc + delivered;
}, 0);
oldQueryDataCopy[index] = {
...oldQueryDataCopy[index],
artisan_production_orders: artisanProdOrders,
items: [
{
...oldQueryDataCopy[index].items[0],
quantity_delivered: totalDelivered,
},
],
};
return oldQueryDataCopy;
});
},
onError: (err) => {
throw new Error(err);
},
}
And last but not least: I already checked that the oldQueryData is being correctly modified (console loging in the onSuccess fn in the mutation response) and, as I said before, the data is correctly modified in the React-query DevTools.
I know this is a lot of code and the problem seems to be complex but I really believe that it might be a really easy thing that I'm not pointing out because of how tired I already am.
Thanks!
Well, I fixed it in the worst possible way imho, so I will answer this question but I really would like to read your thoughts.
It looks like the new query data setted on the expected query is re-rendering the component only if the mutation function is located in the component that we actually want to re-render.
With that in mind what I did was just colocate my mutation function in the parent component and pass it down through the child component.
Something like this:
const Detail = ({ orderId }) => {
const { workGroups } = useWorkGroups();
const navigate = useNavigate();
const { mutate: confirmOrderDelivered } = useMutateOrderDeliveredByArtisan(
queryOrder.id
); ==============> THE MUTATION FN IS NOW IN THE PARENT COMPONENT
const queryClient = useQueryClient();
const orderQueries = queryClient.getQueryData(["orders"]);
const queryOrder = orderQueries?.find((ord) => ord.id === orderId);
// more code
First child:
const Assigned = ({ artisan, queryOrder, index, confirmOrderDelivered }) => {
// THE IMPORTANT PART HERE IS THE PROP BEING PASSED DOWN.
<Modal
isOpen={isModalOpened}
toggleModal={setIsModalOpened}
// className="w312"
width="80%"
height="fit-content"
justifyCont="unset"
alignItems="unset"
>
<Deliver
setIsModalOpened={setIsModalOpened}
artisan={artisan}
queryQuantity={quantity}
queryOrder={queryOrder}
index={index}
confirmOrderDelivered={confirmOrderDelivered} => HERE
/>
</Modal>
Component that actually needs the mutation fn:
const Deliver = ({
setIsModalOpened,
artisan,
index,
queryQuantity,
queryOrder,
confirmOrderDelivered,
}) => {
const [quantity, setQuantity] = useState(() => queryQuantity);
const onSubmit = () => {
confirmOrderDelivered( => HERE.
{
id: queryOrder.artisan_production_orders[index].id,
artisan: artisan.user,
items: [
{
quantity_delivered: quantity,
},
],
}
);
};
You can't mutate any prop.
You always need to create new versions of the objects and props and use destructuring.
Example
queryClient.setQueryData([QUERY_KEYS.MYKEY], (old) => {
const myArray = [...old.myArray];
return {
...old,
// WRONG
myArray[0].name: 'text1',
// CORRECT
myArray[0]: {
...myArray[0],
name: 'text1'
}
}})

How do I merge the results of two Firestore Realtime listeners into one array of objects using React

I am trying to merge the data received from two real-time listeners into one requests array. The array contains objects as its elements. This is so I can pass one single requests prop and manipulate that in my Dashboard component.
In Firestore, my documents in my Requests collection contain employeeUser and managerUser as references to another Users collection which contains information about the user such as their username. So I am also trying to receive their username within my snapshot as well.
Here is an example of the fields in the document of the Requests collection:
I am having issues merging the data from both listeners into one requests array using useEffect and useState hooks. Also, when I try to use the spread syntax to merge arrays it doesn't seem to actually have an effect on setRequests. When I use a callback setRequests((prev) => [...prev, ...employeeRequests]) it doesn't seem to do anything either.
In a nutshell, with the current code I have below, I seem to be receiving the correct data in the console (only in managerRequests & employeeRequests arrays) but I cannot merge the two arrays together and it is not displaying properly after all renders have been completed.
I did also notice that while I am editing the code and make a change right after that involves requests, the hot reload re-renders the page once more and then everything displays correctly. Once I refresh the page, nothing displays again until I have another hot reload that affects setRequests. I am wondering if I have to trigger some more renders somehow to get all the updates in the useEffect dependency array.
Here is my code so far:
const [requests, setRequests] = useState([]); // the array where I am trying to merge both managerRequests and employeeRequests into (cause its the only way I've been able to get it to kinda work)
const [managerRequests, setManagerRequests] = useState([]);
const [employeeRequests, setEmployeeRequests] = useState([]);
useEffect(() => {
if (firebase) {
//Set up first listener
const unsubscribe = firebase.getManagerRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const managerRequests = [];
snapshot.forEach(async doc => {
const [managerPromise, employeePromise] = await Promise.all([doc.data().managerUser.get(), doc.data().employeeUser.get()]); // single get() queries to retrieve employee and manager usernames from referenced User collection
var managerUsername = managerPromise.data().username;
var employeeUsername = employeePromise.data().username;
managerRequests.push({
id: doc.id,
managerUsername: managerUsername,
employeeUsername: employeeUsername,
...doc.data()
})
})
// console.log(managerRequests) -> managerRequests contains the correct set of data in an array of objects ex: [{id:1, ...}, {id:2, ...}]
setManagerRequests(managerRequests);
setLoadingElements((vals) => ({ ...vals, managerRequestsLoading: false })) // Currently not using these as barrier flags
}
})
//Set up second listener
const unsubscribetwo = firebase.getEmployeeRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const employeeRequests = [];
snapshot.forEach(async doc => {
const [managerPromise, employeePromise] = await Promise.all([doc.data().managerUser.get(), doc.data().employeeUser.get()]);
var managerUsername = managerPromise.data().username;
var employeeUsername = employeePromise.data().username;
employeeRequests.push({
id: doc.id,
managerUsername: managerUsername,
employeeUsername: employeeUsername,
...doc.data()
})
})
// console.log(employeeRequests) > employeeRequests contains the correct set of data in an array of objects ex: [{id:1, ...}, {id:2, ...}]
setEmployeeRequests(employeeRequests);
setLoadingElements((vals) => ({ ...vals, employeeRequestsLoading: false })) // Currently not using these as barrier flags
}
})
setRequests([...managerRequests, ...employeeRequests]); // This does not seem to work. requests has nothing in it in the last render
return () => {
if (unsubscribe) {
unsubscribe();
}
if (unsubscribetwo) {
unsubscribetwo();
}
}
}
}, [firebase, user])
console.log(requests) // contains nothing in requests
return (
<>
<Navbar />
<Dashboard requests={requests} /> // This is where I want to pass an array of the merged employee and manager requests as a prop to my Dashboard component
<Footer />
</>
)
Edit: Here is how I fixed it with the help of the first answer:
const [requests, setRequests] = useState([]);
const [managerRequests, setManagerRequests] = useState([]);
const [employeeRequests, setEmployeeRequests] = useState([]);
const [managerUsername, setManagerUsername] = useState('');
const [employeeUsername, setEmployeeUsername] = useState('');
const [managerUsernameEmployeeSide, setManagerUsernameEmployeeSide] = useState('');
const [employeeUsernameEmployeeSide, setEmployeeUsernameEmployeeSide] = useState('');
useEffect(() => {
if (firebase) {
//Set up first listener
const unsubscribe = firebase.getManagerRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const managerRequests = [];
snapshot.forEach(doc => {
// Had to remove await Promise.all(...) to get it to work
doc.data().managerUser.get()
.then(res => {
setManagerUsername(res.data().username);
})
doc.data().employeeUser.get()
.then(res => {
setEmployeeUsername(res.data().username);
})
managerRequests.push({
id: doc.id,
managerUsername: managerUsername,
employeeUsername: employeeUsername,
...doc.data()
})
})
setManagerRequests(managerRequests);
}
})
return () => {
if (unsubscribe) {
unsubscribe();
}
}
}
}, [firebase, user, managerUsername, employeeUsername])
useEffect(() => {
if (firebase) {
//Set up second listener
const unsubscribetwo = firebase.getEmployeeRequests({
userUid: user.uid, onSnapshot: (snapshot) => {
const employeeRequests = [];
snapshot.forEach(doc => {
const [managerPromise, employeePromise] = await Promise.all([doc.data().managerUser.get(), doc.data().employeeUser.get()]);
var managerUsername = managerPromise.data().username;
var employeeUsername = employeePromise.data().username;
doc.data().managerUser.get()
.then(res => {
setManagerUsernameEmployeeSide(res.data().username);
})
doc.data().employeeUser.get()
.then(res => {
setEmployeeUsernameEmployeeSide(res.data().username);
})
employeeRequests.push({
id: doc.id,
managerUsername: managerUsernameEmployeeSide,
employeeUsername: employeeUsernameEmployeeSide,
...doc.data()
})
})
setEmployeeRequests(employeeRequests);
}
})
}
return () => {
if (unsubscribetwo) {
unsubscribetwo();
}
}
}, [firebase, user, managerUsernameEmployeeSide, employeeUsernameEmployeeSide]
useEffect(() => {
if (managerRequests.length > 0 && employeeRequests.length > 0) {
setTransactions([...managerRequests, ...employeeRequests]);
}
}, [managerRequests, employeeRequests])
return (
<>
<Navbar />
<Dashboard requests={requests} /> // Requests finally receives one array with all values from managerRequests and employeeRequests combined
<Footer />
</>
)
You subscribe to firebase.getManagerRequests and firebase.getEmployeeRequests with callbacks (snapshot) => { // ... }. Those callbacks will not be executed synchronously. They will be executed when the responses of the requests come back. So there are no managerRequests and employeeRequests when your setRequests([...managerRequests, ...employeeRequests]).
Here is a simple example solution
const MyComponent = () => {
const [state1, setState1] = React.useState([])
const [state2, setState2] = React.useState([])
React.useEffect(() => {
// Do the first subscribe
// state1 will be updated in the subscription's callback
}, [
// Dependencies of this effect
])
React.useEffect(() => {
// Do the second subscribe
// state2 will be updated in the subscription's callback
}, [
// Dependencies of this effect
])
React.useEffect(() => {
// Merge state1 and state2 here
}, [
state1,
state2
])
}
---- updated#20211203 ----
setState is asynchronous. You can checkout https://reactjs.org/docs/react-component.html#setstate for more information.
useEffect(() => {
// for example
console.log(state1) // [1, 2, 3]
console.log(state2) // [7, 8, 9]
setRequests([...state1, ...state2])
console.log(requests) // this is not guaranteed to be [1, 2, 3, 7, 8, 9]
}. [state1, state2])
If you want to do something based on the merged requests in the same component. You can write another useEffect:
useEffect(() => {
console.log(requests) // [1, 2, 3, 7, 8, 9]
}, [requests])

State does not update in async function

Could you please help me understand why my state was not updated when I called two async functions in the first useEffect? and what is the best way to handle async data in the case that I don't know which one comes first (API1 or API2)?
Thank you!
const MyClass = () => {
const [myState, setMyState] = useState([]);
useEffect(() => {
callApi1();
callApi2();
}, []);
const callApi1 = () => {
fetch(...).then(result => {
// the result of API 1 always comes first and result is not empty
setMyState(result);
)};
}
const callApi2 = () => {
fetch(...).then(result => {
// the result of API 2 always comes 5 - 10 seconds after the API 1
console.log(myState) => [], WHY?
});
}
}
(1.) "... why my state was not updated ..."
Your state was updated, but the callback function captures the old state of myState (as a closure). That means myState inside the callback function will always stay the same as it was when the function was created. (And it is created only when callApi2() is invoked.)
You can not access the current up-to-date state inside an asynchronous callback.
(2.) "...best way to handle async data in the case that I don't know which one comes first"
It depends on your use case.
Generally, you would set some states from your callbacks (e.g. your setMyState(result)), and a different part of your program will do something else dependent on these states, e.g. useEffect(()=>{ /* do something */ }, [ myState ]).
e.g.:
const MyClass = () => {
const [myState, setMyState] = useState([]);
const [myState2, setMyState2] = useState([]);
const [allDone, setAllDone] = useState(false);
useEffect(() => {
callApi1();
callApi2();
}, []);
useEffect(() => {
console.log( 'myState/myState2:', myState, myState2);
if( myState.length && myState2.length ){
setAllDone(true);
}
}, [ myState, myState2 ]);
const callApi1 = () => {
fetch(...).then(result => {
setMyState(result);
)};
}
const callApi2 = () => {
fetch(...).then(result => {
setMyState2(result);
});
}
}

setState in nested async function - React Hooks

How can I build a function which gets some data asynchronously then uses that data to get more asynchronous data?
I am using Dexie.js (indexedDB wrapper) to store data about a direct message. One thing I store in the object is the user id which I'm going to be sending messages to. To build a better UI I'm also getting some information about that user such as the profile picture, username, and display name which is stored on a remote rdbms. To build a complete link component in need data from both databases (local indexedDB and remote rdbms).
My solution returns an empty array. It is being computed when logging it in Google Chrome and I do see my data. However because this is not being computed at render time the array is always empty and therefor I can't iterate over it to build a component.
const [conversations, setConversations] = useState<IConversation[]>()
const [receivers, setReceivers] = useState<Profile[]>()
useEffect(() => {
messagesDatabase.conversations.toArray().then(result => {
setConversations(result)
})
}, [])
useEffect(() => {
if (conversations) {
const getReceivers = async () => {
let receivers: Profile[] = []
await conversations.forEach(async (element) => {
const receiver = await getProfileById(element.conversationWith, token)
// the above await is a javascript fetch call to my backend that returns json about the user values I mentioned
receivers.push(receiver)
})
return receivers
}
getReceivers().then(receivers => {
setReceivers(receivers)
})
}
}, [conversations])
/*
The below log logs an array with a length of 0; receivers.length -> 0
but when clicking the log in Chrome I see:
[
0: {
avatarURL: "https://lh3.googleusercontent.com/..."
displayName: "Cool guy"
userId: "1234"
username: "cool_guy"
}
1: ...
]
*/
console.log(receivers)
My plan is to then iterate over this array using map
{
receivers && conversations
? receivers.map((element, index) => {
return <ChatLink
path={conversations[index].path}
lastMessage={conversations[index].last_message}
displayName={element.displayName}
username={element.username}
avatarURL={element.avatarURL}
key={index}
/>
})
: null
}
How can I write this to not return a empty array?
Here's a SO question related to what I'm experiencing here
I believe your issue is related to you second useEffect hook when you attempt to do the following:
const getReceivers = async () => {
let receivers: Profile[] = []
await conversations.forEach(async (element) => {
const receiver = await getProfileById(element.conversationWith, token)
receivers.push(receiver)
})
return receivers
}
getReceivers().then(receivers => {
setReceivers(receivers)
})
}
Unfortunately, this won't work because async/await doesn't work with forEach. You either need to use for...of or Promise.all() to properly iterate through all conversations, call your API, and then set the state once it's all done.
Here's is a solution using Promise.all():
function App() {
const [conversations, setConversations] = useState<IConversation[]>([]);
const [receivers, setReceivers] = useState<Profile[]>([]);
useEffect(() => {
messagesDatabase.conversations.toArray().then(result => {
setConversations(result);
});
}, []);
useEffect(() => {
if (conversations.length === 0) {
return;
}
async function getReceivers() {
const receivers: Profile[] = await Promise.all(
conversations.map(conversation =>
getProfileById(element.conversationWith, token)
)
);
setReceivers(receivers);
}
getReceivers()
}, [conversations]);
// NOTE: You don't have to do the `receivers && conversations`
// check, and since both are arrays, you should check whether
// `receivers.length !== 0` and `conversations.length !== 0`
// if you want to render something conditionally, but since your
// initial `receivers` state is an empty array, you could just
// render that instead and you won't be seeing anything until
// that array is populated with some data after all fetching is
// done, however, for a better UX, you should probably indicate
// that things are loading and show something rather than returning
// an empty array or null
return receivers.map((receiver, idx) => <ChatLink />)
// or, alternatively
return receivers.length !== 0 ? (
receivers.map((receiver, idx) => <ChatLink />)
) : (
<p>Loading...</p>
);
}
Alternatively, using for...of, you could do the following:
function App() {
const [conversations, setConversations] = useState<IConversation[]>([]);
const [receivers, setReceivers] = useState<Profile[]>([]);
useEffect(() => {
messagesDatabase.conversations.toArray().then(result => {
setConversations(result);
});
}, []);
useEffect(() => {
if (conversations.length === 0) {
return;
}
async function getReceivers() {
let receivers: Profile[] = [];
const profiles = conversations.map(conversation =>
getProfileById(conversation.conversationWith, token)
);
for (const profile of profiles) {
const receiver = await profile;
receivers.push(receiver);
}
return receivers;
}
getReceivers().then(receivers => {
setReceivers(receivers);
});
}, [conversations]);
return receivers.map((receiver, idx) => <ChatLink />);
}
i think it is happening because for getReceivers() function is asynchronous. it waits for the response, in that meantime your state renders with empty array.
you can display spinner untill the response received.
like
const[isLoading,setLoading]= useState(true)
useEffect(()=>{
getReceivers().then(()=>{setLoading(false)}).catch(..)
} )
return {isLoading ? <spinner/> : <yourdata/>}
Please set receivers initial value as array
const [receivers, setReceivers] = useState<Profile[]>([])
Also foreach will not wait as you expect use for loop instead of foreach
I am not sure it is solution for your question
but it could help you to solve your error

React hooks state undefined on handle function

I am creating a simple quiz with persisting response state and i am currently stuck trying to figure out why responseState is undefined in my handleAnswerClick function, this function is triggered much later if at all per click. By then the States should all be set.
const Question: React.FC<IProps> = (props) => {
const [responseState, setResponseState] = useState([]);
const [question, setQuestion] = useState<IStateProps>(props.id);
const [answers, setAnswers] = useState([]);
const [choice, setChoice] = useState();
const initialResponseState = () => {
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: '1',
}
axios.post(`${host}/api/responseState/`, data)
.then(() => {
console.log('Created the initial Response State');
})
.catch((err) => {
console.log(err);
})
}
const getData = async () => {
await axios.all([
axios.get(`${host}/api/question/${question}`),
axios.get(`${host}/api/responseState/examTakerProgress/1/question/${props.id}`)
])
.then(axios.spread((questionData, responseData) => {
if (!responseData.data.length > 0) {
initialResponseState();
}
setResponseState(responseData.data);
setQuestion(questionData.data);
setAnswers(questionData.data.answers);
setChoice(responseData.data.length > 0 ? responseData.data[0].answer : '');
setTimeout(() => {
props.toggleLoading();
}, 50);
}))
.catch(err => console.error(err));
};
useEffect(() => {
getData()
}, [])
const handleAnswerClick = async (number: number) => {
setChoice(number);
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: number,
}
await axios.put(`${host}/api/responseState/${responseState[0].id}/`, data)
.then((data) => {
console.log(data);
console.log('choice changed to :' + number);
})
.catch((err) => {
console.log(err);
})
}
if (props.loading) {
return <Loader/>
}
return (
<React.Fragment>
<h3>{question.label}</h3>
<p className='mb-3'>{question.description}</p>
<ListGroup as="ul">
{answers.map((answer, index) => {
if (answer.id === choice) {
return <ListGroup.Item key={answer.id} action active> <Answer key={index}
answer={answer}/></ListGroup.Item>
} else {
return <ListGroup.Item key={answer.id} action onClick={() => {
handleAnswerClick(answer.id)
}}><Answer key={index}
answer={answer}/></ListGroup.Item>
}
}
)}
</ListGroup>
</React.Fragment>
)};
Can someone explain me the reason why this is happening?
Based on this line
const [responseState, setResponseState] = useState({});,
the responseState is an object.
but in the handleAnswerClick function you are using it as an array ${responseState[0].id}.
Note that this actually works if the object has a 0 key but since you're getting undefined that is not the case.
Reasons why responseState is undefined.
The most obvious is that you set responseState to undefined
The only place you call setResponseState is in getData where you have setResponseState(responseData.data);, so please check if responseData.data isn't undefined.
You never get to call setResponseState and the initial state is an object but you try to get responseState[0].id
I'm not sure what data type you are handling, but if you defined const [responseState, setResponseState] = useState({}); with the default value of useState as {} and then try to access responseState[0].id, there is two things happening.
Either you have an object with number keys or you have an array, but if you have an array, you should declare the initial value as an array.
But this isn't the problem, the problem is that you might never get to the part where you call setResponseState(responseData.data); and then you will have responseState to be {} and when trying to do responseState[0] it will be undefined, and when calling responseState[0].id you will get an error saying that you cannot read id of undefined.
Ways to fix this
Debug your code, make sure that the api call returns what you are expecting and setResponseState(responseData.data); is called.
Check if responseState[0] exists before try to access responseState[0].id
I would be able to help more, but you didn't add more information, so it's hard to help, but above is what I think it's the problem
Basically i am creating a responseState when a question loads for the first time, if a question is afterwards reloaded the State is already there since it is persisted. This means i also have to make sure to call setResponseState for the first case where the ResponseState still does not exist.
const initialResponseState = () => {
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: '1',
}
axios.post(`${host}/api/responseState/`, data)
.then(() => {
setResponseState(res.data)
console.log('Created the initial Response State');
})
.catch((err) => {
console.log(err);
})}

Categories