Storing useState hooks in a common object not working - reactjs

Attempting to organize application hooks in one object:
Minimal, Complete, Verifiable Example
Instead of this:
export default function App() {
const [count1, setCount1] = useState(0)
const [count2, setCount2] = useState(0)
// Usage:
setCount1(17)
console.log(count1)
//-> 17
...this:
export default function App() {
let hook = {
"count1": {
"set": "",
"n": 0
},
"count2": {
"set": "",
"n": 0
}
}
}
const [hook.count1.n, hook.count1.set] = useState(0)
const [hook.count2.n, hook.count2.set] = useState(0)
Also tried this way, but no luck:
const [hook.count1['n'], hook.count1['set'] = useState(0)
const [hook.count2['n'], hook.count2['set'] = useState(0)
Intended to be used like this:
// Usage:
hook.count2.set(17)
console.log(hook.count2.n)
// Expected 17, but...
No luck :( throws this error:
Unexpected token, expected "," (16,13)
(Which is the "." between hook and count1)
Why can't I aggregate hook states and setters as object properties and methods? Ty Keith :^)

The useState hook should return an array consisting of the value at index 0 and setter function at index 1. So what is usually done is to desestructuring the array into 2 other variables, those which we give the names we want. Your problem is not with the hook itself, it is with the array desestructuring, it does not allow, as far as I know, for you to desestructure an array into object attributes. If you try something like the following at the browser console you'll see it won't work either.
const t = [1, () => {}]
const obj = {n: 0, s: null}
const [obj.n, obj.s] = t
You'll see an error happens with the message: Uncaught SyntaxError: Illegal property in declaration context.
So to sum up, javascript syntax does not allow you to do it, as far as I know.

I'm having trouble understanding what the intention is behind your approach of assigning the value and setter to the hooks object. My suggestion would be to use an object that holds all the counts with a single useState instance.
Does something like this work? If not can you elaborate more on why you need to use an approach similar to what you described?
export default function App() {
const [counters, setCouters] = useState({ count1: 0, count2: 0 })
// usage
setCounters({ ...counters, count1: 1 })
// ...
return null
}
Alternatively, you could do something like this if you really want it to be an object for some reason:
export default function App() {
const hooks = {
count1: {},
count2: {},
}
const [count1, setCount1] = React.useState(0)
const [count2, setCount2] = React.useState(0)
hooks.count1.n = count1
hooks.count1.set = setCount1
hooks.count2.n = count2
hooks.count2.set = setCount2
// ...
return null
}

Related

Writing a TypeScript Interface for React Context, how to describe the interface/ different types to be passed as values?

I am trying to write a React Context for my application
This is a follow up from my previous question:
How to pass value to Context Provider in TypeScript?
I would please like some help describing different value types to be passed through the provider.
Overview:
I am currently trying to construct a context provider that can be used across the scope of my application in TypeScript.
It contains some useState hooks and and an asynchronous function to be passed through as values the provider to all the child components.
ResultConextProvider.tsx
export const ResultContextProvider = () => {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [greenStatus, setGreenStatus] =
useState(new Set<MyEnum>());
const [redStatus, setRedStatus] =
useState(new Set<MyEnum>());
const [searchTerm, setSearchTerm] = useState<string>('')
// objects to be passed values
const greenValue = {greenStatus, setGreenStatus};
const redValue = {redStatus, setRedStatus};
const searchValue = {searchTerm, setSearchTerm};
// api function coming from tested API spec (external)
const getResults = async () => {
setIsLoading(true)
myAPI.myGet(greenStatus, redStatus).then((result) => {
setResults(result.data);
})
setIsLoading(false)
}
return (
<ResultContext.Provider value={{getResults, greenValue, redValue, searchValue}}>
{children}
</ResultContext.Provider>
}
export const useResultContext = () => useContext(ResultContext);
As you can see above, I would like to pass the getResults function, my greenValues, redValus and searchValues to all my child components, the Provider implentation will look something like this:
index.tsx
import { ResultContextProvider } from "./contexts/ResultContextProvider";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ResultContextProvider
<Router>
<App />
</Router>
</ResultContextProvider>
);
When it comes to writing the interface, I am struggling to understand what needs to be represented prior to my component.
So far my interface looks something like this:
ResultConextProvider.tsx
interface ContextParametersType {
greenValue: { greenStatus: Set<MyEnum>, setGreenStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
redValue: { redStatus: Set<MyEnum>, setRedStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
///INCORRECT //
// searchValue: {searchTerm: <string>(''), setSearchTerm:
Dispatch<SetStateAction<string> };
///UNSURE
// getResults : {
// What to write as interface for getResults
// }
}
I have worked out what needs to be declared for my greenValue and redValue, based on the answer to my previous question however struggling to fill out the rest of the interface specification
The Search Term:
///INCORRECT //
// searchValue: {searchTerm: (''), setSearchTerm:
Dispatch<SetStateAction };
I have tried to follow the pattern of the Enum state, particularly in declaring the state as type string and rerferecning the Dispatch<SetStateAction as the same type to change the state however this produces error
getResults
This is an asynchronous function with an Axios function inside, to be truthful i do not know where to begin within this one.
My confusion on these two steps persists when creating the ResultContext
const ResultContext = createContext<ContextParametersType>({
greenValue: {
greenStatus: new Set<FirmStatusEnum>(), setGreenStatus: () => {
}
},
redValue: {
redStatus: new Set<FirmStatusEnum>(), setRedStatus: () => {
}
},
// searchValue: {
// searchTerm: <string>(''), setSearchTerm: () => {
// }
// },
getResults()
// Not Sure how to initalsie this?
});
Does this make sense? Am I understanding the issues here correctly?
I would really like some help in understanding how to configure the context interfaces for this.
It is essentially a typescript adaptation of this tutorial
Here:
Thanks, please let me know if I need to provide clarity

React ternary operation for useState()

I am new to React. I have the following setup to initialise my component's state based on a condition, using a ternary operation.
const defaultValue = ['bar']
const myComponent = ({data}) => {
const foo = (data) => {
// ...
}
const [values, setValues] = Object.keys(data).length > 0 ? useState(foo(data)): useState(defaultValue);
return (<Comp state={values} />);
}
export default myComponent
The idea is that the initial state will be dependent on the variable 'data' if it is not empty, otherwise it will be assigned a default value. It is now assumed that 'data' is not empty.
I have it that foo() is being called when I expect it, and the argument, data, is also as I expect. If I log the array that foo returns, it is exactly as I want. Unfortunately, when I log 'state' from inside the Comp component, it is the defaultValue.
If I add a console.log("renders") towards the top of myComponent, I get that printout twice, which I suspect is part of the problem?
If this setup is unconventional please let me know and I'll refactor, but if there is a simple work around that somebody is aware of, please let me know!
It's difficult to give better advice without seeing a bit more code. However, you can use a functional value to initialize the state:
const defaultValue = ['bar'];
const MyComponent = ({data}) => {
const foo = (data) => {
// ...
};
const [values, setValues] = useState(() => Object.keys(data).length > 0 ? foo(data) : defaultValue);
return (<Comp state={values} />);
}
Or just modify the foo closure to fulfill that purpose:
const defaultValue = ['bar'];
const MyComponent = ({data}) => {
const foo = () => {
if (Object.keys(data).length === 0) return defaultValue;
// ...
};
const [values, setValues] = useState(foo);
return (<Comp state={values} />);
}
Using this form has the additional benefit of preventing foo from being invoked needlessly on every render of your component.
The code has a problem because useState is used inside a ternary condition
Object.keys(data).length > 0 ? useState(foo(data)): useState(defaultValue);
We shouldn't useState hook inside any condition check because it'll affect the component rendering as seen in the doc.
To solve the problem
const initialValue = Object.keys(data).length > 0 ? foo(data) : defaultValue;
const [values, setValues] = useState(initialValue);
Ref:
https://beta.reactjs.org/learn/state-a-components-memory#meet-your-first-hook

How to use useReducer to determine which useState variable to use?

This is probably a basic question but,
I have two useState variables:
const [varOne, setVarOne] = useState(null);
const [varTwo, setVarTwo] = useState(null);
And a third variable that tells me which variables I need to use, varOne or varTwo:
const [whichVar, setWhichVar] = useState(0);
And I have a third variable curVar which will be either varOne or varTwo based on the value of whichVar:
const [curVar, setCurVar] = useState(null);
if (whichVar === 0) {
curVar = varOne;
setCurVar = setVarOne;
} else {
curVar = varTwo;
setCurVar = setVarTwo;
}
I realize this is probably wrong, but in another post I was told I could use useReducer to achieve this, what is the most elegant way to achieve this with useReducer?
Well a simple way would be to use a custom hook that stores all the state/logic and returns the currently active value. For instance:
import React, { useState } from 'react';
const useSwitchValue = () => {
const [varOne, setVarOne] = useState('A');
const [varTwo, setVarTwo] = useState('B');
const [whichVar, setWhichVar] = useState(0);
if (whichVar === 0) return {
value: varOne,
setVarOne,
setVarTwo,
switchVars: () => setWhichVar(whichVar ? 0 : 1)
};
else return {
value: varTwo,
setVarOne,
setVarTwo,
switchVars: () => setWhichVar(whichVar ? 0 : 1)
};
};
export default function App() {
const { value, switchVars, setVarOne, setVarTwo } = useSwitchValue();
return (
<div>
<button onClick={switchVars}>
SWITCH VALUES
</button>
<div>{value}</div>
</div>
);
}
With this hook, you can change the state values and switch between them, but when you want to use the currently selected value, you just refer to {value} without having to do any conditional checking (cos that's done once, inside the custom hook).
Sandbox
Yes useReducer can help achieve similar functionality. useReducer will emit your current state and a dispatch function based on a given reducer and initial state. You then use this dispatch method to dispatch types of actions that are specified in your reducer function to manipulate your state.
Check out the first example in the React docs, they’re very helpful https://reactjs.org/docs/hooks-reference.html#usereducer

React Hooks reversing syntax of useState - pretty strange

So I've used React Hooks somewhat heavily in the past few months on one particular project - this is the first time I've seen anything like this and was wondering if anybody had an explanation as to what is happening.
I have the following:
const [ setSectionDefects, sectionDefects ] = useState([]);
const { propertyDefects, propertySections } = props;
useEffect(()=> {
const defectsBySection = [];
propertySections.map(propertySection => {
const sectionId = propertySection.SectionId;
const defectArray = [];
const sectionObject = { id: sectionId, defects: defectArray };
propertyDefects.map(propertyDefect => {
if (propertyDefect.WosectionId == sectionId) {
defectArray.push(propertyDefect);
}
})
defectsBySection.push(sectionObject);
})
// setSectionDefects(defectsBySection);
// sectionDefects(defectsBySection);
}, []);
console.log(setSectionDefects, sectionDefects)
When the code reaches the console.log statement, it says that 'setSectionDefects' is an array and 'sectionDefects' is the function to set it!
My mind is blown, I can't figure it out for the life of me - the syntax, as I've learned it, is the function declaration first, and then the variable to be set -
ie: const [ setSectionDefects, sectionDefects ] = useState([]);
Has anyone else ran into this?
the first item in useState is the state itself and and second item is function to update it.
https://reactjs.org/docs/hooks-reference.html#usestate.
In your case just swipe the names.
const [ sectionDefects , setSectionDefects] = useState([]);
You have array destructuring the wrong way around.
It should be:
const [ sectionDefects, setSectionDefects ] = useState([]);

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