66

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>
    )
}
Filth
  • 3,116
  • 14
  • 51
  • 79
  • 2
    On what line does the error occur? What is the actual value of `i` being passed to your `onChange` handler? I'm guessing that it's an Event object, not an integer. – Jordan Running Feb 09 '17 at 16:24
  • 2
    I think problem is in the parameter `i` you are accepting. It won't be a number. You can't use it as an array index. – Hardik Modha Feb 09 '17 at 16:24

16 Answers16

95

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 {}.

conor909
  • 1,475
  • 14
  • 29
14

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.

Divyanshu Rawat
  • 4,421
  • 2
  • 37
  • 53
12

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
Maheshvirus
  • 6,749
  • 2
  • 38
  • 40
9

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.

PatS
  • 8,833
  • 12
  • 57
  • 100
5

I straight up tried to assign it an empty object!

Bad :(

  const [state, setState] = {};

Good :)

  const [state, setState] = useState({});
sigmapi13
  • 2,355
  • 3
  • 30
  • 27
3

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

Chris
  • 54,599
  • 30
  • 149
  • 186
  • Ok this is close however, I get the error saying "Cannot read property 'value' of undefined"... weird – Filth Feb 09 '17 at 16:41
  • Are you sure you copied everything...? The codepen works. By everything, I mean the "bind" in the constructor as well as the updated value and state – Chris Feb 09 '17 at 16:41
  • The way this project is setup with react is not how you'd normally export components. I agree with the use of class and introducing state however, this project doesn't manage state and using react just for rendering components. It's hooked into PHP so I can't introduce state - the site bombs. – Filth Feb 09 '17 at 16:43
  • .... if you can't use state, well, nothing will update? At all. It might as well be static html - unless I am missing something – Chris Feb 09 '17 at 16:44
  • In a sense it is static, it's completely bizarre how this project is put together - you and I are both missing something... – Filth Feb 09 '17 at 16:46
  • 1
    Haha ok, well I dunno what to say... good luck? I don't think you will receive an answer that doesn't involve state somewhere up the chain. If you're deving a php app (and I know this might not be an option) and you just need a sprinkle of interactivity on the front, consider just using jquery – Chris Feb 09 '17 at 16:49
  • No worries - sounds like an uphill kind of project! – Chris Feb 09 '17 at 16:59
  • I have the fix... wanna see it? The dev that's done this project gave it to me... no way would I guess to do this: https://jsfiddle.net/kL3sxw13/ – Filth Feb 09 '17 at 17:09
2

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 []
John
  • 10,165
  • 5
  • 55
  • 71
2

Make sure your useState is a function call not an array type.

useState('') not useState['']

Charles Chiakwa
  • 184
  • 1
  • 8
1

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/

Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
  • 1
    It won't be the value of option selected in dropdown. It will be an event object. Selected value will be `event.target.value`. – Hardik Modha Feb 09 '17 at 16:29
  • Yes I have this code already but the option does not show the new value if the value is equal to i – Filth Feb 09 '17 at 16:38
  • check the updated ans, removed all the state value, its working properly :) – Mayank Shukla Feb 09 '17 at 16:54
  • 1
    Since we haven't seen the code of the `Select` component we have no way of knowing what the value of `i` is. Event handlers on native elements (e.g `` (capital "S") is not a native element, so without seeing its implementation we have no way of knowing what argument(s) are passed to it. – Jordan Running Feb 09 '17 at 16:59
  • sorry my mistake, in fiddle i used native fields, but forgot to change here :) – Mayank Shukla Feb 09 '17 at 17:04
  • @Jordan Yeah, You are right. Missed that capital `S`. – Hardik Modha Feb 09 '17 at 17:07
1

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();
Ronze
  • 1,544
  • 2
  • 18
  • 33
0

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.

Bumuthu Dilshan
  • 430
  • 5
  • 14
0

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>
}
0

In my Case i did this mistake

const {width,height} = Dimensions("window") to const[width ,height] = Dimensions("window)

0

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];
Manil Malla
  • 133
  • 8
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.

Gishas
  • 380
  • 6
  • 21
0

In my case

const useLoad = () => {
  ...
  const [error, setError] = useState<Error|null>(null);
  ...
  return [..., error]
} 

instead of

const useLoad = () => {
  ...
  const [error, setError] = useState<Error>();
  ...
  return [..., error]
} 
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129