I am loading data on the initial load. When they user clicks the button to add a recognition, the api call adds it, and returns it. I add the new post to the array, but the new update doesn't render. The return object, and the array of objects are the same object type. When I reload the page, the new post is rendered, just not on the add function. Is there something that I am missing?
const [recognitions, setRecognitions] = useState([]);
useEffect(() => {
Api.GetRecognitions(params)
.then(response => {
const items = response || [];
setRecognitions(recognitions => [...recognitions, ...items]);
})
}, [setRecognitions]);
const handleAddPost = () => {
Api.AddRecognition(params)
.then(response => {
const newPost = response;
setRecognitions(recognitions=> [...recognitions, newPost])
});
}
<Form.Group>
<Form.Field>
<Button basic color='blue' onClick={handleAddPost}>Add</Button>
</Form.Field>
</Form.Group>
<Form.Group>
<Form.Field>
{recognitions.map(recognition => (
<RecogWallPost
key={recognition.recogStagingId}
recognition={recognition}
participantId={participantId}
/>
)
)}
</Form.Field>
</Form.Group>
Instead of passing [setRecognitions] as the second argument to useEffect, you want to pass [recognitions]. This tells the useEffect hook to run every time recognitions changes, which it does inside handleAddPost.
You have to create an async function, and then use it as follow:
useEffect(() => {
async function initData() {
Api.GetRecognitions(params)
.then(response => {
const items = response || [];
setRecognitions(recognitions => [...recognitions, ...items]);
})
}
initData()
}, [setRecognitions]);
Related
I have a search component that fetches a single profile from a JSON file (currently local, but will be remote in the future) and displays the information of the matching profile beneath the input field.
Currently, on my first submit of my search query, I've found that all of my state variables return undefined because, if I understand correctly, state does not update until the full chain of promises has resolved. And it's only on my second submit of my search query that my state variables return the correct data of the filtered search result.
On the first submit, it appears that an empty array is being initialized, as my conditional render of {props.activeChart && `OPENED CHART : ${props.firstName} ${props.lastName} (DOB: ${props.DOB})`} becomes truthy and renders out empty values for firstName, lastName, and DOB.
EDIT: I came across this recent post (React state gets updated only after I submit the form twice), which seems to address this same issue resulting from asynchronous fetch and setting state, except with axios. I've tried modifying my code accordingly (edited below), but I'm still not able to update state after my fetch result has resolved. Any advice would be appreciated. Thanks.
import { useState } from 'react';
import StyledSearchForm from './SearchForm.styled';
const SearchForm = props => {
const [queryFirstName, setQueryFirstName] = useState('');
const [queryLastName, setQueryLastName] = useState('');
const [queryDOB, setQueryDOB] = useState('');
const handleQuery = async (e) => {
e.preventDefault();
const result = await fetchRecord();
console.log(result[0]) // fetched object successfully logged
if (result[0]) {
setActiveChart(result[0]);
console.log(activeChart) // activeChart still undefined
setFirstName(activeChart.firstName);
setLastName(activeChart.lastName);
setDOB(activeChart.dob);
}
};
const fetchRecord = () => (
fetch('http://localhost:8000/patients')
.then(resp => { return resp.json(); })
.then(data => {
const result = data.filter(patient => (
(patient.dob === queryDOB.trim() &&
patient.lastName.toLowerCase() ===
queryLastName.toLowerCase().trim()) ||
(patient.lastName.toLowerCase() ===
queryLastName.toLowerCase().trim() &&
patient.firstName.toLowerCase() ===
queryFirstName.toLowerCase().trim())
));
return {...result};
})
);
return (
<StyledSearchForm>
<form onSubmit={handleQuery}>
<label className="first-name" htmlFor="first-name">
First Name:
</label>
<input
type="text"
id="first-name"
className="form-fields"
name="fname"
value={queryFirstName}
onChange={e => setQueryFirstName(e.target.value)}
/>
<label className="last-name" htmlFor="last-name">
Last Name:
</label>
<input
type="text"
id="last-name"
className="form-fields"
name="lname"
value={queryLastName}
onChange={e => setQueryLastName(e.target.value)}
/>
<label className="dob" htmlFor="dob">
DOB:
</label>
<input
type="text"
id="dob"
className="form-fields"
name="dob"
value={queryDOB}
onChange={e => setQueryDOB(e.target.value)}
/>
<button className="submit-btn" type="submit" onClick={e => handleQuery}>Open Chart</button>
</form>
<div className="active-patient">
{props.activeChart && `OPENED CHART : ${props.firstName} ${props.lastName} (DOB: ${props.DOB})`}
</div>
</StyledSearchForm>
);
};
export default SearchForm;
It looks like you're expecting your data filter to return an object, but Array.prototype.filter (docs) returns an array. Arrays, even if empty, are truthy.
You need to handle an array, not an object, in this chain:
const fetchRecord = () =>
fetch("http://localhost:8000/patients")
.then((resp) => {
return resp.json();
})
.then((data) => {
// results is an array!
const results = data.filter(...);
if (results.length === 0) {
// no match - do something about it?
return {};
} else {
// return the first result?
return results[0];
}
})
.then((record) => {
props.setActiveChart(...record);
})
.then(() => {
props.setFirstName(props.activeChart.firstName);
props.setLastName(props.activeChart.lastName);
props.setDOB(props.activeChart.dob);
});
It seems the issue resulted from trying to set all of my state variables in the same async function that was fetching my search result, and by moving the if(results[0]) statement out of the handleQuery function while leaving just the setActiveChart() inside the handleQuery function resolved my issue.
I'd like make an API call, which user input makes part of the API URL. Data is only fetched on demand after user submit.
My problem is: after first time input and submit, input is processed as an empty string, constructed wrong URL and made API call. (still loads data but the wrong data)
Only after second submit does it get actual user input, construct the correct URL and display the right data.
monitering network:
User input is stored in enteredWallet, Console.log(enteredWallet) prints the input, but setOwner(enteredWallet) doesn't change owner to be enteredWallet.
import { useState } from "react";
// example input: 0x147412d494731cbb91dbb5d7019464a536de04dc
function App() {
const [data, setData] = useState([]);
const [enteredWallet, setEnteredWallet] = useState("");
const [owner, setOwner] = useState("");
const walletChangeHandler = (event) => {
setEnteredWallet(event.target.value);
};
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
fetchNFTHandler();
console.log("enteredWallet:", enteredWallet);
console.log("owner:", owner);
};
function fetchNFTHandler() {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)
.then((res) => {
return res.json();
})
.then((data) => {
const transformedData = data.assets.map((element, index) => {
return {
title: element.name,
id: index,
};
});
setData(transformedData);
console.log("fetched");
});
}
return (
<div className="App">
<header className="App-header">
<h3>Show me assets in this wallet</h3>
<form onSubmit={submittedHandler}>
<input
placeholder="wallet address"
value={enteredWallet}
onChange={walletChangeHandler}
/>
<button>Submit</button>
</form>
<div>
{data.map((element) => (
<li key={element.id}>{element.title}</li>
))}
</div>
</header>
</div>
);
}
export default App;
Because owner in fetchNFTHandler doesn't update immediately after call setOwner.
Why don't use onwer as a param.
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
fetchNFTHandler(enteredWallet); //here
console.log("enteredWallet:", enteredWallet);
console.log("owner:", owner);
};
function fetchNFTHandler(owner) {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)...
or if you need use it as state indeed.
use useEffect to call fetchNFTHandler
useEffect(() => {
fetchNFTHandler();
}, [owner]) // when owner change, fetchNFTHandler will be call
If you want use a variable, it can take effect at once. you can try useRef.
const ownerRef = useRef("");
const submittedHandler = (event) => {
event.preventDefault();
ownerRef.current = enteredWallet;
fetchNFTHandler();
};
function fetchNFTHandler(owner) {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${ownerRef.current}&order_direction=desc&offset=0&limit=10`
)...
The function returned by useState (in your case, setEnteredWallet or setOwner) is not synchronous. The state is not immediately changed after calling either it. If you want to call fetchNFTHandler every time enteredWallet changes, you can use useEffect. Or simply, you can pass enteredWallet to fetchNFTHandler as a parameter. An example usage of useEffect:
useEffect(() => {
fetchNFTHandler();
console.log("enteredWallet:", enteredWallet);
console.log("owner:", owner);
}, [owner, enteredWallet]) // Call method above when owner or enteredWallet change
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
// You don't need the following lines anymore
// fetchNFTHandler();
// console.log("enteredWallet:", enteredWallet);
// console.log("owner:", owner);
};
Hi I do have to following simplyfied code. I use Formik as a Validation. Also Material Ui and Reactjs. The Form, Row and Col Tags come from Material. The FastField is same as InputField.
What I want is onClick in the Inputfield a dropdown appears and shows an array which I fetched with the axios-Request.
ยดยดยด
const url = 'http://localhost:3000';
const [searchValues, setSearchValues] = useState([]);
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => setSearchValues(res),
console.log('restl', searchValues)
);
};
render(
<Form
onFocus={getDropdownItems}
onSubmit={formik.handleSubmit}
>
<Row>
<Col xs="auto" style={minWidth}>
<FastField
id="DatumEingabe"
name="DatumEingabe"
component={Autocomplete}
label="Datum-Eingabe"
type="text"
options={searchValues}
/>
</Col>
</Row>
</Form>
)
When I check my console I get from the first console.log the name of
the Inputfield. The second console.log says the array is empty,
despite the res is available and should be set. Why does it not work
this way.
setSearchValues(res) will not update searchValues until the next render. If you want to log it each time it changes, you should instead do
const [searchValues, setSearchValues] = useState([]);
useEffect(() => {
console.log(searchValues);
}, [searchValues]);
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => setSearchValues(res)
);
};
I don't think the change is made inmediatly. Try logging searchValues after a second or something like that to see if that is the problem.
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => {
setSearchValues(res);
setTimeout(() => {
console.log('restl', searchValues);
}, 1000)
}
);
};
Also, you have the useEffect hook, which fires an event when a variable is change, so if you want to log it the second it changes you should use:
useEffect(() => {
console.log(searchValues);
}, [searchValues])
To acomplish that, remember to import:
import { useEffect } from "react";
or use
React.useEffect(...)
I have an input tag: <input type="text" onChange={(e) => setMessage(e.target.value)} /> (message and setMessage are state variables).
I also have a Firebase query: firebase.firestore().collection('messages').where('users', 'array-contains', uid)
I set up a query.onSnapshot listener to listen for collection updates, and put a console.log inside of it.
It triggers twice every time the text changes in the text box, and I included the entire tag because it doesn't trigger when another input tag, with an onChange attribute (but doesn't change a state variable) is changed, so it seems that the problem is somewhere with the state variable.
Does anyone know what might be triggering the onSnapshot event?
function Chatroom(props) {
const [ messages, setMessages ] = useState([])
const [ chatWithUser, setChatWithUser ] = useState("")
const [ chatWithUserTemp, setChatWithUserTemp ] = useState("")
const [ message, setMessage ] = useState("")
const { uid, photoURL } = auth.currentUser
const mref = firestore.collection('messages')
const query = mref.where('users', 'array-contains', uid).orderBy('time')
const getContent = async() => {
let content = []
await query.get().then((docs) => {
docs.forEach(doc => {
if(doc.data().users.includes(chatWithUser)) {
content.push(<li key={doc.id}>From: {doc.data().sender}, message: {doc.data().message}</li>)
}
})
})
setMessages(content)
}
const updateMessages = (data) => {
}
query.onSnapshot((snapshot) => {
getContent()
})
useEffect(() => {
getContent()
}, [])
const sendMessage = () => {
mref.add({
message: message,
sender: auth.currentUser.uid,
time: firebase.firestore.FieldValue.serverTimestamp(),
users: [auth.currentUser.uid, chatWithUser]
})
setMessage("")
}
return (
<div>
<div className="sidenav">
<h3>Chat with Users</h3>
<input type="text" className="form-control" placeholder="Enter UID" onChange={(e) => setChatWithUserTemp(e.target.value)}></input>
<Button onClick={() => setChatWithUser(chatWithUserTemp)}>Chat</Button>
<p>Your UID: {auth.currentUser.uid}</p>
<Logout />
</div>
<div className="main">
<p>Chatting with {chatWithUser}</p>
<ul>
{messages}
</ul>
<input type="text" value={message} className="form-control" placeholder="Message..." onChange={(e) => setMessage(e.target.value)} />
<Button onClick={sendMessage}>Send</Button>
</div>
</div>
)
}
This method call is in the body of the component:
query.onSnapshot((snapshot) => {
getContent()
})
The component body gets called every time the component rerenders, so this is creating a new subscription to the query every time the component renders.
Since subscribing to a query is a side effect, it should be called inside a useEffect callback:
function Chatroom(props) {
// ...
// mref is used both inside and outside the effect. useMemo ensures it's only
// called once so we can add it to the effect's dependency array
const mref = useMemo(() => firestore.collection("messages"), []);
useEffect(() => {
// since query and getContent are only used by this effect, we should
// define them inside the effect so we don't have to worry about
// adding them to the dependency array
const query = mref.where("users", "array-contains", uid).orderBy("time");
const getContent = async () => {
let content = [];
await query.get().then((docs) => {
docs.forEach((doc) => {
if (doc.data().users.includes(chatWithUser)) {
content.push(
<li key={doc.id}>
From: {doc.data().sender}, message: {doc.data().message}
</li>
);
}
});
});
setMessages(content);
};
const unsubscribe = query.onSnapshot((snapshot) => {
getContent();
});
// Firebase will call the onSnapshot callback once automatically, so there
// is no need to call getContent outside of onSnapshot
// When the component is unmounted, we need to unsubscribe from the
// query so we don't keep getting updates
return () => unsubscribe();
}, [mref]);
const sendMessage = () => {
mref.add({
message: message,
sender: auth.currentUser.uid,
time: firebase.firestore.FieldValue.serverTimestamp(),
users: [auth.currentUser.uid, chatWithUser],
});
setMessage("");
};
...
}
I think that your query.onSnapshot function is being triggered in every state update. The general approach with listeners is to put them in lifecycle hooks and then clean them
something like this:
useEffect(() => {
const unsubscribe = query.onSnapshot((snapshot) => {
getContent()
})
return () => unsubscribe()
}, [])
The return of an useEffect will unsubscribe the listener
you only will call getContent in the onSnapshot , also the snapshot will have your latest messages, so not need to query them again in getContent
In my app I have profile section with a form. When the component mounts I want to fetch user data from firebase, and display it in the form, with the current values of the user profile. Either using the "value" prop or the "placeholder" prop.
When the user makes changes in the form inputs and submit the changes, I want the database to update and the form to update with the new data.
Currently I can make the database value appear in the form input field, or I can make the form input field empty, but update the database. But not both.
The following code makes the database data render in the form input, but it cant be changed.
I know it could be something with the second useEffect() and the getUserData() function, that I cant seem to figure out.
const UserEdit = (props) => {
const [currentUser, setCurrentUser] = useState('');
const [forening, setForening] = useState('');
useEffect(() => {
firebase_app.auth().onAuthStateChanged(setCurrentUser);
}, [])
const getUserData = async () => {
await dbRef.ref('/' + currentUser.uid + '/profil/' ).once('value', snapshot => {
const value = snapshot.val();
setForening(value)
})
}
useEffect(() => {
getUserData()
},[] )
const handleInput = (event) => {
setForening(event.target.value)
}
const updateUserData = () => {
dbRef.ref('/' + currentUser.uid + '/profil/' ).set({foreningsnavn: forening}, function(error) {
if(error) {
console.log("update failed")
} else {
alert(forening)
}
})
}
const handleClick = () => {
updateUserData()
}
return (
<>
<div className="card-body">
<div className="row">
<div className="col-md-5">
<div className="form-group">
<label className="form-label">{Forening}</label>
<input className="form-control" type="text" value={forening} onChange={handleInput}/>
</div>
</div>
</div>
</div>
</>
)
}
Your second useEffect will run only one time because the second argument array [] of dependencies is empty:
useEffect(() => {
getUserData()
},[] )
You can add foreign dependency to make useEffect run with input change
useEffect(() => {
getUserData()
},[foreign] )
or you can use polling to sync database state