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
Related
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
I have an API call set up with two search buttons from one input box. Each button adds something using state to the api call which the code below should demonstrate. The calls both work fine independently and display the correct information.
If a user has clicked the 'wild cards' button the results show but then on clicking the 'standard set' button the results don't re-render the correct results until the button is pressed a second time (vice versa for both buttons).
I have removed the un-related code as to condense the question
Any help appreciated
Home.js - with api call, state and functions passed down as props to the searchBar
export const Home = () => {
const [searchTerm, setSearchTerm] = useState('')
const [wild, setWild] = useState('')
let accessToken;
const getCards = async() => {
try {
await getToken()
const response = await fetch(`https://eu.api.blizzard.com/hearthstone/cards/?collectible=1${wild}&textFilter=${searchTerm}&locale=en-US$access_token=${accessToken}`, {
headers: {
Authorization: `Bearer ${accessToken}`
}})
const data = await response.json()
const {cards} = data
if (cards){
const newCards = cards.map((card) => { ** some data i want ** }
setCards(newCards)
}
} catch (error) {
console.log(error)
}
}
return (
<SearchBar getCards = {getCards}
setWild = {setWild}
setSearchTerm = {setSearchTerm} />
</div>
</div>
)
}
SearchBar Component - again condensed for the sake of this question
export const SearchBar = (props) => {
const searchBox = useRef('')
const handleClickWild = (e) => {
e.preventDefault()
props.setWild('')
props.getCards()
}
const handleClickStandard = (e) => {
e.preventDefault()
props.setWild('&set=standard')
props.getCards()
}
const handleChange = (e) => {
props.setSearchTerm(e.target.value)
}
return (
<form>
<input className = 'input-search'
type = 'text'
placeholder = 'Search for Cards by Name...'
ref = {searchBox}
onChange = {handleChange} />
<div className = 'search-buttons'>
<input type = 'submit' className = 'button-search' onClick = {handleClickWild} value = 'Wild Cards' />
<input type = 'submit' className = 'button-search' onClick = {handleClickStandard} value = 'Standard Set' />
</div>
</form>
)
}
You have to use useEffect hook here.
You can use wild in dependency array and whenever you change the value of searchTerm use effect will automatically call your getCards function.
As you mentioned in the comment you want to show changes when user search anything then keep the wild in simple variable and add search term in useEffect and if you want you can add both in the useEffect dependency array
useEffect(()=> {
getCards()
}, [searchTerm])
Just remove explicite calls of props.getCards after setting wild from SearchBar component.
I solved this with useEffect as suggested but I added an 'if (hasSearched)' as state value of is the user had searched previously to prevent the API auto calling on page load
I use the following component to add new data in a Google Cloud FireStore collection:
import React, { useState } from 'react';
import { db } from '../firebase';
const AddCard = ({ totalDoclNumbers }) => {
const [newOriginalText, setNewOriginalText] = useState([]);
const [newTranslatedText, setNewTranslatedText] = useState([]);
const nextNumber = totalDoclNumbers + 1;
const onAdd = async () => {
await db.collection('FlashCards').add({
originalText: newOriginalText,
translatedText: newTranslatedText,
customId: Number(nextNumber),
});
};
return (
<ul>
<li key={nextNumber}>
<input
type='text'
readOnly
defaultValue={nextNumber}
/>
<div>
<textarea
placeholder='English'
onChange={(e) => setNewOriginalText(e.target.value)}
/>
</div>
<textarea
placeholder='Translation'
onChange={(e) => setNewTranslatedText(e.target.value)}
/>
<button onClick={onAdd}>
Add
</button>
</li>
</ul>
);
};
export default AddCard;
The add button works and I can see that new data are added to the collection on Google server, however to be able to see those new data, I have to refresh the page.
In order to fix it, I decided to make use of onSnapshot function as follow:
import React, { useState } from 'react';
import { db } from '../firebase';
const AddCard = ({ totalDoclNumbers }) => {
const [newOriginalText, setNewOriginalText] = useState([]);
const [newTranslatedText, setNewTranslatedText] = useState([]);
const nextNumber = totalDoclNumbers + 1;
const onAdd = async () => {
await db.collection('FlashCards').add({
originalText: newOriginalText,
translatedText: newTranslatedText,
customId: Number(nextNumber),
});
};
const renderDocList = () => {
return (
<ul>
<li key={nextNumber}>
<input
type='text'
defaultValue={nextNumber}
/>
<div>
<textarea
placeholder='English'
onChange={(e) => setNewOriginalText(e.target.value)}
/>
</div>
<textarea
placeholder='Translation'
onChange={(e) => setNewTranslatedText(e.target.value)}
/>
<button onClick={onAdd}>
Add
</button>
</li>
</ul>
);
};
db.collection('FlashCards').onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === 'added') {
renderDocList();
}
});
});
return <>{renderDocList()}</>;
};
export default AddCard;
But although the new data get added to FireStore collection but I cannot see any changes on the page. Still I have to refresh (reload) browser to see those changes.
What am I doing wrong?
I would have approached it in a different way - why upload and then load again?
When onAdd is called save your data to set state, the change in state will re-render your component with the new data.
If you insist on going the save\load path then try using the use effect hook combined with set state as mentioned here:
https://dev.to/chantastic/connect-useeffect-and-usestate-to-update-components-with-data-4aaj
Lots of the power of React.js is in state, use it :)
Nobody is going to see the result of the renderDocList call in this snippet:
db.collection('FlashCards').onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === 'added') {
renderDocList();
}
});
});
If you want the UI to update, you need to tell React that there is new data for it to render. And that's what useState hooks are used for. It don't fully see how your code affects what is being rendered, but you'll need some call to a setter that modifies the state that your component renders.
So say that your original text comes from the database, you'd do:
db.collection('FlashCards').onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === 'added') {
setNewOriginalText("new document added");
}
});
});
And that in turn will then rerender any components that display the original text.
Also see:
Can't fetch firebase collection with React, where I use setState instead of the useState hook.
NodeJs/Firestore/React- Storing Firestore query results into the state