Reputation: 864
I'm trying to access state from useState hook but it is giving me the initial state even after I have modified it.
const quotesURL = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
function QuoteGenerator() {
const [quotes, setQuotes] = useState([]);
const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
useEffect(() => {
axios(quotesURL)
.then(result => {
console.log(result);
setQuotes(result.data);
})
.then(() => {
console.log(quotes);
});
}, []);
console.log(quotes) is returning empty array instead of array of objects
Upvotes: 7
Views: 23338
Reputation: 15413
Another way you could refactor your code to work:
const quotesURL = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
function QuoteGenerator = ({ quote }) => {
const [quotes, setQuotes] = useState([]);
const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
const fetchQuote = async quote => {
const result = await axios.get(quotesURL);
setQuotes(result.data);
};
useEffect(() => {
fetchQuote(quote);
}, [quote]);
};
So now you have a function inside of your QuoteGenerator
functional component called fetchQuote
. The useEffect
hook allows us to use something like lifecycle methods, kind of like combining the componentDidMount
and componentDidUpdate
lifecycle methods. In this case I called useEffect
with a function to be ran everytime this component initially gets rendered to the screen and any time the component update as well.
You see in the other answers, that a second argument is passed as an empty array. I put quote
as the first element inside of that empty array as it was passed as a prop in my example, but in others' example it was not, therefore they have an empty array.
If you want to understand why we use an empty array as the second argument, I think the best way to explain it is to quote the Hooks API:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
If you pass an empty array ([]), the props and state as inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model...
In place of setState
we call setQuotes
and this is used to update the list of quotes and I passed in the new array of quotes which is result.data
.
So I passed in fetchQuote
and then passed it the prop that was provided to the component of quote
.
That second argument of the empty array in useEffect
is pretty powerful and not easy to explain and/or understand for everybody right away. For example, if you do something like this useEffect(() => {})
with no empty array as a second argument, that useEffect
function will be making non-stop requests to the JSON server endpoint or whatever.
If you use useEffect(() => {}, [])
with an empty array, it will only be invoked one time which is identical to using a componentDidMount
in a class-based component.
In the example, I gave above, I am instituting a check to limit how often useEffect
gets called, I passed in the value of the props.
The reason I did not put the async function inside of useEffect
is because it's my understanding that we cannot use useEffect
if we are passing an async function or a function that returns a Promise, at least according to the errors I have seen in the past.
With that said, there is a workaround to that limitation like so:
useEffect(
() => {
(async quote => {
const result = await axios.get(quotesURL);
setQuotes(result.data);
})(quote);
},
[quote]
);
This is a more confusing syntax but it supposedly works because we are defining a function and immediately invoking it. Similar to something like this:
(() => console.log('howdy'))()
Upvotes: 1
Reputation: 17333
This is expected. Here's how your code works:
quotes
and setQuotes
are returned from the useState
function.useEffect
runs for the first time once your component is mounted. quotes
(empty array) and setQuotes
are available within this function.setQuotes
. However, two things: 1 - this doesn't immediately update the value of the state. 2 - within the context of useEffect
, quotes
is still an empty array - when you do setQuotes(result.data)
you're creating a new array, and that will not be directly accessible within this context.console.log(quotes);
will give an empty array.Depends on what you're trying to use quotes
for. Why not just directly work with result.data
?
Update: I'm thinking of maybe something like this:
function QuoteGenerator() {
const [quotes, setQuotes] = useState([]);
const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
useEffect(() => {
axios(quotesURL).then(result => {
console.log(result);
setQuotes(result.data);
setSomeOtherState(); // why not do it here?
});
}, []);
}
This way you maintain closer control of the data, without giving it over to lifecycle methods.
Upvotes: 1
Reputation: 31385
Here's how you should do it:
const quotesURL = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
function QuoteGenerator() {
const [quotes, setQuotes] = useState([]);
const [currentQuote, setCurrentQuote] = useState({ quote: "", author: "" });
useEffect(() => { // THIS WILL RUN ONLY AFTER YOUR 1ST RENDER
axios(quotesURL)
.then(result => {
console.log(result);
setQuotes(result.data); // HERE YOU SET quotes AND IT WILL TRIGGER A NEW RENDER
})
}, []); // BECAUSE YOU'VE SET IT WITH '[]'
useEffect(() => { // THIS WILL RUN WHEN THERE'S A CHANGE IN 'quotes'
if (quotes.length) {
setSomeOtherState(); // YOU CAN USE IT TO SET SOME OTHER STATE
}
},[quotes]);
}
How this code works:
useEffects
are not run yet.quotes
has no length
yet.then
clause will run and setQuotes
will be called to set the new quotes
value. This will trigger a re-render.quotes
state has beens updated with the new value.useEffect
will run, because it's "listening" for changes in the quotes
variable that just changes. Then you can use it to set some state like you said.Upvotes: 11