how to use useState inside inner function scope in react.js hooks - javascript

I am completely new to react.js.
I am fetching async data from my server that uses express.js. After I get that data I want to set my house parameters when I open that page for the first time.
const Houses = () => {
const [house, setHouse] = useState({});
...
useEffect(() => {
const fetchData = () => {
fetch('http://localhost:9000/houses')
.then(res => res.text())
.then(res => {
if (res) {
let houseData = JSON.parse(res);
console.log(houseData); // server res prints correctly - prints: { name: 'sweet house', address: 'fukuoka hakata' }
setHouse({...house, houseData});
console.log(house); // doesnt change after sethouse - prints : {}
}
});
}
fetchData();
}, []);
...
}
Im getting the data from my server without any problem.
The problem is that the house parameters dont get updated. I know its a different scope, what I need to know is how I do it in this case. Thanks

You cannot access the house state immediately after setting it, setHouse may be batched.
See more of State: https://reactjs.org/docs/state-and-lifecycle.html
You are trying to do
let houseData = JSON.parse(res);
// houseData = { name: 'sweet house', address: 'fukuoka hakata' }
setHouse({ ...house, houseData: houseData });
instead of setHouse(houseData). Since houseData is an Object, you can directly set it.
Replace
setHouse({...house, houseData})
with
setHouse(houseData)

Related

Capturing data out of a firebase collection, and actually making it usable?

I have a collection in firebase called "polls". The collection is an array, and within the array I have 4 urls (url_a, url_b, url_c, and url_d). I want to pull these urls out of the array in my react app so I can use them as the src for 4 different images.
I know since "polls" is an array, I can't just pull the urls directly by setting some variable to "polls.data.url_a". With the 2nd useEffect function in the code below, I'm able to console.log the array
and the data within it using forEach. I can console.log poll.data, and even poll.data.url_a and it returns the proper result.
But I am stuck on how to get outside of that forEach and actually capture the individual urls to where I can assign them to a variable so I can make use of them.
import { useEffect, useState } from "react"
import { collection, getDocs } from "firebase/firestore"
import { db } from '../firebase.config'
function Food() {
const [polls, setPolls] = useState([])
useEffect(() => {
getPolls()
}, [])
** useEffect(() => {
polls.forEach((poll) =>
console.log(poll.data.url_a))
}, [polls])
**
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const poll = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
setPolls(poll)
}).catch(error => console.log(error.message))
}
return (
<div>
Food
</div>
)
}
export default Food
I've tried setting a variable within the forEach and calling it later, and running a forEach later on in the function. But I just don't know how to get to the URL in a usable way, outside of just console logging it. Any help would be greatly appreciated, thanks!
Check out what Renaud suggested here to see how to assign variables: Get Firebase Collection into simple array
If I understand what you're asking, you'll need the poll const to instead point towards a global store variable or to reference it within the template section.
If the data your fetching is structured like this:
const polls = [{
data: {
url_a: "url a",
url_b: "url b"
}
}]
It's easier to just assign it to an object state variable instead of an array.
const [polls, setPolls] = useState({})
// ...
useEffect(() => {
getPolls()
}, [polls])
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const polls = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
polls.map(poll => setPolls(poll.data))
}).catch(error => console.log(error.message))
}
// ...
return <img src={polls.url_a} alt="" />

REACT HOOKS - Passing JSON data (From API through Axios) into const

In the backend I am processing the data and then sending it to React through JSON. Here is the code.
res.setHeader('Content-Type', 'application/json');
var obj = {field: current, fieldCnt: cnt }
var obj2 = JSON.stringify(obj)
res.write(obj2);
var obj3 = {length: currentcnt, field2: current, fieldCnt2: cnt }
var obj4 = JSON.stringify(obj3)
res.end(obj4);
The backend I showed here is not the same as it is. I just showed it as an example. I had to use res.write and res.end because I had to collect data for different if conditions.
Below here is the code in react where I am getting this data through Axios.
var data2 = axios.get('http://localhost:5000/get').then(res => {
console.log(res.data) //Check the Attached Chrome Developers console image.
return res.data
});
const data3 = data2.length //This isn't working (length can be seen the console image last line "length" : 18)
Chrome Developers console image
Please tell me how to solve this with React Hooks. I think it has to be done with mapping but i don't how.
As #Tobias Geiselmann said axios.get() returns a promise, and you're assigning the length of the promise.
If you return a value from .then(), the next then() is called with that value.
After looking at your screenshot. It looks like you're trying to access the length property from the data return by your API. I would recommend structuring your API response like so
{
count: 18,
results: [
{ key: 'val' },
{ key: 'val' },
{ key: 'val' },
]
}
By doing so you'll be to access the length with:
res.data.count // will have the count
res.data.results // will have all the results
Try this
axios.get('http://localhost:5000/get').then((res) => {
console.log(res.data.length); // will print the length
});
another example:
const data2 = axios.get('http://localhost:5000/get')
.then((data) => {
return data;
});
data2.then(data => {
console.log(data.length) // will print the length
})
example with async/await
const myFunc = async () => {
const { data } = await axios.get('http://localhost:5000/get');
console.log(data.length); // will print the length
};
To solve this with hooks you can fetch data when the component loads with useEffect hook and store it inside useState hook.
const [data, setData] = useState([])
useEffect(() => {
axios.get('http://localhost:5000/get').then(res => setData(res.data))
}, [])
const data3 = data.length

Why is my custom hook called so many times?

I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.

Store data from useQuery with useState

I'm using React hooks both to fetch GraphQL data with react-apollo and to store local state:
const [userData, setUserData] = useState({})
const { loading, error, data } = useQuery(USER_QUERY)
However, I'm wondering how to store data to userData. Is this how it's supposed to work:
useEffect(() => {
setUserData(data)
}, [Object.entries(data).length])
Looks like what you have probably works. There is also a onCompleted option available in the options parameter. it takes a callback of type:
(data: TData | {}) => void
so this is another way of doing it:
const { loading, error, data } = useQuery(USER_QUERY, {onCompleted: setUserData})
What are you trying to do with the returned data that you are unable to accomplish by simply using it as destructured from the query hook? In most use cases it can be used immediately, as it will update itself when refetched.
If it is necessary (and it could be), as the other answer says, the useEffect hook you posted should work, but I would replace the dependency with simply data, to prevent an edge case where the response has an equal length consisting of different data and does not update:
useEffect(() => {
setUserData(data)
}, [data])
I think something like this would work - you will need to create the initial state with useState, could be empty array and then onComplete in the useQuery would setTranscationsData... it is triggered every render when state or props change. Could of course add an inital state inside useState which insn't an empty array.
const [transactionsData, setTransactionsData] = React.useState([]);
const { error, data } = useQuery(GET_TRANSACTIONS, {
onCompleted: () => {
setTransactionsData(data.transactions);
},
});
another example
const [getLegalStatement] = useLazyQuery(GET_LEGAL_STATEMENT, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
setTempLegalStatement(data.getLegalStatement);
},
onError: () => {
setTempLegalStatement({
consentedLegalStatementHash: '',
consentedSuppliersHash: '',
statement: '',
suppliersModal: '',
});
setTimeout(() => {
setRefetchNeeded(true);
}, 10000);
},
});
Use onSuccess
const [userData, setUserData] = useState({})
const { data, isLoading, error } = useQuery('QueryKey', QueryFunction, { onSuccess: setUserData })
This onSuccess callback function will fire setUserData(data) for you automatically any time the query successfully fetches new data.
To elaborate above, you can't use onSuccess/onSettled because those will not rerun if the data is cached, so if you leave the component and come back before the query expires your data won't get set.

Relay Modern request onClick

How can i send a request to graphql using relay onclick ?
render(){
<div>
<img src={this.state.picture}>
<input type="email" value={this.state.email} onChange{...}/>
<button onClick={this.checkEmail}>Check</button>
</div>
}
checkEmail = async () => {
const res = await axios({
method: 'post',
url: __RELAY_API_ENDPOINT__,
data: {
query: `query CheckEmail($email: String!){lookupEmail(email: $email){id, picture}}`,
variables: {"email": this.state.email}
}
});
//set state using res
}
I cant figure out how to do this with relay.
In the examples relay is used to fetch and render onMount.
But how would i get data and change state on event listeners (onclick) ?
I couldnt find any example like that .
you can declare data dependency in relay but in some cases when you had a paginationcontainer which will fetch not 'all' e.g. first: 10 so we cannot get the length of it, in this case, you need to declare another data dependency by doing request. I hope you understand what I'm trying to say.
This is how i do it in my code, u need to explore the relay props more:
getPublicTodosLengthForPagination = async () => { // get publicTodos length since we cannot get it declared on createPaginationContainer
const getPublicTodosLengthQueryText = `
query TodoListHomeQuery {# filename+Query
viewer {
publicTodos {
edges {
cursor
node {
id
}
}
pageInfo { # for pagination
hasPreviousPage
startCursor
hasNextPage
endCursor
}
}
}
}`
const getPublicTodosLengthQuery = { text: getPublicTodosLengthQueryText }
const result = await this.props.relay.environment._network.fetch(getPublicTodosLengthQuery, {}) // 2nd arguments is for variables in ur fragment, in this case: e.g. getPublicTodosLengthQueryText but we dont need it
return await result.data.viewer.publicTodos.edges.length;
}
componentDidMount = async () => {
let result = await this.getPublicTodosLengthForPagination();
this.setState((prevState, props) => {
return {
getPublicTodosLength: result
}
});
}
implement this on ur code then update me.best of luck!

Categories

Resources