.map is not a function (am I really not using an array here?) - reactjs

I'm trying to learn some react hooks by practice and trying to change the state at the same time.
Here is what my useState hook looks like:
const [names, setNames] = useState([
"James",
"Jessica",
"Roger",
"Alfred"
])
I have used JSX to return the array items from "names" on the page as follows:
{names.map((name)=> {
return <p>{name}</p>
})}
This seems to display everything just fine, suggesting that the map function works correctly on the array called names.
However, when I create a function to update the state using setNames, I get the error "TypeError: names.map is not a function"
Here is what the function looks like:
const addName = () => {
setNames({
names: [...names, "Jessica"]
})
}
I am just running this in an onClick event through a button in the app:
<button onClick={addName}>Add</button>
Sorry in advanced if this is novice but I can't seem to understand why I'm getting this error. I understand that .map can only be used on an array, however that's what I thought names was.. an array. Also it displays names when I use the .map function so I'm just confused by the error itself.
Thanks in advance for any help.

setNames({
names: [...names, "Jessica"]
})
This is changing your state to no longer be an array, but rather to be an object with a .names property on it. Only in class components do you need to pass in an object when setting state.
Instead, you should do the following:
setNames([...names, "Jessica");
One slight improvement you could do is to use the function version of setNames. This will make sure you're always using the most recent version of the state, and thus eliminate the possibility of some bugs when setting state multiple times:
const addName = () => {
setNames(prev => {
return [...prev, "Jessica"];
});
}

You're using the old setState logic.
setNames accepts the new value as first param, not an object, to add a name, change the addName to the following:
const addName = () => {
setNames([...names, "Jessica"])
}
Correct modification of state arrays in React.js
Working example:
const {useState} = React;
const SameSet = () => {
const [names, setNames] = useState([
"James",
"Jessica",
"Roger",
"Alfred"
]);
const addName = () => {
setNames([...names, "Jessica"])
}
return (
<div>
{names.map((name) => <p>{name}</p>)}
<button onClick={addName}>Add</button>
</div>
)
}
ReactDOM.render(<SameSet />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Related

Different values of state in different places

I'm currently learning react and came to the following problem.
When I start dragging div I update state writing div's id in it.
useEffect - writes that it's updated.
console.log() before return does the same.
So if I'm not mistaken, it comfirms that state updated. (I used it to debug and to see if state even updates)
But when dropHandler runs, it says that startBlock is ''. So it doesn't contain value.
export function SomePage() {
const [startBlock, setStartBlock] = useState('') # using this state to store id of start div.
useEffect(() => {
console.log("changed to", startBlock)
}, [startBlock])
function dragStartHandler(e) {
setStartBlock(e.currentTarget.parentElement.id);
}
function dropHandler(e) {
e.preventDefault();
console.log('drop', startBlock)
}
console.log(startBlock)
return (
<div draggable dragStartHandler={dragStartHandler} dropHandler={dropHandler}> # simplified doesn't really matter
)
}
I know that useState is async. But as I already said, useEffect printed that value was updated. So I'm quite confused.
The questions are:
Why startBlock in dropHandler doesn't have value?
How can I fix it?
The attributes you're looking for are called onDragStart and onDragEnd. Correct the names and it works properly.
export function SomePage() {
const [startBlock, setStartBlock] = useState('') # using this state to store id of start div.
useEffect(() => {
console.log("changed to", startBlock)
}, [startBlock])
function dragStartHandler(e) {
setStartBlock(e.currentTarget.parentElement.id);
}
function dropHandler(e) {
e.preventDefault();
console.log('drop', startBlock)
}
console.log(startBlock)
return (
<div draggable onDragStart={dragStartHandler} onDragEnd={dropHandler}> # simplified doesn't really matter
)
}
https://codesandbox.io/s/react-playground-forked-od6bcr?file=/index.js

React useState is not re-rendering while it is referring cloned array by spreading

I am trying to sort an array and reflect its sort result immediately by useState hook.
I already new that react is detecting its state change by Object.is(), so trying to spread array before using useState like below;
const [reviews, setReviews] = useState([...book.reviews]);
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviews([...sortedReviews])
}, [sortRule])
After sorting I checked the value of variable sortedReviews and it was sorted as expected, but react did not re-render the page so this sorting was not reflected to UI.
I already searched solutions and it seemed many could solve the issue by spreading an array to before calling useState like this stack overflow is explaining: Why is useState not triggering re-render?.
However, on my end it is not working.. Any help will be very appreciated. Thank you!
[Added]
And my rendering part is like below;
<>
{
sortedReviews
.map((review: IReview) => (
<ReviewBlock id={review.id}
review={review}
targetBook={book}
setTargetBook={setBook}/>
))
}
</>
Sometimes I facing this issue too, in my case I just "force" render calling callback.
First solution:
const [reviews, setReviews] = useState([...book.reviews]);
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviews(()=> [...sortedReviews]) // <-- Here
}, [sortRule])
second solution:
You can use useRef to get data in real time, see below:
const [reviews, setReviews] = useState([...book.reviews]);
const reviewsRef = useRef();
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviewRef([...sortedReviews])
}, [sortRule])
function setReviewRef(data){
setReview(data);
reviewsRef.current = data;
}
So, instead use the state reviews use reviewsRef.current as u array
I hope you can solve this!
When components don't re-render it is almost always due to mutations of state. Here you are calling a mutating operation .sort on reviews. You would need to spread the array before you mutate it.
useEffect(() => {
const sortedReviews = [...reviews].sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule)
);
setReviews(sortedReviews);
}, [sortRule]);
But there are other issues here. reviews is not a dependency of the useEffect so we would want to use a setReviews callback like setReviews(current => [...current].sort(....
In general sorted data makes more sense as a useMemo than a useState. The sortRule is a state and the sortedReviews are derived from it.
The way that you are calling sortBy as a comparer function feels a bit off. Maybe it's just a confusing name?
Also you should not need to include the type IReview in your callback if book is typed correctly as {reviews: IReview[]}.
If you include your sortBy function and sortRule variable then I can be of more help. But here's what I came up with.
import React, { useState, useMemo } from "react";
type IReview = {
rating: number;
}
type Book = {
reviews: IReview[]
}
type SortRule = string; // just a placeholder - what is this really?
declare function sortBy(a: IReview, b: IReview, rule: SortRule): number;
const MyComponent = ({book}: {book: Book}) => {
const [sortRule, setSortRule] = useState("");
const reviews = book.reviews;
const sortedReviews = useMemo( () => {
return [...reviews].sort((review1, review2) =>
sortBy(review1, review2, sortRule)
);
}, [sortRule, reviews]);
...
}
Typescript Playground Link

React hook renders [object object]

I am new in React and I am not sure what I do wrong. I am trying to use useEffect and save list in useState. But I am getting [object object] back.
I am building simple weather app and I managed to save 1 result into useState, but now I wanna have 3 constant cities showing weather on page load, not depending on what user enteres. Here is the code
const [query, setQuery] = useState('');
const [weather, setWeather] = useState({});
const [weatherConst, setWeatherConst] = useState([]); <-- not working
useEffect(() => {
fetch(`${api.base}group?id=3413829,6618983,2759794&units=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setWeatherConst(result)
console.log("new list" + result)})
}, []) <-- not working
function apiCall() {
fetch(`${api.base}weather?q=${query}&unit=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setWeather(result)
setQuery('')
console.log(result)
})
}
When I console log "new list" i get [object object], but when I run link by itself in browser I get list of 3 cities back
Image of result getting back
Here is a quick snippet illustrating the sequence.
We set the initial state to [] as you have.
On the first render we check state.length and because our initial array is empty it renders <h2>Loading...</h2>.
The useEffect runs and waits 1 second before callingsetState([...loadedState]) which triggers a render (the useEffect cleanup runs here and clears the timer).
We check state.length again and now because our array is no longer empty we render state[0].name, state[1].name, state[2].name. (For a known index or limited number of indexes this is ok, but you'll usually want to use state.map())
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect } = React
const loadedState = [{name: 'foo'},{name: 'bar'},{name: 'baz'}]
function App() {
const [state, setState] = useState([])
useEffect(() => {
const timer = setTimeout(() => {
setState([...loadedState]);
}, 1000);
return () => clearTimeout(timer);
},[])
return (
<div className="container">
{state.length ?
(<div>
<p>{state[0].name}</p>
<p>{state[1].name}</p>
<p>{state[2].name}</p>
</div>) : (<h2>Loading...</h2>)
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
<div id="root"></div>
Note
The answer to your actual question was given in the comments by jonrsharpe.
"new list" + result is string concatenation, which will implicitly call result.toString(), which gives "[object Object]". Use console.log("new list", result) instead, or if you want to make a more readable string form try e.g. JSON.stringify(result, null, 2).
If you would like another example of explanation:
I would like to explain by simple words. When we use "useEffect" or "setSmth (created by useState hook)" hook we need to understand that this is not a simple function which we just call. This is some kind of async function which calls when it needs.
So, for example :
useEffect(() => {
axios
.get(
"https://api.oceandrivers.com:443/v1.0/getForecastPoints/cnarenal/language/en"
)
.then((res) => res.data.data)
.then((res) => {
setWeather(res);
console.log(weather);
})
.catch((e) => {
console.table(e);
});
}, []);
We can not see results in "console.log(weather)" , because setWeather(res) didn't finish work. We see result only after render of component. So, if we create a list:
let weatherList = weather.map((el) => {
return <li key={el.name}> {el.name} </li>;
});
return (
<div className="App">
<ul>{weatherList}</ul>
</div>
);
we'll see all information that we need.
If you would like to see all code: https://codesandbox.io/s/wonderful-feynman-n0h39?file=/src/App.js:503-677
Sorry for my English. If I said smth wrong tell me please, I'll be very appreciated!

How I add an object to an existing array? React

recently I started to work in a proyect and I notice that I need some way to modify a value without losing my previous state. Basically work with an array. However I don't know how to do that.
Can anyone help me by telling me how can I modify an state (I'm using react hooks by the way...) to add more values to the previous state in an array?
Sorry not posting code or something like that, I don't even know how to write that.
Thank you in advance.
Use ES6 spread operator to push new object to the existing array.
Adding a new object to an Array:
const [todo, setTodo] = useState([{ task: "Todo 1" }]);
const addTodo = () => {
let newTodoTask = { task: `Task ${todo.length + 1}` };
setTodo(tasks => [...tasks, { ...newTodoTask }]);
};
Modifying an object in an Array:
const editTask = (e, taskId = 0) => {
setTodo(tasks =>
tasks.map((task, idx) =>
idx === taskId ? { task: "Edited Todo 1" } : { ...task }
)
);
};
Deleting an object from an array
const deleteTask = (e, taskId = 0) => {
setTodo(tasks => tasks.filter((task, idx) => idx !== taskId));
};
Find the simple working example here.
https://codesandbox.io/s/unruffled-elgamal-h7fhg?file=/src/App.js:482-593
First, Learn basic ES6 before start working on the project.
You can use the previous value returned from the setState to update the existing Array
import { useState } from 'react';
export default function Test() {
const [state, setstate] = useState([1,2,3,4);
return <div onClick={() => setstate((prev) => [...prev,99])}>Heyy{state}</div>;
}

React JS Error: Invalid attempt to destructure non-iterable instance

I have a sort filter that takes an array to populate the options. Trying to see the option value equal to the text within the array but I get the error within the title:
Invalid attempt to destructure non-iterable instance
I need to pass the text as the value within the option tag so that when the user updates the filter, the correct text displays to the choice the user made.
Here is my code:
function Sorting({by, order, rp}: SortingProps) {
const opts = [
['Price (low)', 'price', 'asc'],
['Price (high)', 'price', 'desc'],
['Discount (low)', 'discount', 'asc'],
['Discount (high)', 'discount', 'desc'],
['Most popular', 'arrival', 'latest'],
['Most recent', 'arrival', 'latest'],
];
const onChange = (i) => {
const [text, by, order] = opts[i];
refresh({so: {[by]: order}});
/* GA TRACKING */
ga('send', 'event', 'My Shop Sort By', text, 'Used');
};
return (
<div className={cn(shop.sorting, rp.sorting.fill && shop.sortingFill)}>
<Select className={shop.sortingSelect} label="Sort By" onChange={onChange} value={`${by}:${order}`}>
{opts.map(([text], i) =>
<Option key={i} value={text}>{text}</Option>
)}
</Select>
</div>
)
}
I caused this error a few times because whenever I write a useState hook, which I would do often, I'm used to using an array to destructure like so:
const [ state, setState ] = useState();
But my custom hooks usually return an object with properties:
const { data, isLoading } = useMyCustomFetchApiHook();
Sometime I accidentally write [ data, isLoading ] instead of { data, isLoading }, which tiggers this message because you're asking to destructure properties from an array [], when the object you're destructuring from is an object {}.
I also encountered a similar error and honestly, I did a very silly mistake maybe because of editor autocomplete.
I had to make use of the useState hook but somehow due to autocomplete, I wrote it like this.
const [state, setState] = useEffect(defaultValue);
instead of :(.
const [state, setState] = useState(defaultValue);
Hope it will help as an error message, in this case, was not helpful at all until I spent some time debugging this.
The error Invalid attempt to destructure non-iterable instance occurs because of a logic/coding error. The following javascript is used to illustrate the problem:
[aaa,bbb] = somefunc()
When somefunc() is called it must return an array of at least two items. If it doesn't there is no way to convert the result from somefunc() into values for aaa and bbb. For example, the code:
[aaa,bbb] = { 'some': 'object'}
would produce this error.
So the error is really a Javascript coding error and it is just happening inside React code that handles this situation by printing the error shown. See MDN for destructuring assignment documentation.
As #Mayank Shukla states in his answer, the answer to the OP question is to fix this line of code:
const [text, by, order] = opts[i];
By changing it to this:
const [text, by, order] = opts[i.target.value];
With my above description it should be clearer that opts[i] the original code by the OP was not returning an array of at least 3 items so the javascript runtime was not able to set the values of the variables text, by and order. The modified/fixed code does return an array so the variables can be set.
After looking for an answer to this question I realized that the other answers were correct, and I am just summarizing the root cause of the error message.
If anybody is using useState() hooks, and facing above issue while using context. They can try below solution.
In place of []
const [userInfo, setUserInfo] = useContext(userInfoContext);
Use {}
const {userInfo, setUserInfo} = useContext(userInfoContext); // {} can fix your issue
I straight up tried to assign it an empty object!
Bad :(
const [state, setState] = {};
Good :)
const [state, setState] = useState({});
You aren't passing an argument along with your onChange, it's a pretty common thing to miss - however a little less obvious with a select/option combination.
It should look something like:
class Sorting extends React.Component {
constructor(props) {
super(props);
this.opts = [
['Price (low)', 'price', 'asc'],
['Price (high)', 'price', 'desc'],
['Discount (low)', 'discount', 'asc'],
['Discount (high)', 'discount', 'desc'],
['Most popular', 'arrival', 'latest'],
['Most recent', 'arrival', 'latest'],
];
this.state = {
selected: 0, // default value
}
this.onChange = this.onChange.bind(this);
}
onChange(i) {
const [text, by, order] = opts[i.target.value];
};
render() {
return (
<div>
<select onChange={this.onChange} value={this.state.selected}>
{this.opts.map(([text], i) =>
<option key={i} value={i}>{text}</option>
)}
</select>
</div>
)
}
}
ReactDOM.render(<Sorting />, document.getElementById("a"));
Note I stripped out your classes and styles to keep it simple. Also note you were using uppercase Select and Option - unless these are custom in house components, they should be lowercase.
Note2 I also introduced state, because the state of the select needs to be stored somewhere - if you are maintaining the state of the select box outside of this component, you can obviously use a combination of props/callbacks to maintain that value one level higher.
http://codepen.io/cjke/pen/egPKPB?editors=0010
I encountered this question because I had the same error, but in order to make it work for me, I wrote
const [someRef] = useRef(null);
When it should have been
const someRef = useRef(null); // removed the braces []
Make sure your useState is a function call not an array type.
useState('') not useState['']
Problem is with variable i, i will be the event object, use i.target.value to get the value selected by the user, one more thing you used text as the value of the options, instead of that use the index, it will work, try this:
const onChange = (i) => {
const [text, by, order] = opts[i.target.value];
refresh({so: {[by]: order}});
/* GA TRACKING */
ga('send', 'event', 'My Shop Sort By', text, 'Used');
};
return (
<div className={cn(shop.sorting, rp.sorting.fill && shop.sortingFill)}>
<select className={shop.sortingSelect} label="Sort By" onChange={onChange} value={`${by}:${order}`}>
{opts.map(([text], i) =>
<option key={i} value={i}>{text}</option>
)}
</select>
</div>
)
Check this fiddle: https://jsfiddle.net/5pzcr0ef/
This error can also happen if you have an async function that returns an array, but you forget to run it with await. This will result in that error:
const myFunction = async () => {
return [1, 2]
}
const [num1, num2] = myFunction();
Whereas this will succeed:
const [num1, num2] = await myFunction();
Invalid attempt to destructure non-iterable instance
says the instance you are trying to iterate is not iterable. What you should do is checking whether the opt object is iterable and can be accessed in the JSX code.
When using React Context API, this error can occur if you try to use React.useContext() in a component that is not wrapped in the <ContextProvider>
For example, the following code would throw this error:
const App = () => {
const [state, setState] = React.useContext(MyContext)
return (
<ContextProvider>
<SubComponent />
</ContextProvider>
);
}
You can use the line:
const [state, setState] = React.useContext(MyContext)
inside the SubComponent, but not inside the App component. If you want to use it in the App component, place App component inside another component and wrap the App component in <ContextProvider></ContextProvider>.
const App = () => {
const [state, setState] = React.useContext(MyContext)
return (
<div>
<SubComponent />
</div>);
}
const Master = () => {
<ContextProvider>
<App/>
</ContextProvider>
}
In my Case i did this mistake
const {width,height} = Dimensions("window") to const[width ,height] = Dimensions("window)
For me the issue was that I tried to destructure useState incorrectly.
I wrote
const [counter] = useState(0)[0];
instead of
const counter = useState(0)[0];
My 5 cents.
I did
const [seconds, setSeconds] = 0
instead of
const [seconds, setSeconds] = useState(0)
Hope it helps someone. It got me mad for a minute or two because "it worked yesterday" and error was reported on top of functional component body actually, so it wasn't giving right clues and I had to dig deeper. I commented out whole function body just to make sure everything was OK with my arguments... and the error was below in code.

Resources