I stuck using React Hooks state. I am practicing with a weather app. I am requesting to Open Weather Map and a component called algolia-places-react. It connects to Alglia API to request name places.
I'm trying to make the request when the city name change. It looks it is working OK, But something is changed the value state to the initial state, before setting the new state. It happens on handleChange function. For example I added a new variable state and its function for checking that: count and setCount. When it enters to handleChange the console always prints: 1, except the first time. It isn't incresing. The same case with {name: 'boston', countryCode:'us'} always it is printing {name: 'boston', ountryCode:'us'}, except the first time.
import React, { useState, useEffect } from "react";
import { Segment, Form } from "semantic-ui-react";
import AlgoliaPlaces from "algolia-places-react";
export default function TempetureForm() {
const [count, setCount] = useState(0);
const [city, setCity] = useState({name: 'boston', countryCode:'us'});
const [data, setData] = useState(null);
const handleChange = ({ suggestion }) => {
console.dir(suggestion)
setCity({name: suggestion.name, countryCode: suggestion.countryCode})
setCount(count + 1)
console.dir(count) // Always prints 1
console.dir(city) // Always prints {name: 'boston', countryCode:'us'}
}
useEffect(() => {
console.dir(city)
fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city.name},${city.countryCode}&appid=XXXXXXXXXXXXXXXXXXX`)
.then(res => res.json())
.then(data => setData(data));
},[city]);
return (
<Segment basic>
<Form>
<Form.Field width={6}>
<AlgoliaPlaces
placeholder='How weather is in ...'
options={{
appId: 'XXXXXXXXXXXXX',
apiKey: 'XXXXXXXXXXX',
language: 'en',
type: 'city',
}}
onChange={handleChange}
/>
</Form.Field>
{city.name + " " + city.countryCode}
<p>{data && data.weather[0].description}</p>
</Form>
</Segment>
);
}
first, checking whether the value updated just after calling setCount is not the way to go since the value is not updated yet. someone suggested it's because setting the value is asynchronous. i didn't know that but it makes sense.
the console.dir(city) in your useEffect should show the updated value after handleChange was called as it's being called when city is updated.
Related
I'm trying to figure out why my useEffect hook keeps getting called multiple times, even when the dependency has the same value. I'm using the following code:
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import Cards from '../../../cards/Cards'
import UserCard from '../../../cards/users/Card'
import LoadingContainer from '../../../LoadingContainer'
import UsersResource from '../../../../api/resources/Users'
const Users = ({ users }) => (
<Cards>
{users.map((user) => (
<UserCard user={user} key={`user-${user.id}`} />
))}
</Cards>
)
const UsersPage = () => {
const [initialLoad, setInitialLoad] = useState(true)
const [loading, setLoading] = useState(true)
const [initialUsers, setInitialUsers] = useState([])
const [users, setUsers] = useState([])
const fetchUsers = async () => {
setLoading(true)
const response = await UsersResource.getIndex()
setInitialUsers(response.data)
}
useEffect(() => {
fetchUsers()
}, [])
useEffect(() => {
console.log('users changed:', users)
initialLoad ? setInitialLoad(false) : setLoading(false)
}, [users])
useEffect(() => {
setUsers(initialUsers)
}, [initialUsers])
return (
<LoadingContainer
loading={loading}
hasContent={!!users.length}
>
<Users users={users} />
</LoadingContainer>
)
}
Users.propTypes = {
users: PropTypes.arrayOf(PropTypes.shape).isRequired,
}
export default UsersPage
This is the effect that gets re-run when the value of the users dependency stays the same:
useEffect(() => {
console.log('users changed:', users)
initialLoad ? setInitialLoad(false) : setLoading(false)
}, [users])
Here's the output:
users changed: []
users changed: []
users changed: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
So users is obviously being recognized as changed twice, even though both times the effect is called, it returns the same value. This results in my loading state being set to false before the request finishes.
It only runs once if I change the initial state assignment of users from this...
const [users, setUsers] = useState([])
To this...
const [users, setUsers] = useState(initialUsers)
This tells me that the component must be rerendering simply because users is pointing to initialUsers in the second effect, instead of just a blank array (even though initialUsers returns a blank array as well). Can anyone explain why this happens this way? I can't seem to find any documentation describing this behavior (maybe I'm blind).
I would expect the value to be the only thing to influence an effect, but it seems like it might get triggered because the dependency is pointing to a new reference in memory. Am I off?
This appears to be a bit of a misunderstanding between value equality and reference equality. React uses reference equality.
The initial initialUsers and users state values are [], and on the initial render cycle there is a useEffect hook that enqueues an update to users with the current initialUsers value.
Note that initialUsers isn't not the same reference as users, so initialUsers === users evaluates false.
const initialUsers = [];
const users = [];
console.log(initialUsers === users); // false
Note also that [] === [] is also never true since they are two object references.
console.log([] === []); // false
This is roughly how the logic flows:
On the initial render cycle the initial users state [] is logged in the second useEffect hook.
The useEffect with dependency on initialUsers runs and updates the users state to the value of the initialUsers state. [] (but a different reference).
The second useEffect hook logs the users state update, again [].
The fetchUsers handler has fetched data and enqueues an update to the initialUsers state.
The second useEffect hook logs the users state update, now a populated array.
Code:
const fetchUsers = async () => {
setLoading(true);
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
// (4) update initialUsers
setInitialUsers(response.data);
};
useEffect(() => {
fetchUsers();
}, []);
useEffect(() => {
// (1) initial render, first log "[]"
// (3) second render, second log "[]"
// (5) third render, third log "[.........]"
console.log("users changed:", users);
initialLoad ? setInitialLoad(false) : setLoading(false);
}, [users]);
useEffect(() => {
// (2) initial render update users
setUsers(initialUsers);
}, [initialUsers]);
The difference when you initialize the users state to the initialState value is now they are the same reference.
const initialUsers = [];
const users = initialUsers;
console.log(initialUsers === users); // true
This subtle difference skips the enqueued update #2 above since users and initialUsers are already the same reference.
How to set the value to the input field in react. Data is fetching from firebase.
I was trying to fetch data from the firebase and then data is populated to the input field. But data is sometimes set to input field sometimes not.
You have to fill your data into a local state using useState. Here is the general idea. Can't go into more details without code example from your side.
For example:
const [value, setValue] = useState('')
Then in your useEffect fetching the data:
useEffect(() => {
const data = fetch('gettingdata') // replace by the way you get your data
setValue(data)
}, []}
Then in your input:
<input value={value} onChange={manageYourValueChange} />
You can use the useRef hook to avoid unnecessary rerender:
import {useRef, userEffect} from 'react'
const Test:React.FC = () => {
const inputRef = useRef(null)
userEffect(() => {
const resp = yourAsyncGetData()
inputRef.current.value = (resp)
}, [])
return <input ref={inputRef} />
}
export default Test
I am using react and a mongoose/mongo database. On the page loading, I do an API call and receive an array of objects (they are "posts" with titles, descriptions, etc). I am trying to map over the data to dynamically display the post info on separate cards. I can get the data from the API call, but when I try to change state to the array of objects and console log it, I get undefined.
Attached is the photo of the code and result:
Console log of Info
The code of API call (related to pic 1 - please notice the code line numbers)
Why isn't usestate accepting the change in state from the data? Attached is my code:
import React, { useState, useEffect } from 'react'
import API from "../utils/API"
const Home = () => {
const [PostObject, setPosts] = useState({
title: "",
description: "",
image: ""
})
const [PostList, setList] = useState()
useEffect(() => {
retrievePosts()
}, [])
const handleInputChange = (e) => {
e.preventDefault()
setPosts({ ...PostObject, [e.target.name]: e.target.value })
}
const createPost = (e) => {
e.preventDefault()
setPosts({ ...PostObject, title: "", description: "", image: "" })
API.addPost(PostObject)
.then(retrievePosts())
}
const retrievePosts = () => {
API.getPost()
.then(res => {
console.log(res.data);
setList(res.data)
console.log(PostList);
})
}
Why isn't usestate accepting the change in state from the data? Attached is my code:
Because you attached an empty dependency array to useEffect as in:
useEffect(() => {
retrievePosts()
}, [])
If you pass an empty dependency array, this useEffect will be called only once. If you want to run the useEffect after every data change you have to pass the data to the dependency array:
useEffect(() => {
retrievePosts()
}, [data /*i.e PostList*/])
However, be careful not to re-render the component infinite amount of times. You can check libraries like React-Query and SWR
One more thing:
I can get the data from the API call, but when I try to change state to the array of objects and console log it, I get undefined.
setState is an async call. You can't log the data right after calling setData. Take a look at this article or to this question and see if they help
I export a JS object called Products to this file, just to replace a real API call initially while I am building/testing. I want to set the function's state to the object, but mapped. I have the component looking like this:
function App() {
const [rooms, setRooms] = useState([]);
const [days, setDays] = useState([]);
const roomsMapped = products.data.map(room => ({
id: room.id,
title: room.title
}))
useEffect(() => {
setRooms(roomsMapped);
})
return ( etc )
This returns the following error: Error: Maximum update depth exceeded.
I feel like I'm missing something really obvious here, but am pretty new to React and Hooks. How can I set this data before the component renders?
Just declare it as initial value of rooms
const Component = () =>{
const [rooms, setRooms] = useState(products.data.map(room => ({
id: room.id,
title: room.title
})))
}
You can also use lazy initial state to avoid reprocessing the initial value on each render
const Component = () =>{
const [rooms, setRooms] = useState(() => products.data.map(room => ({
id: room.id,
title: room.title
})))
}
Change useEffect to this
useEffect(() => {
setRooms(roomsMapped);
},[])
With Lazy initialisation with function as a parameter of useState
import React, { useState } from "react";
function App() {
const [rooms, setRooms] = useState(() => {
// May be a long computation initialization
const data = products.data || [];
return data.map(({ id, title }) => ({ id, title }));
});
return (
// JSX stuffs
)
}
You can use default props for this.set initial value with empty list .
You are getting 'Error: Maximum update depth exceeded', because your useEffect function doesn't have dependency array. Best way to fix this is to pass empty array as the second argument to useEffect like this:
useEffect(() => {
setRooms(roomsMapped);
},[]) <= pass empty array here
this will prevent component to re render, it you want your component to re render on props change you can pass the props in the array like this:
useEffect(() => {
setRooms(roomsMapped);
},[props.props1,props.props2])
here you can pass as many props as you want...
When I call toggleFilterSidebar it should toggle the state of filterSidebarIsOpen from false to true and vice versa but onClick nothing happens, but when I pass the Provider value directly as an object it works.
Why does this work?
1).
return <FilterSidebarContext.Provider value={{
toggleFilterSidebar,
filterSidebarIsOpen,
filters,
}}>{children}</FilterSidebarContext.Provider>;
and this doesnt
2).
const [value] = useState({
toggleFilterSidebar,
filterSidebarIsOpen,
filters,
});
return <FilterSidebarContext.Provider value={value}>{children}</FilterSidebarContext.Provider>;
My Code
FilterSidebar.context.js
import React, { useState } from 'react';
export const FilterSidebarContext = React.createContext({});
export const FilterSidebarProvider = ({ children }) => {
const [filterSidebarIsOpen, setFilterSidebarIsOpen] = useState(true);
const toggleFilterSidebar = () => setFilterSidebarIsOpen(!filterSidebarIsOpen);
const [filters] = useState({ regions: [] });
const [value] = useState({
toggleFilterSidebar,
filterSidebarIsOpen,
filters,
});
return <FilterSidebarContext.Provider value={value}>{children}</FilterSidebarContext.Provider>;
};
export const FilterSidebarConsumer = FilterSidebarContext.Consumer;
export default FilterSidebarContext;
FilterButton.js
const FilterButton = ({ className, getTotalActiveFilters }) => {
const { toggleFilterSidebar, filterSidebarIsOpen } = useContext(FilterSidebarContext);
return <Button className={cx({ [active]: filterSidebarIsOpen })} onClick={toggleFilterSidebar} />;
};
With this code:
const [value] = useState({
toggleFilterSidebar,
filterSidebarIsOpen,
filters,
});
you are providing useState with an initial value which is only used when the component is first mounted. It will not be possible for value to ever change since you aren't even creating a variable for the setter (e.g. const [value, setValue] = useState(...)).
I assume you are using useState here to try to avoid a new object being created with each render and thus forcing a re-render of everything dependent on the context even if it didn't change. The appropriate hook to use for this purpose is useMemo:
const value = useMemo(()=>({
toggleFilterSidebar,
filterSidebarIsOpen,
filters
})[filterSidebarIsOpen]);
I've only put filterSidebarIsOpen into the dependencies array, because with your current code it is the only one of the three that can change (toggleFilterSidebar is a state setter which won't change, filters doesn't currently have a setter so it can't change).
useState expects a function to set the value after useState initially does, so if value represents state, setValue would represent setState...
const [value, setValue] = useState(initialValue);
then use setValue to change it
onClick={() => setValue(newValue)}