React state not being changed after passed down from parent - javascript

I a setState function being passed down from a parent component, I want to setTheState of the parent setter if the enterKey is pressed. Though, when I set the state nothing happens and I'm still left with an empty array
Here's the code snippet
const { check, setCheck } = props // receive from props
const callApi = async (onEnter) => {
const res = await callFunction('', data)
if (res) {
setResults(res)
if (onEnter) {
setCheck(res)
console.log('check', check) // returns []
}
}
Returns [] when I log in parent as well

Instead of console logging it immediately, wait for it to change, because it's an async operation. You can wrap your log in a useEffect:
useEffect(() => {
console.log(check);
}, [check]);

Related

React: How to update a state prior to another function?

I have a state variable called list that updates when setList is called. SetList lives under the function AddToList, which adds a value to the existing values in the list. As of this moment, the function handleList executes prior to the state variable setList even though I have setList added prior to the function handleList. What I am trying to achieve is for the setList to update its list prior to running the handleList. Could you provide insights on how to fix this?
If you want to test the code, https://codesandbox.io/s/asynchronous-test-mp2fq?file=/Form.js
export default function Form() {
const [list, setList] = useState([]);
const addToList = (name) => {
let newDataList = list.concat(name);
setList(newDataList);
console.log("List: ", list);
handleList();
};
const handleList = async () => {
console.log("Handle List Triggered");
await axios
// .put("", list)
.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("Response: ", response);
})
.catch((error) => {});
};
return (
<AutoComplete
name="list"
label="Add to List"
onChange={(events, values) => {
addToList(values.title);
}}
/>
);
}
As you can tell, the get response is made prior to updating the list.
It's not clear what you want to do with the updated list, but you know what the new list will be, so you can just pass that around if you need it immediately.
const addToList = (name) => {
let newDataList = list.concat(name);
setList(newDataList);
console.log("List: ", list);
handleList(newDataList);
};
const handleList = async (list) => {
console.log("Handle List Triggered");
await axios
// .put("", list)
.get("https://api.publicapis.org/entries")
.then((response) => {
console.log("Response: ", response);
})
.catch((error) => {});
};
React's useEffect hook has an array of dependencies that it watches for changes. While a lot of the time it's used with no dependencies (i.e. no second parameter or empty array) to replicate the functionality of componentDidMount and componentDidUpdate, you may be able to use that to trigger handleList by specifying list as a dependency like this:
useEffect(() => {
handleList(list);
}, [list]);
I think there may be a redundant request when page loads though because list will be populated which you'll most likely want to account for to prevent unnecessary requests.
first of all you have to understand setState is not synchronized that means when you call setList(newDataList) that not gonna triggered refer why setState is async
therefore you can use #spender solution
or useStateCallback hook but it's important understand setState is not sync
const [state, setState] = useStateCallback([]);
const addToList = (name) => {
........... your code
setList(newDataList, () => {
// call handleList function here
handleList();
});
}

why useState object value is undefined in first time component render in react native?

i have set one product details to setProduct use state in function fetchProduct().
const [ product, setProduct ] = useState({})
const fetchProduct = () => {
const apiURL = //api url;
fetch(apiURL)
.then((response) => response.json())
.then(res => {
setProduct(res.data[0])
})
.catch(error => {
console.error(error);
});
}
after i have call this function inside useEffect hook. and i have checked product is set or no in call with colsole log
useEffect(() => {
fetchProduct()
console.log(product)
return () => {
}
},[])
following result i have get in console
{}
then i have refreshed again result is showing in console. why in first time result is empty object. thanks for help
fetch product is asynchronous as is setState your console log executes before the data is retrieved and set
There are several behaviors for useEffect and it's dependency array:
In your example you use an empty array which acts as- componentDidMount and runs once.
that's why when you refresh your component it doesn't hold the default value anymore which is {}, but the value that was changed by your function.
In this case i'd add product state to the dependecy array which will cause the useEffect to execute the code inside the useEffect once on mount and whenever the
product state changes
for more https://reactjs.org/docs/hooks-effect.html
In your useEffect when javascript start executing
FetchProduct it continue to compile the next line it wont stop till the function execution finishes and then continue.
If you want to access product and do whatever you want with it you can use another use Effect right after yours.
useEffect(() => {
// Here you can access the product
console.log(product);
}, [product])

How to use an async database call to set a variable with useState() and useEffect()?

I'm trying to set a variable with a simple GET database call. The database call is returning the data correctly, but the variable remains undefined after every re-render. Code is below... the getMyThing() function in the useState() function is working correctly and returning the data I want.
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(getMyThing(id));
useEffect(() => {
setMyThing(myThing)
}, [myThing]);
}
My thinking here was to use useState() to set the initial state of the myThing variable with the data returned from my database. I assume it's not immediately working since a database call is asynchronous, so I thought I could use useEffect() to update the myThing variable after the response of the database call completes, since that would trigger the useEffect() function because I have the myThing variable included as a dependency.
What am I missing here? Thanks!
EDIT: Thanks for the answers everyone, but I still can't get it to work by calling the getMyThing function asynchronously inside useEffect(). Is something wrong with my database call function? I guess it's not set up to a return a promise? Here's what that looks like:
export const getMyThing = (id) => {
axios.get('http://localhost:4000/thing/' + id)
.then(response => {
return(response.data);
})
.catch(function (error){
console.log(error);
})
}
You should do all your side effects(fetching data, subscriptions and such) in useEffect hooks and event handlers. Don't execute async logic in useState as you just assign the promise itself to the variable and not the result of it. In any case, it is a bad practice and it won't work. You should either:
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await getMyThing(id);
setMyThing(result);
};
fetchData();
}, [id, getMyThing]);
}
Or if you don't want to introduce an async function:
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(null);
useEffect(() => {
getMyThing()
.then(result => setMyThing(result));
}, [id, getMyThing]);
}
Also, take note of the [id, getMyThing] part as it is important. This is a dependency array determining when your useEffect hooks are gonna execute/re-execute.
If getMyThing returns a Promise, the myThing will be set to that Promise on the first render, and then myThing will stay referring to that Promise. setMyThing(myThing) just sets the state to the Promise again - it's superfluous.
Call the asynchronous method inside the effect hook instead:
const [myThing, setMyThing] = useState();
useEffect(() => {
getMyThing(id)
.then(setMyThing);
}, []);
Here, myThing will start out undefined, and will be then set to the result of the async call as soon as it resolves.
You can't set the initial state with a value obtained asynchronously because you can't have the value in time.
myThing cannot both return the value you want and be asynchronous. Maybe it returns a promise that resolves to what you want.
Set an initial value with some default data. This might be null data (and later when you return some JSX from your component you can special case myThing === null by, for example, returning a Loading message).
const [myThing, setMyThing] = useState(null);
Trigger the asynchronous call in useEffect, much like you are doing now, but:
Make it rerun when the data it depends on changes, not when the data it sets changes.
Deal with whatever asynchronous mechanism your code uses. In this example I'll assume it returns a promise.
Thus:
useEffect(async () => {
const myNewThing = await getMyThing(id);
setMyThing(myNewThing)
}, [id]);

React re-render not showing updated state array

I am updating state(adding new object to state array) in event handler function.
const handleIncomingData = (data) => {
setState(state => {
var _state = state
if (_state.someDummyStateValue === 1234) {
_state.arr.push({ message: data.message })
}
return _state
})
}
React.useEffect(() => {
socket.on('data', handleIncomingData)
return()=>{
socket.off('data', handleIncomingData)
}
},[])
I am updating state in this manner as event handler function is not having access to latest state values. console.log(state) shows updated state but after re-render newly added object is not displayed. Also when same event fires again previously received data is displayed but not the latest one. Is there something wrong with updating state in this manner?
Object in javascript are copied by reference (the address in the memory where its stored).
When you do some thing like:
let obj1 = {}
let obj2 = obj1;
obj2 has the same reference as obj1.
In your case, you are directly copying the state object. When you call setState, it triggers a re-render. React will not bother updating if the value from the previous render is the same as the current render. Therefore, you need to create a new copy of your state.
Try this instead:
setState(prevValue => [...prevValue, {message: data.message}])
To better illustrate my point here's a codesandbox: https://codesandbox.io/s/wild-snowflake-vm1o4?file=/src/App.js
Try using the default way of making states it is preferred, clean and simple codeing way
const [message, setMessage] = useState([])
const handleIncomingData = data => {
let newMessage = message;
if (data.message.somevalue === 1234){
newMessage.push(data.message);
setMessage(newMessage)
}
}
React.useEffect(() => {
socket.on('data', handleIncomingData)
return()=>{
socket.off('data', handleIncomingData)
}
},[message])
Yes, it's wrong... You should never do operations directly in the state...
If you want to append messages to your array of messages, the correct snippet code would be:
const [messages, setMessage] = useState([])
const handleIncomingData = useCallback(
data => {
if (messages.someDummyStateValue === 1234) {
setMessage([...messages,{message:data.message}])
}
// do nothing
},[messages])
React.useEffect(() => {
socket.on('data', handleIncomingData)
return()=>{
socket.off('data', handleIncomingData)
}
},[handleIncomingData])
This way I'm not doing any operation to any state, I'm just replace the old state, with the new one (which is just the old one with a new message)
Always you want to manipulate data in an array state, you should never do operations directly in the state...

Setting a useEffect hook's dependency within`useEffect` without triggering useEffect

Edit: It just occurred to me that there's likely no need to reset the variable within the useEffect hook. In fact, stateTheCausesUseEffectToBeInvoked's actual value is likely inconsequential. It is, for all intents and purposes, simply a way of triggering useEffect.
Let's say I have a functional React component whose state I initialize using the useEffect hook. I make a call to a service. I retrieve some data. I commit that data to state. Cool. Now, let's say I, at a later time, interact with the same service, except that this time, rather than simply retrieving a list of results, I CREATE or DELETE a single result item, thus modifying the entire result set. I now wish to retrieve an updated copy of the list of data I retrieved earlier. At this point, I'd like to again trigger the useEffect hook I used to initialize my component's state, because I want to re-render the list, this time accounting for the newly-created result item.
​
const myComponent = () => {
const [items, setItems] = ([])
useEffect(() => {
const getSomeData = async () => {
try {
const response = await callToSomeService()
setItems(response.data)
setStateThatCausesUseEffectToBeInvoked(false)
} catch (error) {
// Handle error
console.log(error)
}
}
}, [stateThatCausesUseEffectToBeInvoked])
const createNewItem = async () => {
try {
const response = await callToSomeService()
setStateThatCausesUseEffectToBeInvoked(true)
} catch (error) {
// Handle error
console.log(error)
}
}
}
​
I hope the above makes sense.
​
The thing is that I want to reset stateThatCausesUseEffectToBeInvoked to false WITHOUT forcing a re-render. (Currently, I end up calling the service twice--once for win stateThatCausesUseEffectToBeInvoked is set to true then again when it is reset to false within the context of the useEffect hook. This variable exists solely for the purpose of triggering useEffect and sparing me the need to elsewhere make the selfsame service request that I make within useEffect.
​
Does anyone know how this might be accomplished?
There are a few things you could do to achieve a behavior similar to what you described:
Change stateThatCausesUseEffectToBeInvoked to a number
If you change stateThatCausesUseEffectToBeInvoked to a number, you don't need to reset it after use and can just keep incrementing it to trigger the effect.
useEffect(() => {
// ...
}, [stateThatCausesUseEffectToBeInvoked]);
setStateThatCausesUseEffectToBeInvoked(n => n+1); // Trigger useEffect
Add a condition to the useEffect
Instead of actually changing any logic outside, you could just adjust your useEffect-body to only run if stateThatCausesUseEffectToBeInvoked is true.
This will still trigger the useEffect but jump right out and not cause any unnecessary requests or rerenders.
useEffect(() => {
if (stateThatCausesUseEffectToBeInvoked === true) {
// ...
}
}, [stateThatCausesUseEffectToBeInvoked]);
Assuming that 1) by const [items, setItems] = ([]) you mean const [items, setItems] = useState([]), and 2) that you simply want to reflect the latest data after a call to the API:
When the state of the component is updated, it re-renders on it's own. No need for stateThatCausesUseEffectToBeInvoked:
const myComponent = () => {
const [ items, setItems ] = useState( [] )
const getSomeData = async () => {
try {
const response = await callToSomeService1()
// When response (data) is received, state is updated (setItems)
// When state is updated, the component re-renders on its own
setItems( response.data )
} catch ( error ) {
console.log( error )
}
}
useEffect( () => {
// Call the GET function once ititially, to populate the state (items)
getSomeData()
// use [] to run this only on component mount (initially)
}, [] )
const createNewItem = async () => {
try {
const response = await callToSomeService2()
// Call the POST function to create the item
// When response is received (e.g. is OK), call the GET function
// to ask for all items again.
getSomeData()
} catch ( error ) {
console.log( error )
}
} }
However, instead of getting all items after every action, you could change your array locally, so if the create (POST) response.data is the newly created item, you can add it to items (create a new array that includes it).

Categories