State of array is not updated in ComponentDidMount - javascript

I have a weird situation, where I have an array as a state:
this.state = { ActivityItem: []} and I am pushing values to it from library that calls an API like this:
getDataFromKit(dateFrom) {
Kit.getSamples(stepCountSample, (err, results) => { //api call
if (err) {
return;
}
const newData = results.map(item => { return { ...item, name: 'itemAmount' } });
this.setState({ d: [...this.state.ActivityItem, ...newData] })
})
Then, I call this method from ComponentDidMount() for array to be loaded
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
console.log("values are", this.state.ActivityItem)
}
Now, the weirdest part: somehow the array is empty in ComponentDidMount, but when I display elements of Array in return of render() function it displays all the values that were added correctly. How is that possible and how I might fix that?

setState is asynchronous in nature. Therefore, logging the state just immediately after setting it can give this behaviour but if set properly, it will display the required content which is happening in your case. Also componentDidMount is called only once in the beginning so you can check for logs in componentDidUpdate method.

State updates are async in nature. If you want to print the state soon after setting your state in class component, then pass a function to the 2nd argument of setState.
Like this
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
// remove console.log
}
...
getDataFromKit(dateFrom) {
...
this.setState({ ActivityItem: [...this.state.ActivityItem, ...newData] }), () => {
console.log("values are", this.state.ActivityItem) //<----
}
})
...
}

use prevstate while updating the state value. React setState is an asynchronous update and does batch updating. Using prevState makes sure that the state value is updated before calculating new state value.
getDataFromKit(dateFrom) {
let stepCountSample = {
startDate: dateFrom.toISOString(),
type: "Type1"
};
Kit.getSamples(stepCountSample, (err, results) => {
//api call
if (err) {
return;
}
const newData = results.map(item => {
return { ...item, name: "itemAmount" };
});
this.setState(prevState => {
ActivityItem: [...prevState.ActivityItem, ...newData];
});
});
}
DOCUMENTATION would help understand the concept
Also, console.log would directly not give the updated state, since state updates are batched. You can use a callback method to setState function. The callback will run only after successfull updation of state value

Related

React state not being changed after passed down from parent

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]);

Managing two states in one action in Vuex

I have two APIs reporting two sets of data (lockboxes and workstations). The lockboxes API has a collection of agencies with a recordId that I need to manipulate. The workstations API is the main collection that will assign one of these agencies (lockboxes) on a toggle to a workstation by sending the lockboxes.recordId and the workstation.recordId in the body to the backend.
My store looks like this
import { axiosInstance } from "boot/axios";
export default {
state: {
lockboxes: [],
workstation: []
},
getters: {
allLockboxes: state => {
return state.lockboxes;
},
singleWorkstation: state => {
let result = {
...state.workstation,
...state.lockboxes
};
return result;
}
},
actions: {
async fetchLockboxes({ commit }) {
const response = await axiosInstance.get("agency/subagency");
commit("setLockboxes", response.data.data);
},
updateAgency: ({ commit, state }, { workstation, lockboxes }) => {
const postdata = {
recordId: state.workstation.recordId,
agency: state.lockboxes.recordId
};
return new Promise((resolve, reject) => {
axiosInstance
.post("Workstation/update", postdata)
.then(({ data, status }) => {
if (status === 200) {
resolve(true);
commit("setWorkstation", data.data);
commit("assignAgency", workstation);
console.log(state);
}
})
.catch(({ error }) => {
reject(error);
});
});
}
},
mutations: {
setWorkstation: (state, workstation) => (state.workstation = workstation),
assignAgency(workstation) { workstation.assign = !workstation.assign},
setLockboxes: (state, lockboxes) => (state.lockboxes = lockboxes)
}
};
Process:
When I select a lockbox from the dropdown and select a toggle switch in the workstation that I want to assign the lockbox too, I do get the lockbox to show but it goes away on refresh because the change only happened on the front end. I'm not really passing the workstation.recordId or lockboxes.recordId in my body as I hoped I was. It is not reading the state and recognizing the recordId for either state(workstation or lockboxes).
the console.log is returning (Uncaught (in promise) undefined)
The request is 404ing with an empty Payload in the body ( {} )
Not even the mutation is firing
template
toggleAssign(workstation) {
this.updateAgency(workstation);
}
At some point I had it that is was reading the workstation.recordId before I tried to merge the two states in the getter but I was never able to access the lockboxes.recordId. How can I have access to two states that live in two independent APIs so I can pass those values in the body of the request?
You can add debugger; in your code instead of console.log to create a breakpoint, and inspect everything in your browser's debug tools.
I can't really help because there are very confusing things:
state: {
lockboxes: [],
workstation: []
},
So both are arrays.
But then:
setWorkstation: (state, workstation) => (state.workstation = workstation),
assignAgency(workstation) { workstation.assign = !workstation.assign},
It seems that workstation is not an array?
And also this, in the getters:
singleWorkstation: state => {
let result = {
...state.workstation,
...state.lockboxes
};
return result;
}
I'm not understanding this. You're creating an object by ...ing arrays? Maybe you meant to do something like:
singleWorkstation: state => {
let result = {
...state.workstation,
lockboxes: [...state.lockboxes]
};
return result;
}
Unless lockboxes is not an array? But it's named like an array, it's declared as an array. You do have this however:
const postdata = {
recordId: state.workstation.recordId,
agency: state.lockboxes.recordId
};
So it seems it's not an array?
Finally, in your updageAgency method, and this is where the problem may lie:
return new Promise((resolve, reject) => {
axiosInstance
.post("Workstation/update", postdata)
.then(({ data, status }) => {
if (status === 200) {
resolve(true);
commit("setWorkstation", data.data);
commit("assignAgency", workstation);
console.log(state);
}
})
.catch(({ error }) => {
reject(error);
});
});
The .then first arg of axios is only invoked if the status code is 2xx or 3xx. So your test if (status === 200) is superfluous because errors would not get there. And if for a reason of another you have a valid code other than 200, the promise never ends. reject is never called, as it's not an error, and neither is resolve. So you should remove check on the status code.
You should also call resolve(true) after the two commits, not before...
Finally your mutation assignAgency is declared all wrong:
assignAgency(workstation) { workstation.assign = !workstation.assign},
A mutation always takes the state as the first param. So it should either be:
assignAgency(state, workstation) {
state.workstation = {...workstation, assign: !workstation.assign}
},
Or
assignAgency(state) {
state.workstation = {...state.workstation, assign: !state.workstation.assign}
},
Depending on if you even need the workstation argument, given that what you want is just toggle a boolean inside an object.
TLDR: I'm not sure if lockboxes should be an array or an object, remove the status check inside your axios callback, fix the assignAgency mutation, use breakpoints with debugger; and the VueJS chrome plugin to help examine your store during development.
In an action, you get passed 2 objects
async myAction(store, payload)
the store object is the whole vuex store as it is right now. So where you are getting commit, you can get the state like so
async fetchLockboxes({ commit,state }) {//...}
Then you can access all state in the app.
You may use rootState to get/set whole state.
updateAgency: ({ commit, rootState , state }, { workstation, lockboxes }) {
rootState.lockboxes=[anything you can set ]
}

How can I save in state the response of an Api Call?

I'm trying to save in the state of my component the data an Api call retrieves, but the data have no time to come cause of the async function so when I check the state its value is an empty array. Here is the code.
async getValuesData() {
let id = "dataid";
let getValuesCall = urlCallToDatabase + "valuesById/" + id;
const response = await fetch(getValuesCall, { headers: {'Content-Type': 'application/json'}})
const res = await response.json()
this.setState = ({
values: res
})
console.log("Data Values: ", res);
console.log("Data Values from state: ", this.state.values);
}
I'm calling the function in the contructor.
First, you've to call the function inside ComponentDidMount lifecycle if you want the component to appear as soon as the data is mounted
Second,I'd do the following:
I declare, either in the same file or in a different one, for example, x.business.js the function that calls the backend and returns the result:
const getValuesData = async () => {
const id = "dataid";
const getValuesCall = urlCallToDatabase + "valuesById/" + id;
const response = await fetch(getValuesCall, { headers: {'Content-Type': 'application/json'}})
return await response.json();
}
Then in the component, if I want it to be called as soon as it is assembled, this is when I make the assignment to its state (and if you want to check that it has been set, you use the callback that setState has after the assignment):
class SampleComponent extends React.Component {
state = {
values: {}
}
componentDidMount() {
getValuesData().then(response =>
this.setState(prevState => ({
...prevState,
values: response
}), () => {
console.log(this.state.values);
}))
}
...
}
As the community says, it's all in the documentation:
componentDidMount: if you need to load data from a remote endpoint and update your state
setState(): to update state of the component
Here's an example of how it would work
You're calling setState incorrectly. It should be:
this.setState({ values: res });
The console.log() calls, even if you adjust the above, won't show accurately what you expect. If that's what you want try this too:
this.setState({ values, res },
() => {
console.log("Data Values: ", res);
console.log("Data Values from state: ", this.state.values);
}
);
I.e., make the console.log()'s the second argument to setState which will then accurately show the value of state.
You should do this :
this.setState({values: res})
this.setState should be a function : https://reactjs.org/docs/react-component.html#setstate
You have to use setState instead of this.state = {}
this.setState({values: res})
Use like this
this.setState({values: res})
use this.setState() to schedule updates to the component local state
Do Not Modify State Directly
// Wrong
this.state.values = res
Instead, use setState()
// Correct
this.setState({values: res});

Correct way to make single conditional call to React Hooks' useFetch function?

I have built a useFetch function that is closely modeled after this: https://www.robinwieruch.de/react-hooks-fetch-data Here's a simplified version of it: How to correctly call useFetch function?
Note that once the fetch is initiated, isLoading is set to true.
I have a use case where a fetch call needs to go out only for an admin user. Here's the code I've added to my React component:
const [companies, companiesFetch] = useFetch(null, {});
if (appStore.currentAccessLevel === 99 && !companies.isLoading && newUsers.companies.length === 0) {
console.log('About to call companiesFetch');
companiesFetch(`${API_ROOT()}acct_mgmt/companies`);
}
useEffect(() => {
if (!companies.isLoading && companies.status === 200) {
newUsers.companies = companies.data.companies;
}
}, [companies.isLoading, companies.status, newUsers.companies, companies.data]);
The idea with the first if statement is to only make the fetch call if ALL of the following is true:
1. The currently logged in user is an admin.
2. companiesFetch hasn't been previously called.
3. newUsers.companies hasn't yet been populated.
This seems logical, but yet if I run this code, companiesFetch is called 25 times before the app crashes. I assume the problem is a timing issue, namely that companies.isLoading isn't set quickly enough.
How would you fix this?
Based on the simplified example you've provided in the link, the problem is the useEffect() within the implementation of useFetch() that calls fetchData() unconditionally after the first render. Given your use-case, you don't need that behavior since you're calling it manually based on the conditions you've enumerated.
In addition to this, you've exacerbated the problem by calling companiesFetch() directly in the functional component, which you're not supposed to do because fetchData() makes synchronous calls to setState() and modifies your component's state during rendering.
You can solve these issues by first modifying useFetch() to remove the unconditional fetch after the first render, and then by moving your conditional logic into a useEffect() callback within the component. Here's how I'd implement useFetch():
const initialFetchState = { isLoading: false, isError: false };
// automatically merge update objects into state
const useLegacyState = initialState => useReducer(
(state, update) => ({ ...state, ...update }),
initialState
);
export const useFetch = config => {
const instance = useMemo(() => axios.create(config), [config]);
const [state, setState] = useLegacyState(initialFetchState);
const latestRef = useRef();
const fetch = useCallback(async (...args) => {
try {
// keep ref for most recent call to fetch()
const current = latestRef.current = Symbol();
setState({ isError: false, isLoading: true });
const { data } = await instance.get(...args).finally(() => {
// cancel if fetch() was called again before this settled
if (current !== latestRef.current) {
return Promise.reject(new Error('cancelled by later call'));
}
});
setState({ data });
} catch (error) {
if (current === latestRef.current) {
setState({ isError: true });
}
} finally {
if (current === latestRef.current) {
setState({ isLoading: false });
}
}
}, [instance]);
return [state, fetch];
};
and here's how I'd call it within useEffect() to prevent synchronous calls to setState():
const [companies, companiesFetch] = useFetch({ baseURL: API_ROOT() });
const [newUsers, setNewUsers] = useState({ companies: [] });
useEffect(() => {
if (!companies.isLoading) {
if (appStore.currentAccessLevel === 99 && newUsers.companies.length === 0) {
console.log('About to call companiesFetch');
companiesFetch('acct_mgmt/companies');
} else if (companies.status === 200 && newUsers.companies !== companies.data.companies) {
setNewUsers({ companies: companies.data.companies });
}
}
}, [appStore.currentAccessLevel, companies, newUsers]);
In your example, I'm not sure what scope newUsers was declared in, but seeing newUsers.companies = ... within the useEffect() callback raised some concern, since the implication is that your functional component is impure, which can easily lead to subtle bugs.

setState not working inside AsyncStorage in react native?

setState not working inside AsyncStorage in React Native.
constructor(props) {
super(props);
this.state = {userId: ''};
}
componentDidMount() {
AsyncStorage.getItem('USER_ID', (err, result) => {
if (!err && result != null) {
this.setState({
userId: result
});
}
else {
this.setState({
userId: null
});
}
});
alert(this.state.userId);
let userId = this.state.userId;
fetch('http://localhost/JsonApi/myprofile.php', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: userId,
}),
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({userDetails: responseJson});
})
.catch((error) => {
console.error(error);
});
}
Setting the userId value using setState and alert returns no value at all. Tried other solutions from Stackoverflow but not as per my expectation.
Note: Code updated. After getting userId from AsyncStorage, it will be passed to fetch. Here, userId value is missing.
2 ways to do this. One is Simple but other is correct way according to react recommendation
One is here- pass value to state directly.
.then((responseJson) => {
// this.setState({userDetails: responseJson});
this.state.userDetails=responseJson;
this.setState({}); //for update render
})
Second Way is here
in the render function Check state Value like this .if UserDetails state is null it will be not give you error whenever userDetails state get data render execute again and provide perfect result.
render() {
return (
<div>
{this.state.userDetails ?
this.state.userDetails.map((data, index) =>
<tr key={index}>
<td>{data.userName}</td>
<td>{data.userEmail}</td>
</tr>
)
: null
}
</div>)}
Let me know gain. if facing issue
Try to alert after updating state. You will get callback once state is updated.
this.setState({
userId: result
},function(){
console.log("userId in async = ", this.state.userId);
alert(this.state.userId);
});
I don't know why you wrote so much code.
First way
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
});
You don't need to check error & result both because if that is null, you are setting userId null in state. so you can directly set value to state userId.
Also set a log to see what is output of your async storage userId.
Please also verify that you are setting value in "USER_ID" somewhere.
Second way
There can different ways also like using async method.
const getUserId = async () => {
try {
const userId = await AsyncStorage.getItem('USER_ID') || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return userId;
}
and you can use
this.setState ({
userId : getUserId()
});
I don't like this way because I need to create another method with async & await keywords.
I use the first way so.
Update
Do your work related to userId inside getIten();, because you alert userId immediately after calling AsyncStorage. And AsyncStorage returns value after you call alert.
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
alert(this.state.userId); // move this line here
});
// removed from here

Categories