Unable to setState with array of objects - arrays

I'm trying to setState of an empty array with an array of objects on component load.
I've tried new ES6 syntax, I've tried mapping, join, etc but can't get it to work.
The console output of the array I'm trying to insert (not push) into my state looks correct.
mapped arrayObj : [{"word":"teacher","correct":true,"image":"/Assets/Art/Icons/teacher.png"},{"word":"backpack","correct":false,"image":"/Assets/Art/Icons/backpack.png"},{"word":"paper","correct":false,"image":"/Assets/Art/Icons/paper.jpg"}]
Here's the function where I'm mapping my array of objects and then I'm trying to setState of my empty answersObj.
mapArray(){
const arrayObj = this.state.data.answers.map(obj => obj);
let shuffledObjArray = [{}];
shuffledObjArray = this.shuffleArray(arrayObj)
this.setState({
answersObj: shuffledObjArray
})
return shuffledObjArray;
}
I call the mapArray function when the component loads
componentDidMount() {
this.mapArray();
}

Don't forget that setState is async function, so the state doesn't sets immediately.
For example this piece of code works for me:
async componentDidMount() {
await this.mapArray();
console.log(this.state)
}
the obj state gets the value it needs, while w/o the async/await it would print empty state.
Therefore, if you need to render the data from that state I'd suggest making 'dataLoaded' bool and render the data only if the data finished loading.

Related

Explanation needed: getting data from API with useEffect hook and get name

const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})
}
useEffect(hook, [])
This one below doesn't work:
//Uncaught TypeError: Cannot read property 'name' of undefined
console.log(countries[1].name)
This one below does work:
<ul>
{countries.map(country => (
<li>{country.name}</li>
))}
</ul>
Any ide why one method of printing name does work, while the other doesn't?
Coz you can loop through the empty array, but you can't access the index which is not available yet
// So if
countries = []
// this will not throw error
{countries.map(country => (
<li>{country.name}</li>
))}
// but this will
console.log(countries[1].name)
// if you want to check try to run this
console.log(countries.length ? countries[1].name : "not available yer");
The usage of useEffect hook notifies React that component has to perform some side-effects(passed as a callback function to the hook) after it has been rendered, The default behavior of useEffect will run both after the first render and after every update, but when an empty array is passed as a dependency the side-effect will be performed only once after the component has been mounted for the first time.
In the case above useEffect(hook, []) the callback hook will be called after the component has mounted for the first time, which means the component will render with the initial state on it's first render which is an empty array ([]).
That is why when you try to access countries[1].name it errors out, because the value of countries is still an empty array on the first render.
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})
}
useEffect(hook, [])
// can not use index expression to get the first element because
// the value of countries is still an empty array on first render
// it only gets populated when axios.get call is succesful inside the
// callback in useEffect hook after the component has mounted for the first time
console.log(countries[1].name)
Solution
Check for the length of the array before trying to get the first element,
if (countries.length) {
console.log(countries[1].name)
}
P.S.- You should be using a .catch block for handling the error when the API call fails.
There is an example solution for a type of request like this in the React document:
https://reactjs.org/docs/hooks-effect.html
The hooks provided by React are for the most part, asynchronous functions provided by React, to help manage the loading of data, presenting it to the DOM, and dealing with updates. The useEffect behaves in a similar way to componentHasLoaded, where the hook is triggered once the functional component has rendered, and the DOM has been loaded, but it may not have been presented to the user yet. It's important to remember this when working with useEffect. useState is another asynchronous hook, but it provides access to the state property of the hook after it has been instantiated, and won't immediately trigger a re-render of the component, unless the data is updated.
The reason you get an undefined error when you attempt to access console.log(countries[1].name) is because the array at that point is still empty.
I'll explain in code:
const myComponent = () => {
// initialise countries: []
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
// This is allow you to see the DOM change after the effect has run
setTimeout(() => setCountries(response.data), 5000);
})
}
// Tell react to run useEffect once the component is loaded
useEffect(hook, [])
// Display data
return (
<p>Countries: {countries.length}<p>
);
};
Because useEffect is an asynchronous function, it doesn't block the execution of the function and the rendering of the DOM, but refreshes the DOM once useEffect is completed. In this case, you are setting the country list, based on the result of the useEffect function.
The useEffect function will still trigger, you will have access to the state, and the function will re-render when the state is updated.
See codepen example:
https://codepen.io/jmitchell38488/pen/OJMXZPv

API call using React Hooks

I am trying to fetch an api with react hooks. I am able to get the data from the api and declared some attributes with useState. Now I am trying to set the attributes from the api to the attributes in useState, the same way you would do it with setState in class components, but I am not sure how to go about it.
Above is the userData that I did a console.log for in the promise.
useState returns an array where the first element is the data and the second is a function which you call anywhere to update the state later.
From your example:
// Receive the array from useState and use the "array destructuring"
const [myState, setMyState] = useState({/*... you data */});
// ...
useEffect(() => {
// fetch...
// .then...
.then(userData => {
console.log(userData);
setMyState(userData); // call the function here
});
// ...
This should be enough to have your state object updated!
You need to make a few changes, first is that the initial state should be an array
const [initialState, setInitialState] = useState([])
I would use an array since the response is giving you an array of users, in your then statement where you have the console.log you can do
setInitialState(userData)
That will update the initialState value with the array of items, if you actually plan to use just one user just pick one from the userData array. The setInitialState will be the function you need to use when you plan to use the initialState value.

_this2.setState is not a function on react asynchonous

I have some function (Promise Resolve) when onChange handler
This function will fetch data array by PromiseValue, so I use .Then( result ) to get the array field and it work when I print result with console.log.
But the problem is on this.setState because always get error with error message : _this2.setState is not a function
getList(checkboxes['myCheckbox']).then(
result => {
this.setState({ CheckBox: result })
}
).catch( err => {
console.log(err)
});
thanks before
Without more of the component, I'd guess this is probably occurring because your onChange handler in your component isn't bound to the class instance. So when the onChange fires and the promise code is executed, this.setState will not be defined.
You could make it work if you:
bound the functions in the constructor of the class component, i.e. this.handleChange = this.handleChange.bind(this)
use the class fields syntax, i.e. const handleChange = () => { // your code };
converted the component to a functional component and used hooks, i.e. useState;
I could be wrong though as I don't have the full text of the code! Hope this helps :)
Inside Promise.then(). this always refers to the callback function itself. so there would be no setState function.
Try defining a new function to save the result into the state.

react table: refresh table after new data received

I want to refresh my react table after new data is received. I was expecting I trigger this when I change state, however it doesnt work.
Below is state and the update function:
this.state = {
switchListSelected: [],
switchListData: [],
interfaceStatusData: [],
interfaceErrorsData: []
}
updateParentState(data, element){
this.setState(prevState => {
return prevState[element].push(data)
}
}
You are using setState wrong. You should call setState with the new state (or an updater function which returns the new state) and optionally a callback which will be called when the update is done. You can check the usage of setState in the react docs here.
In your example the updateParentState function should look like this:
updateParentState = (data, element) => {
this.setState({
...this.state,
[element]: [...this.state[element], data]
})
}
Note that the state of a component should never be mutated directly. Hence the creation of a new array with the spread operator instead of Array.push which mutates the array.
2 issues here :
When using setState you need to return an object of the state's attributes to update, here you return the result of prevState[element].push(data) (the array length).
You are mutating the array by using Array.push(), you need to use immutable pattern updates to correctly trigger a render with your updated state.
This should work for your case :
this.setState(prevState => ({
[element]: [...prevState[element], data],
}));

keys in object are undefined but object is not (react-redux)

So I have a strange issue with the data I'm fetching from an API. In my reducer I've set the initial state to an empty object and in my component's render method I'm accessing this fetched data using this.props.data.value.one.
The actual fetched data from the API look like this:
{data:
{value: {one: '1'}}
}
However when I try to console.log(this.props.data) it will log {value: {one: '1'}} after logging undefined a couple of times but will log this.props.data.value.one as undefined.
I'm doing similar stuff in my other reducers which are getting data from other API's and they work fine.
App.js Component:
#connect(store => store)
...
componentWillMount = () => {//API Call here}
render = () => {
return (
<div><p>{this.props.data.value.one}</p><div>
)
}
reducer.js:
...
export default function reducer(state={data: {}}, action) {
...
case 'FETCHED_DATA':
return {...state, data: action.data}
action.js:
...
fetchData = user => dispatch => {
//request.get...
.then(res => dispatch({type: 'FETCHED_DATA', data: res.body.data})
}
Since you set you initial state to be an empty object, and the data you get is though an API call which is async, so when the component renders first time this.props.data is undefined.
What you need to do is check if the data is available before using it
Add a conditional check like
this.props.data && this.props.data.value.one
Since you have defined initial state as data: {}, and not as data : {value : ''}, and thus when you fetched data from the api data object is not initialized as data.value to access it. Setting it to the latter should work.
There are 2 issues i see here:
As mentioned in other answers and comments, you should always
conditionally access objects that are fetched asynchronously.
this.props.myObj && this.props.myObj.myValue
By the way, don't fetch inside componentWillMount, instead do it
in componentDidMount. You can read about the reasons for this in
the DOCS.
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
By the look of the shape of your reducer (state) it seems that the
data object is nested inside the reducer:
state={data: {}}
So you can't access it directly from props (unless you are changing
the shape in mapStateToProps).
So i would expect to see this:
this.props.reducerName.data.value.one
instead of this:
this.props.data.value.one

Resources