React updating nested arrayelement via setState - reactjs

I'm trying to set state of input fields.
I want to update specific input value when a key is pressed.
setState state looks fine, but I'm getting error on map function of render part.
(this.state.form[this.state.mode].form.map)
I don't understand why it breaks on map.
export type IMode = "login" | "forgot";
export interface IInput {
value: string;
error: string;
id: string;
name: string;
placeholder: string;
type: "text" | "password";
}
export interface IState {
isRequestPending: boolean;
backendError: string;
mode: IMode;
form: {
[key in IMode]: {
name: string;
method: string;
action: string;
fields: IInput[];
};
};
}
in constructor
this.state = {
isRequestPending: false,
backendError: "",
mode: "login",
form: {
login: {
name: "Login",
method: "",
action: "",
fields: [
{
value: "",
error: "",
id: "test",
placeholder: "Login",
type: "text",
name: "login"
}
]
},
.... and so on
}
};
private handleFormInput = (e, input: IInput, index) => {
this.setState((prevState) => ({
...prevState,
backendError: "",
form: {
...prevState.form,
[this.state.mode]: {
...prevState.form[this.state.mode],
fields: {
...prevState.form[this.state.mode].fields,
[index]: {
...prevState.form[this.state.mode].fields[index],
value: "it Works, but map crash"
}
}
}
}
}

You are converting fields into an object which doesn't contain a map method. Update your state like this
this.setState(prev =>({
/*...*/
fields : prev.form[this.state.mode].fields.map((field, i) =>{
if(i !== index) return field
return {
...field,
value : 'foo'
}
})
}))

Related

How to update nested state in redux

I'm a bit stuck with redux. I want to create reducer that can update state onClick with data that provided in each button.
Here's my TabSlice.ts
interface ITabContext {
tabIndex: number,
posterUrlFetch: string,
tabData: {
fetchUrl: string;
title: string;
}[]
}
const initialState = {
tabIndex: 0,
posterUrlFetch: 'movie/popular',
tabData: [
{ fetchUrl: 'movie/popular', title: 'Trending' },
{ fetchUrl: 'movie/upcoming', title: 'Upcoming' },
{ fetchUrl: 'tv/popular', title: 'TV Series' },
]
}
const tabSlice = createSlice({
name: 'tab',
initialState: initialState as ITabContext,
reducers: {
changeTab(state, action: PayloadAction<ITab>) {
const newItem = action.payload;
return state = {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: [
{ fetchUrl: newItem.posterUrlFetch, title: newItem.posterUrlFetch },
]
}
}
}
})
Then I dispatch changeTab in my component and create function onClick:
const click = () => dispatch(changeTab({
tabIndex: 1,
posterUrlFetch: 'movie/popular',
tabData: [
{
fetchUrl: 'tv/latest',
title: 'TV Latest'
},
{
fetchUrl: 'movie/popular',
title: 'Popular'
},
{
fetchUrl: 'movie/latest',
title: 'Latest'
},
]
}));
As i click some info updates, but in tabData I have only first object. How to make it to push all data to tabData, not only first one? Thanks!
Remove return state = {} from your reducer function and instead return the object as a whole.
return {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: newItem.tabData,
};
For the payload's tabData you can pass newItem.tabData

react-select:: Type '{ label: string; value: string; }' is missing the following properties from type 'readonly never[]'

state = {
groupPermissionValue: {label: '', value: ''},
}
<Select instanceId="user-group" onChange={this.selectUserGroupOption}
value={this.state.groupPermissionValue}
options={this.state.groupPermission} id="user-group" className="form-conrol"/>
error
Type error: Type '{ label: string; value: string; }' is missing the following properties from type 'readonly never[]': length, concat, join, slice, and 16 more.
value format is "{label: String, value: String}". but I don't know why get this error.
Options should be an array of objects like below,
const options = [
{ value: 'apple', label: 'Apple' },
{ value: 'orange', label: 'Orange' }
];
Sample Code :-
import React from 'react';
import Select from 'react-select';
const options = [
{ value: 'apple', label: 'Apple' },
{ value: 'orange', label: 'Orange' }
];
class App extends React.Component {
state = {
selectedOption: null,
};
handleChange = selectedOption => {
this.setState(
{ selectedOption },
() => console.log(`Selected Option:`, this.state.selectedOption)
);
};
render() {
const { selectedOption } = this.state;
return (
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
);
}
}

react How to change the value of object in react array

development enviroment
・ react
・ typescript
state to update the member object in the state array.
I implemented is as follows.  
However, I get the following error and cannot implement it well.
errormessage
Type'(IMemberLavel | {Language: IMember;}) []'cannot be assigned to type'IMemberLavel []'
interface IMemberLevel {
member: IMember;
level: ILevel;
}
interface IMember {
id: number;
name: string;
}
interface ILevel {
id: number;
name: string;
}
interface ISearch {
name: string;
age: number;
groups?: IGroups[];
memberLavel: IMemberLevel[];
}
interface IState {
searchState: ISearch;
text: string,
display: boolean
}
const Index: FC = () => {
const [state, setState] = useState<IState>({
searchState: initialSearch,
text: '',
display: boolean
});
const onClickMember = (member: IMember) => {
setState({
...state,
searchState: {
...state.searchState,
memberLavel: [...state.searchState., { member : member }],
},
});
};
Postscript
const inithialMemberLavel: IMemberLevel = {
member: { id: 0, name: '' },
level: { id: 0, name: '' },
};
const initialSearch: ISearch = {
name: '',
age: 0,
groups: [],
memberLavel: inithialMemberLavel[]
}
The new member you're adding to memberLavel is missing level property, either add the property
setState({
...state,
searchState: {
...state.searchState,
memberLavel: [
...state.searchState.memberLavel,
{ member, level: { id: /* some number */, name: /* some name */ } },
],
},
});
or set it as optional in IMemberLevel interface
interface IMemberLevel {
member: IMember;
level?: ILevel;
}

type of custom object in typescript

I'm new to typescript and I created this object:
const initialState = {
title: {
value: "",
error: false
},
amount: {
value: 0,
error: false
},
type: "Food",
time: new Date()
}
I want to use is as initial state of a useState. therefore I want to now what I need to passe as type to my useState.
const [Form, setForm] = useState< "what should come here?" >(initialState);
thanks in advance,
interface IState {
title: {
value: string;
error: boolean;
};
amount: {
value: number;
error: boolean;
};
type: string;
time: Date;
}
const [Form, setForm] = useState<IState>(initialState);
You could do something like this:
interface SomeName {
value: string;
error: boolean;
}
interface IState {
title: SomeName;
amount: SomeName;
type: string;
time: Date;
}
const initialState: IState = {
title: {
value: "",
error: false
},
amount: {
value: 0,
error: false
},
type: "Food",
time: new Date()
}
const [Form, setForm] = useState<IState>(initialState);

Nested Object -> Element implicitly has an 'any' type because expression of type 'string' can't be used to index type

I have a react form component that I'm trying to rewrite to typescript. I am trying to retrieve an object within another object in a for loop but I keep getting the following error
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
I've attempted the solution presented in the following questions but I was still unable to have a working solution and I do not wish to set noImplicitAny to false in tsconfig.json:
TypeScript: Object.keys return string[]
TypeScript TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'
element implicitly has an 'any' type because type '{0}' has no index signature.
The code in question is - The problematic area can be found near the bottom:
import React from 'react';
import Button from '../../UI/Button/Button';
import Input from '../../UI/Input/Input';
type Config = {
elementType: string;
elementConfig: {
type: string;
placeholder: string;
options: {
value: string;
displayValue: string;
};
};
value: string;
validation: {
required: boolean;
isEmail?: boolean;
minLength?: number;
};
valid: boolean;
touched: boolean;
fieldActive: boolean;
// [key: string]: string | Object;
}
interface SignInFormProps {
isSignIn: boolean;
}
interface SignInFormState {
controls: {
email: Config;
password: Config;
};
isSignUp: boolean;
[key: string]: boolean | Object | Config;
}
class SignInForm extends React.Component<SignInFormProps, SignInFormState> {
state = {
controls: {
email: {
elementType: 'input',
elementConfig: {
type: 'email',
placeholder: 'Your Email',
options: {
value: '',
displayValue: ''
},
},
value: '',
validation: {
required: true,
isEmail: true
},
valid: false,
touched: false,
fieldActive: false
},
password: {
elementType: 'input',
elementConfig: {
type: 'password',
placeholder: 'Password',
options: {
value: '',
displayValue: ''
},
},
value: '',
validation: {
required: true,
minLength: 6
},
valid: false,
touched: false,
fieldActive: false
}
},
isSignUp: true
}
private activateField = ( controlName: keyof SignInFormState['controls'] ) => {
do stuff...
}
...
render() {
const formElementsArray: {id: string, config: Config}[] = [];
// ################ The config value is causing the error ################
for ( let key in this.state.controls ) {
formElementsArray.push({
id: key,
config: this.state.controls[key] as Config
});
}
let form = formElementsArray.map( formElement => (
<Input
blur={ ( event ) => this.disableFocus(event, formElement.id) }
changed={ ( event ) => this.inputChangedHandler(event, formElement.id) }
elementConfig={formElement.config.elementConfig}
elementType={formElement.config.elementType}
fieldActive={formElement.config.fieldActive}
focus={ () => this.activateField(formElement.id) }
invalid={!formElement.config.valid}
key={formElement.id}
shouldValidate={formElement.config.validation.required}
touched={formElement.config.touched}
value={formElement.config.value} />
));
If anybody has any ideas on how to resolve this while having clearly defined types and without using any then that would be helpful.
First of all, there isn't a nice way to do this and it's still in discussion: https://github.com/Microsoft/TypeScript/issues/3500
Below are 2 potential ways to solve your issue.
Declare the key outside of the loop:
const formElementsArray: { id: string; config: Config }[] = [];
let key: keyof typeof this.state.controls;
for (key in this.state.controls) {
formElementsArray.push({
id: key,
config: this.state.controls[key],
});
}
Use Object.keys & cast the key to the desired type:
const formElementsArray: { id: string; config: Config }[] = (Object.keys(
this.state.controls
) as (keyof typeof this.state.controls)[]).map((key) => ({
id: key,
config: this.state.controls[key],
}));
You could also make it clearer by using an enum for the control key
enum ControlKey {
email = "email",
password = "password",
}
interface SignInFormState {
controls: {
[key in ControlKey]: Config;
};
isSignUp: boolean;
[key: string]: boolean | Object | Config;
}
then
1b.
const formElementsArray: { id: string; config: Config }[] = [];
let key: ControlKey;
for (key in this.state.controls) {
formElementsArray.push({
id: key,
config: this.state.controls[key],
});
}
or
2b.
const formElementsArray: { id: string; config: Config }[] = (Object.keys(
this.state.controls
) as ControlKey[]).map((key) => ({
id: key,
config: this.state.controls[key],
}));
Hope this helps

Resources