Do functions get the latest state value in React? - javascript

I have a function inside of my functional component that uses a value saved in state. However, when it is called, it has the original value in state, not the updated value. When I look at my component in Chrome React Dev Tools, I see that the updated value is stored in state. Aren't functions supposed to get the latest state value in React? I didn't think I'd have to wrap my functions in a useEffect every time some value in state they depend on changes. Why is this happening?
const Editor = (props) => {
const [template, setTemplate] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
useEffect(() => {
if (props.templateId) {
getTemplate(props.templateId));
}
},[]);
const getTemplate = (templateId) => {
{...make API to get template...}
.then((response) => {
if (response.template) setTemplate(response.template);
});
}
/* THIS FUNCTION SAYS TEMPLATE IS ALWAYS NULL */
const sendClick = async () => {
if (template) {
await updateTemplate();
} else {
await initializeTemplate();
}
setOpenDialog(true);
};
}
UPDATE: I figured out the issue. The sendClick function is being used inside an object that I have in state. When that object is created, it creates a version of the sendClick function based on the state at that time. I realized I needed to refactor my code so that the function is not stored within my object in state so that the function will always have the latest state values.

Please correct the code there its setTemplate(template)); not getTemplate(template));
I'm guessing that you have that right in the source code... if Yes then,
You have got into a trap that all developers new to React fall into.
This code is betraying you ...
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[]); // Here is where you make the mistake
The second argument you pass to the useEffect is called as Dependencies. Meaning if your useEffect is dependent on any state or any variable or function, Ii should be pass as the second argument inside the []. By now you should have got the answer.
Clearly, your useEffect is dependent on template. You should pass that inside the [].
So the code will be : -
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[template]);
Now React will automatically run the function every time the value of template changes therefore, updates template.
For more information about useEffect ...
Refer React Documentation
Refer the useEffect API

Related

how to map array result from console.log into table in react js

I have an array of results coming from an API that I called using axios. get(). Then the result is in array and I want to map it into table in react js.
Below is the API fetching data
const [data, getData] = useState([])
useEffect(() => {
axios.get("http://localhost:4000/api/authentication/history")
.then(result =>{
console.log(result.data.result)
getData(JSON.stringify(result.data.result))
console.log(data.length)
})
}, [])
The line console.log(data.length) returns 0. I dont know why the data is not stored in the data function. That is why when I map {data.map} into it will return error as data.map is not a function. So, is there a simpler way to display and map this array of result into react js table?
Here is the image of the array of result (it is not in json type but in array)
Update #1
Since posting this (7 hours ago), I tried to do this myself, and realised I completely invented this feature, and callback isn't actually a function of useState
I did some research and came across this very useful function:
const useStateCallback = (initialState) => {
const [state, setState] = useState(initialState);
const cbRef = useRef(null); // init mutable ref container for callbacks
const setStateCallback = useCallback((s, cb) => {
cbRef.current = cb; // store current, passed callback in ref
setState(s);
}, []); // keep object reference stable, exactly like `useState`
useEffect(() => {
// cb.current is `null` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
};
Original
I don't know what getData is, but I'll assume it's state, and you have something like this:
const [data, getData] = useStateCallback(); // previously useState
If that's the case, when you call your getData, you can do a callback as the 2nd argument. That callback happens when your state updates successfully:
...
getData(JSON.stringify(result.data.result), () => console.log(data.length));
...
If that's now how you're doing it, then I'd suggest you change whatever you're doing to be in state, and also rename getData to setData
Explanation
When you're calling your getData, you're telling react that you want to update your data? Great! The thing to note is, react doesn't do this update immediately. Instead, it updates it (and other state) in the future, all at the same time.
With that in mind, you pass the callback function as the 2nd argument to tell react what you want to happen once this specific state has been updated
As for getData to setData? That's because the function doesn't return (get) anything, but does set something. Makes your code clearer

How can I update a state variable from a promise?

I am trying to determine if a customer has an active subscription or not. To do this I am utilizing the following code:
const stripe = require('stripe')('some-api-key');
export default function Example(){
// the user will automatically be considered non-subbed by default
const [isSubscriber, setIsSubscriber] = useState(false)
// grab the customer id from stripe
async function get_customer_id() {
const customers = await stripe.customers.search({
query: `metadata[\'some-meta-data-key\']:\'some-meta-data-value\'`
});
return customers.data[0]['id']
}
// grab the list of active subscriptions from stripe
async function customer_is_subscriber(){
const subs = await stripe.subscriptions.list({
status: 'active',
});
return subs
}
// determine if the customer id is in the list of active subscriptions.
// return true if so, false otherwise
async function test_equality(){
const customer_id = await get_customer_id();
const subbed = await customer_is_subscriber();
const answer = subbed.find(sub => sub.customer === customer_id)
return !!answer;
}
useEffect( () => {
async function load_result() {
const promise_function_return = await test_equality()
setIsSubscriber(promise_function_return)
}
load_result();
}, [isSubscriber]);
return (
// some react code
)
}
I have been able to successfully get all of my other functions where I am doing the comparisons for if a user is a subscriber but where I am having an issue is updating the state value (e.g. true if they are subbed, false otherwise).
I found some good past questions on this specific topic such as:
here The useState set method is not reflecting a change immediately
here: setState inside Promise in React
and here: setState inside a Promise function in a useEffect with hooks?
but I just have not been able to get it working correctly. This is currently the closest I have been able to get to solving this problem.
Currently your code says that, when isSubscriber changes, it should check if the user is a subscriber and update the isSubscriber state... so it's a chicken and egg problem. It won't set isSubscriber until isSubscriber gets set.
I think you want to change }, [isSubscriber]); to }, []); so that that code executes when the component first loads (not when isSubscriber changes).
The useEffect hook will always run on mount regardless of if there is anything in its dependency array. This means that your useEffect will work as is, and will run onMount as well as when isSubscriber changes:
useEffect( () => {
async function load_result() {
const promise_function_return = await test_equality()
setIsSubscriber(promise_function_return)
}
load_result();
}, [isSubscriber]);
To verify this, you can check out this codesandbox example. The useEffect looks just like yours, and you will notice that isSubscriber is initially set to false, but is updated to true after 3 seconds.
There's still an adjustment you may want to make even though that part appears to work ok. With isSubscriber in the dependency array, the function in your useEffect will be called any time isSubscriber changes. This probably not what you want, since this function doesn't actually depend on isSubscriber, but actually sets isSubscriber. In this case, that means test_equality() will be run on initial mount and then one more time after it sets isSubscriber, which is unnecessary.
This blog post explains the useEffect dependency array really well.
You can fix this by removing isSubscriber from the dependency array, like this:
useEffect(() => {
console.log("in useEffect");
async function load_result() {
const promise_function_return = await test_equality();
setIsSubscriber(promise_function_return);
}
load_result();
}, [isSubscriber]);
Since you mentioned the state value is not getting updated, there must be another issue going on in either get_customer_id() or customer_is_subscriber(). It would be good to double check and make sure the stripe api calls are working as expected.

Asynchronous way to get the latest change in State with React Hooks

I have started learning React and developing an application using ReactJS. Recently i have moved to React Hooks. I know that in Class Component we can get the latest data from the state with the 2nd argument which is present in setState() like
state = {name: ''};
this.setState({name: name}, () => {console.log(this.state)});
I wanted to know if there is any arguments in React Hooks with which we can get the latest data from it.
I am using React Hooks in the below mentioned way, but upon console logging it always return the previous state
Hooks Eg:
const [socialData, setSocialData] = useState([
{ id: new Date().getTime().toString(), ...newItem }
]);
const onChangeCallback = (index, type, data) => {
let newSocialData = [...socialData];
newSocialData[index] = { ...newSocialData[index], [type]: data };
setSocialData(newSocialData);
onInputChange(newSocialData, formKey);
console.log(newSocialData);
};
The this.setState() second argument is not exactly to get the latest data from the state, but to run some code after the state has been effectively changed.
Remember that setting the state is an asynchronous operation. It is because React needs to wait for other potential state change requests so it can optimize changes and perform them in a single DOM update.
With this.setState() you can pass a function as the first argument, which will receive the latest known state value and should return the new state value.
this.setState((previousState) => {
const newState = previousState + 1;
return newState;
}, () => {
console.log('State has been updated!')
});
With that being said, there are special and rare cases when you need to know exactly when the state change has taken place. In many years of working with React I only faced this scenario once and I consider it a desperate attempt to make things work.
Usually, during a callback execution, like your onChangeCallback you want to change the state as the last thing your function does. If you already know the new state value, why do you want to wait for the real state to change to use it?
The new state value should be a problem to be handled during the next render.
If you want to run some code only when that particular state value changes you can do something like this:
import React, {useState, useEffect, useCallback} from 'react';
function MyComponent() {
const [value, setValue] = useState(false);
const onChangeHandler = useCallback((e) => {
setValue(!!e.target.checked);
}, []);
useEffect(() => {
// THIS WILL RUN ONLY WHEN value CHANGES. VERY SIMILAR TO WHAT YOU ARE TRYING TO DO WITH THE this.setState SECOND ARGUMENT.
}, [value]);
return (
<input type='checkbox' onChange={onChangeHandler} />
);
}
There is also a way to create a custom useState hook, to allow you passing a second argument to setValue and mimic the behavior of this.setState, but internally it would do exactly what I did in the above component. Please let me know if you have any doubt.

React useState hook is not working when submitting form

I am new to React and I have some doubt regarding useState hook.I was recently working on an API based recipe react app .The problem I am facing is when I submit something in search form a state change should happen but the state is not changing but if I resubmit the form the state changes.
import React,{useState,useEffect} from "react";
import Form from "./componnents/form";
import RecipeBlock from "./componnents/recipeblock"
import './App.css';
function App() {
const API_id=process.env.REACT_APP_MY_API_ID;
const API_key=process.env.REACT_APP_MY_API_KEY;
const [query,setQuery]=useState("chicken");
const path=`https://api.edamam.com/search?q=${query}&app_id=${API_id}&app_key=${API_key}`
const [recipe,setRecipe]=useState([]);
useEffect(() => {
console.log("use effect is running")
getRecipe(query);
}, []);
function search(queryString){
setQuery(queryString);
getRecipe();
}
async function getRecipe(){
const response=await fetch(path);
const data=await response.json();
setRecipe(data.hits);
console.log(data.hits);
}
queryString in search() function holds the value of form input,Every time I submit the form this value is coming correctly but setQuery(queryString) is not changing the query value or state and if I resubmit the form then it change the state.
The code you provided something doesn't make sense.
useEffect(() => {
console.log("use effect is running")
getRecipe(query);
}, []);
Your getRecipe doesn't take a variable. But from what I am understanding whenever you search you want to set the Query then get the recipe from that Query.
With the useEffect you can pass in a parameters to check if they changed before running a function. So update the setQuery then when the component reloads it will fire the useEffect if query has changed. Here is the code to explain:
useEffect(() => {
console.log("use effect is running")
getRecipe(query); <-- this doesn't make sense on your code
}, [query]);
function search(queryString){
setQuery(queryString);
}
By doing this when the state updates it causes the component to re-render and therefore if query has changed it will call your getRecipe function.
The main issue in your code is that you are running getRecipe() directly after setQuery(queryString). setQuery(queryString) is asynchronous and will queue a state change. When you then run getRecipe() directly after, the state will still hold the old value of query (and path) and therefore does not fetch the new data correctly.
One solution would be to call getRecipe() within a useEffect() dependent on path.
useEffect(() => {
getRecipe();
}, [path]);
function search(queryString){
setQuery(queryString);
// getRecipe() <- removed
}
With [path] given as dependencies for useEffect(), getRecipe() will be called automatically whenever path changes. So we don't have to call it manually from search() and therefore can remove getRecipe() from the function body. This also makes the current useEffect() (without [path] dependency) redundant, so it can be removed.
Another solution would be to provide the new query value through the getRecipe() parameters, removing the dependency upon the state.
function search(queryString){
setQuery(queryString);
getRecipe(queryString);
}
async function getRecipe(query) {
const path = `https://api.edamam.com/search?q=${query}&app_id=${API_id}&app_key=${API_key}`;
const response = await fetch(path); // <- is no longer dependent upon the state
const data = await response.json();
setRecipe(data.hits);
}
This does require moving the path definition inside getRecipe().

React state inside a function is not changing even after calling it with a delay of (5 seconds)

In react I am using functional component and I have two functions (getBooks) and (loadMore)
getBooks get data from an endPoint. But when I call loadMore function on button click inside the getBooks function (loadMoreClicked) is not changed it uses the previous state even after calling it with a delay of (5 seconds). But when I call loadMore again the state changes and everything works fine.
can someone explain why the (loadMoreClicked) on the initial call to (getBooks) didn't update
even calling it after 5 seconds delay.
function component() {
const [loadMoreClicked, setLoadMore] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`; //this is my end point
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked); //the (loadMoreClicked) value is still (false) after (5 sec)
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
setLoadMore(true); //here i am changing (loadMoreClicked) value to (true)
setTimeout(() => {
getBooks(); // i am calling (getBooks()) after 5 seconds.
}, 5000);
};
return (
<div>
<button onClick={() => loadMore()}>loadMore</button> //calling (loadMore)
function
</div>
);
}
There's two things going on:
getBooks() is using const values that are defined in the surrounding function. When a function references const or let variables outside of its definition, it creates what's called a closure. Closures take the values from those outer variables, and gives the inner function copies of the values as they were when the function was built. In this case, the function was built right after the state was initially called, with loadMoreClicked set to false.
So why didn't setLoadMore(true) trigger a rerender and rewrite the function? When we set state, a rerender doesn't happen instantaneously. It is added to a queue that React manages. This means that, when loadMore() is executed, setLoadMore(true) says "update the state after I'm done running the rest of the code." The rerender happens after the end of the function, so the copy of getBooks() used is the one built and queued in this cycle, with the original values built in.
For what you're doing, you may want to have different functions called in your timeout, depending on whether or not the button was clicked. Or you can create another, more immediate closure, based on whether you want getBooks() to consider the button clicked or not, like so:
const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
() => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
})
.catch(err => {
console.log(err);
});
}
...
const loadMore = () => {
setLoadMore(true);
setTimeout(
getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
5000
);
};
There is a third option, which is rewriting const [loadMoreClicked, setLoadMore] to var [loadMoreClicked, setLoadMore]. While referencing const variables freezes the value in that moment, var does not. var allows a function to reference the variable dynamically, so that the value is determined when the function executes, not when the function was defined.
This sounds like a quick and easy fix, but it can cause confusion when used in a closure such as the second solution above. In that situation, the value is fixed again, because of how closures work. So your code would have values frozen in closures but not in regular functions, which could cause more confusion down the road.
My personal recommendation is to keep the const definitions. var is being used less frequently by the development community because of the confusion of how it works in closures versus standard functions. Most if not all hooks populate consts in practice. Having this as a lone var reference will confuse future developers, who will likely think it's a mistake and change it to fit the pattern, breaking your code.
If you do want to dynamically reference the state of loadMoreClicked, and you don't necessarily need the component to rerender, I'd actually recommend using useRef() instead of useState().
useRef creats an object with a single property, current, which holds whatever value you put in it. When you change current, you are updating a value on a mutable object. So even though the reference to the object is frozen in time, it refers to an object that is available with the most current value.
This would look like:
function component() {
const loadMoreClicked = useRef(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked.current); // This references the property as it is currently defined
})
.catch(err => {
console.log(err);
});
}
const loadMore = () => {
loadMoreClicked.current = true; // property is uodated immediately
setTimeout(getBooks(), 5000);
};
}
This works because, while loadMoreClicked is defined as a const at the top, it is a constant reference to an object, not a constant value. The object being referenced can be mutated however you like.
This is one of the more confusing things in Javascript, and it's usually glossed over in tutorials, so unless you're coming in with some back-end experience with pointers such as in C or C++, it will be weird.
So, for what you are doing, I'd recommend using useRef() instead of useState(). If you really do want to rerender the component, say, if you want to disable a button while loading the content, then reenable it when the content is loaded, I'd probably use both, and rename them to be clearer as to their purpose:
function component() {
const isLoadPending = useRef(false);
const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
if (isLoadPending.current) {
isLoadPending.current = false:
setLoadButtonDisabled(false);
}
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
isLoadPending.current = true;
setLoadButtonDisabled(true);
setTimeout(getBooks(), 5000);
};
}
It's a little more verbose, but it works, and it separates your concerns. The ref is your flag to tell your component what it's doing right now. The state is indicating how the component should render to reflect the button.
Setting state is a fire-and-forget operation. You won't actually see a change in it until your component's entire function has executed. Keep in mind that you get your value before you can use the setter function. So when you set state, you aren't changing anything in this cycle, you're telling React to run another cycle. It's smart enough not to render anything before that second cycle completes, so it's fast, but it still runs two complete cycles, top to bottom.
you can use the useEffect method to watch for loadMoreClicked updates like componentDidUpdate lifecycle method and call the setTimeout inside that,
useEffect(() => {
if(loadMoreClicked){
setTimeout(() => {
getBooks();
}, 5000);
}
}, [loadMoreClicked])
this way only after the loadMoreClicked is changed to true we are calling the setTimeout.
This boils down to how closures work in JavaScript. The function given to setTimeout will get the loadMoreClicked variable from the initial render, since loadMoreClicked is not mutated.

Categories

Resources