When you load the page ComponentWillMount triggers getLocalStorage function. This has several checks and triggers the search function. When you load the page it is trying to retrieve the query from localStorage. When you change the input(which changes the query) and submit, the search function should trigger but does not fetch.. instead it refreshes the page and load componentDidMount again? Then after the refresh it works perfectly. Why is it only refreshing one time?
componentWillMount(){
this.getLocalStorage();
};
getLocalStorage = () => {
//Check if localstorage is supported by browser
if (typeof(Storage) !== "undefined") {
//Check if localstorage item query is defined
if (localStorage.getItem("query") !== null) {
//Set query to localstorage item and go to search function
//This works and triggers the search function
this.setState({
query: localStorage.getItem("query")
},() => {
this.search();
});
}
// If localstorage item is not defined go to location
else{
this.getLocation();
}
}
// If localstorage is not supported by the browser go to location
else {
this.getLocation();
}
};
When you click on the button it triggers the search function but does not fetch. Instead it trigger componentDidMount again?
<input type="text" onChange={this.handleChange} placeholder="Find your location..."/>
<button onClick={this.search}>Submit</button>
Search function
search = () => {
this.setState({
secondLoader:true
});
let BASE_URL = "https://maps.googleapis.com/maps/api/geocode/json?";
let ACCES_TOKEN = "token";
let FETCH_URL = `${BASE_URL}address=${this.state.query}&key=${ACCES_TOKEN}`;
alert('the search function does not fetch like below instead it trigger componentDidMount again');
fetch(FETCH_URL, {
method: "GET"
})
.then(response => response.json())
.then(json => {
//If repsonse got zero results use amsterdam location
if(json.status === 'ZERO_RESULTS'){
this.setState({
query: 'Amsterdam'
});
}
//Otherwise use query
else {
const geocode = json.results[0].geometry.location;
this.setState({
latitude: geocode.lat,
longitude: geocode.lng
});
}
const BASE_URL = "https://api.darksky.net/forecast/";
const ACCES_TOKEN = "token";
const FETCH_URL = `${BASE_URL}${ACCES_TOKEN}/${this.state.latitude},${this.state.longitude}?lang=nl&units=si`;
fetch(FETCH_URL, {
method: "GET",
})
.then(response => response.json())
.then(json => {
const data = json;
this.setState({
weather: data,
loader: false,
secondLoader: false
});
})
})
};
Try adding a type attribute to your button. I believe the reason why it's refreshing is that it has a default type of submit. Try using type="button" More info here.
Related
I have this piece of code that is connecting to metamask wallet and sets the account address using useState().
const [currentAccount, setCurrentAccount] = useState("")
const connectWallet = async () => {
try {
if (!ethereum) return alert("Please install MetaMask.")
const accounts = await ethereum.request({ method: "eth_requestAccounts" })
setCurrentAccount(accounts[0])
console.log(accounts)
// TODO:Add conditional statement to check if user has token
navigate("/portfolio")
} catch (error) {
console.log(error)
throw new Error("No ethereum object")
}
}
console.log("current account", currentAccount)
const returnCollection = async (currentAccount) => {
const options = { method: 'GET', headers: { Accept: 'application/json' } };
fetch(`https://api.opensea.io/api/v1/collections?asset_owner=${currentAccount}&offset=0&limit=300`, options)
.then(response => response.json())
.then(response => console.log("collection owned by current address", response))
.catch(err => console.error(err));
useEffect(() => {
returnCollection(currentAccount)
})
The console is logging the account but when I try to pass it in the returnCollection call in useEffect() it comes back as undefined.
It seems here, you are doing something like this:
useEffect(() => {
returnCollection(currentAccount)
})
This will run after every rerender or every state change, and also initially when the component is first mounted, when currentAccount is "". Initially, current account is "", so you may not want to get the collection from "". Instead, maybe what you can do is create another state variable for the result of the returnConnection, and set the state variable to the result of the returnConnection, since you typically don't return results from useEffect, unless it's a cleanup function. Also, maybe you can check the state of currentAccount inside of the useEffect to make sure it's not "", before returning the result.
I am putting together a check box that will push data to async storage in react native but am having trouble getting the data to stick. I've set up the function presumably to push data to storage when the box is checked and then when it is unchecked. Below is the code, it seems unchecking the box won't stick. When I open the app again the box is still checked, what might I be doing wrong here? Assume that as of right now the local item stored is true and I want to make it false when I uncheck. To confirm nothing is changing I have added a button to call data.
const STORAGE_KEY = '#save_enableauto'
state={
enableAuto:false
}
_storeData = async enableAuto => {
let x = enableAuto;
console.log('setting auto messages to: ', !x);
//send check box to state
this.setState({ enableAuto: !x });
try {
if(x) {
await AsyncStorage.setItem(STORAGE_KEY, 'true')
alert('Data successfully saved!')
} else {
await AsyncStorage.setItem(STORAGE_KEY, 'false')
}
} catch (e) {
console.log(e)
alert('Failed to save name.')
}
}
_retrieveData = async () => {
try {
const enableAuto = await AsyncStorage.getItem(STORAGE_KEY);
if (enableAuto !== null) {
// We have data!!
console.log('receiving from local storage: ',enableAuto);
this.setState({ enableAuto:eval(enableAuto) });
}
} catch (error) {
alert('failed to load previous settings.')
// Error retrieving data
}
};
componentDidMount() {
this._retrieveData()
}
....
<Button title={'call Data'} onPress={() => this._retrieveData()}/>
<CheckBox
value={this.state.enableAuto}
onValueChange={() => this._storeData(this.state.enableAuto)}
/>
update _storeData call to:
<CheckBox value={this.state.enableAuto}
onValueChange={() => this._storeData(!this.state.enableAuto)}
/>
and your function body to :
_storeData = async enableAuto => {
let x = enableAuto;
console.log('setting auto messages to: ', x);
//send check box to state
this.setState({ enableAuto: x });
...
}
I'm trying to build a very simple app for searching articles and using the localstorage to display more info about each article but when I set to save into the session storage for some reason is saving the initial or previous state, I assume because I need to set async for this but I just can't figure how
This is how I have it right now, findArticleQuery() is called on the handleSubmit
useEffect(
() =>{
findArticlesQuery();
} ,[]
)
const findArticlesQuery = (query) => { //search function
axios.get(`url`)
.then(res => {
[res.data].map((val) =>(
setState(val)
))
}).catch(error => {
console.log(error.response)
});
}
const handleSubmit = (e) => {
e.preventDefault();
findArticlesQuery(e.target.search.value)
sessionStorage.setItem('localData', JSON.stringify(state)) //<--here I get the value of the previous state
e.target.search.value = '';
}
I need to use the session storage because I will have a detailed article component page.
Thank you guys!
You can get search result from findArticlesQuery() like below.
...
const findArticlesQuery = (query) => { //search function
return axios.get(`url`)
.then(res => {
setState(res.data)
return res.data
})
}
const handleSubmit = (e) => {
e.preventDefault();
findArticlesQuery(e.target.search.value)
.then(val => sessionStorage.setItem('localData', JSON.stringify(val)))
e.target.search.value = '';
}
For saving state in the localStorage with React Hooks, something I've been using and that is extremely convenient is useLocalStorage.
Disclaimer : I am not the creator of this lib.
I'm trying to display the value of a collection property stored using mongoose in the client side of my react app when a user clicks a button.
By retrieving the value on click and storing the value using setState.
My Problem --
I can't seem to setState and display the new value immediately onClick...
On the next click, the value shown is the value for the previous click.
my backend node.js code for fetching data from mongodb
app.post('/api/showengagement', async (req, res) => {
const result = await Post.findOne({url: req.body.url}, "likes");
res.send(result);
})
My frontend react.js code for handling onClick and displaying the gotten data
handleLike(url, e){
if (e.target.innerText.toLowerCase() === "like"){
axios.post('/api/showengagement', {url: url})
.then(res => this.setState({
likeEngage: res.data.likes
}));
e.target.innerText = `This post has ${this.state.likeEngage} like(s)`
axios.post('/api/incrementlikes', {url: url});
}
else{
e.target.innerText = "Like";
axios.post('/api/decrementlikes', {url: url});
}
}
Thank you for your help!
You are seeing the wrong value because you are setting the e.target.innerText before the axios.post is done. You can move that code into the callback to get the desired order of operations like this:
handleLike = (url, e) => {
if (e.target.innerText.toLowerCase() === "like"){
axios.post('/api/showengagement', {url: url})
.then(res => {
e.target.innerText = `This post has ${res.data.likes} like(s)`
});
axios.post('/api/incrementlikes', {url: url});
}
else{
e.target.innerText = "Like";
axios.post('/api/decrementlikes', {url: url});
}
}
By doing it this way you don't even need to store the value in state. You can still do that if it's needed elsewhere in the code though.
The reason your setting of innerText happened before the value came back is that the axios.post is asynchronous, and returns before the operation is done and your function continues to the next line (setting the text to the old value). The new value comes in later when the post completes and your callback function is called.
Even Better: Avoid Using innerText - Use render()
Another way to do this is to have your render() function be responsible for writing the value in the appropriate place using the state variable. When you call this.setState() it causes the component to render again and you would see the value appear. Here is a working React class that shows it:
import React from "react";
let likes = [];
let urls = ["url1", "url2"];
class Sample extends React.Component {
constructor(props) {
super(props);
this.state = { likeEngages: [] }; // initialize the array of likeEngages
}
// fake implementation of 'post' instead of axios.post
post = (url, body) => {
if (!likes[body.id]) likes[body.id] = 1;
if (url === "/api/showengagement") {
return Promise.resolve({ data: { likes: likes[body.id] } });
} else {
likes[body.id]++; // increment Likes for given id
return Promise.resolve();
}
};
handleLike = (url, e) => {
const id = e.target.id;
this.post("/api/showengagement", { url, id }).then(res => {
const engages = this.state.likeEngages;
engages[id] = res.data.likes; // set the likes count in the state
this.setState({ likeEngages: engages });
});
this.post("/api/incrementlikes", { url, id });
};
showLikes = id => {
if (this.state.likeEngages[id])
return (
<span>
{" This post has " + this.state.likeEngages[id] + " like(s)"}
</span>
);
return " No likes yet";
};
render() {
return (
<div>
<ul>
<li>
<button id="id1" onClick={e => this.handleLike(urls[0], e)}>
Like1
</button>
{this.showLikes("id1")}
</li>
<li>
<button id="id2" onClick={e => this.handleLike(urls[1], e)}>
Like2
</button>
{this.showLikes("id2")}
</li>
</ul>
</div>
);
}
}
export default Sample;
Update your setState to the following;
this.setState({...this.state, likeEngage: res.data.likes})
If this doesn't solve the problem, you will need to call the incrementLikes after setState like below
this.setState({...this.state, likeEngage: res.data.likes}, () => incrementlikes(url))
I'm new to react and tying in the back end but after I make the fetch requests, I have to reload the page to see any changes. The database is updated as soon as the functions are called but the component doesn't re-render. I know setState works asynchronously, so I tried calling my functions in the callback of setState but that did not work.
This happens on both my handleSubmit and handleDelete functions. My initial get request is in my componentDidMount so I'm including that in case it helps.
I couldn't find the answer that I needed on the site, maybe the recommendations were just off but here I am, lol. Thanks in advance.
componentDidMount() {
// todos is the data we get back
// setting the state to newly aquired data
fetch("/api/todos")`enter code here`
.then(res => res.json())
.then(todos => this.setState({ todos }, () =>
console.log("Todos fetched...", todos)))
.catch(err => console.log(err))
}
// onClick for submit button
handleSubmit = (e) => {
e.preventDefault();
const data = this.state;
fetch("/api/todos", {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
};
// onClick for delete button
handleDelete = (e) => {
e.preventDefault();
let uniqueId = e.target.getAttribute("id")
fetch(`/api/todos/${uniqueId}`, {
method: "delete",
headers: { 'Content-Type': 'application/json' }
})
};
// Some of the JSX if needed
<DeleteBtn
id={todo._id}
onClick={this.handleDelete}
>X</DeleteBtn>
<Form onSubmit={this.handleSubmit} id="myForm"></Form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The result I'm looking for is once I add a todo, for it to render on my list immediately, rather than only upon page reload.
Return the details from the back-end in the requests, use that values to update the state,
Currently you just perform the operation on the back-end and front-end doesn't know that it happened in the back-end.
The Best way is to either pass the full data(list or object) back to front-end after operation performed on the DB and link the values to a state,
if the data is bulk then send a success message(200 is enough) back from back-end to front-end and if success change the value(list) in front-end,
Link the value(list) to a state in front-end to have a re rendering of the component.
you've to update your state, and once you'll update the state your component will re-render and it'll shows the latest changes.
Here i am assuming "todos" you've set in your state is an array, then just update it on deleting and adding.
i.e:
// onClick for submit button
handleSubmit = (e) => {
e.preventDefault();
const data = this.state;
const currentTodos = [...this.state.todos]
fetch("/api/todos", {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(()=>{
currentTodos.push(data);
this.setState({todos:currentTodos})
})
};
// similarly for delete you can do
// onClick for delete button
handleDelete = (e) => {
e.preventDefault();
let uniqueId = e.target.getAttribute("id")
let currentTodos = [...this.state.todos];
fetch(`/api/todos/${uniqueId}`, {
method: "delete",
headers: { 'Content-Type': 'application/json' }
}).then(()=>{
let updatedTodos = currentTodos.filter(todo=>todo._id !==uniqueId);
this.setState({todos:updatedTodos})
})
};
You are probably not changing your state "todos" that is why it doesn't render. You could fetch todos after every change (after remove, update, add...) or change the state yourself.
Methode 1:
componentDidMount() {
this.getTodos();
}
getTodos = () => {
//fetch todos, setState
}
handleSubmit = () => {
fetch(...).then(this.getTodos);
}
handleDelete = () => {
fetch(...).then(this.getTodos);
}
Methode 2:
componentDidMount() {
this.getTodos();
}
getTodos = () => {
//fetch todos, setState
}
handleSubmit = () => {
fetch(...);
let todos = this.state.todos;
todos.push(newTodo);
this.setState({todos});
}
handleDelete = () => {
fetch(...);
let todos = this.state.todos;
//remove todo from todos
this.setState({todos});
}