Updating array in react state, using concat - javascript

I have a component of checkbox, basically what im trying to do is to make array of the checked ones and send the information to an api.
but im having trouble updating the state array without deleteing the last object inserted.
im using concat and still same result.
here is the code for the whole component:
import React, { Fragment, Component } from 'react';
import { Inputt, Checkbox } from './style';
interface Istate {
checked: boolean;
products?: any[];
}
interface Iprops {
id: any;
value: any;
changeValue?: (checkBoxId: string, value: any) => void;
}
class CheckBoxComp extends Component<Iprops, Istate> {
state = {
checked: true,
products: [] as any,
};
addOne = (id: any, checked: any) => {
let { products } = this.state;
const newObj = { id, checked };
products = products.concat(newObj);
this.setState({ products }, () => {
console.log(products);
});
};
isChecked = (id: any) => {
const { checked, products } = this.state;
const { changeValue } = this.props;
this.setState({
checked: !checked,
});
if (changeValue) {
changeValue(id, checked);
}
this.addOne(id, checked);
};
render() {
const { id, value } = this.props;
const { checked, products } = this.state;
return (
<Fragment>
<Checkbox>
<span />{' '}
<label className="checkbox-wrapper">
<span className="display" />
<Inputt
type="checkbox"
value={checked}
onChange={() => {
this.isChecked(id);
}}
/>
<span className="checkmark" />
</label>
</Checkbox>
</Fragment>
);
}
}
export default CheckBoxComp;

You can try this approach which utilizes the spread operator to make an array copy:
addOne = (id: any, checked: any) => {
this.setState((state: Istate): Partial<Istate> => ({
products: [
...state.products,
{ id, checked },
],
}), () => console.log(this.state.products));
};

Related

React doesn't update when parent component data retrieved from API

I'm having a functional components, where parent retrieves data from couple of arrays from API and passes them as single object to children. I get the data in parent's useEffect. Unfortunately it seems I'm not getting any data to the component, when debugging the dictionary object in child looks empty.
So in my parent tsx (cutted out nonimportant parts):
export default function RozliczeniaFaktur ({web, context, formType}: IRozliczeniaFakturProps) {
const [dataRows, setDataRows] = React.useState(emptyRowArr);
const [isError, setIsError] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState(null);
const [dictionary, setDictionary] = React.useState<IDictionary>({
organizacja: [], mpk: [], vat: [], zastepstwa: [], konto: []
});
const initDictionary = () => {
let dict : IDictionary = {
organizacja: [], mpk: [], vat: [], zastepstwa: [], konto: []
};
web.lists.getByTitle("Organizacja").items.get().then((items: any[])=> {
dict.organizacja = items;
}, (e: any) => setError(`"Błąd pobierania listy Organizacja: ${e}"`));
// ommited for brevity
setDictionary(dict);
}
React.useEffect(() => {
initDictionary();
},[]);
const addRow = (rowId: number) => {
let d = dataRows;
let i = dataRows[rowId];
d.push(i);
setDataRows(d);
}
return (
<div className={styles.rozliczeniaFaktur}>
{dataRows &&
dataRows.map((dataRow, i) => {
return <TableRowControl web={web} key={i} Context={context} RowId={i} PozFaktury={dataRow} FormType={formType}
onAddRowCallback={addRow} onDeleteRowCallback={delRow} Dictionary={dictionary} />
})
}
</div>
);
}
And in child tsx:
import * as React from 'react';
import styles from './TableRowControl.module.scss';
import {IPozFaktury} from '../IPozFaktury';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { DateTimePicker, DateConvention, IDateTimePickerStrings } from '#pnp/spfx-controls-react/lib/DateTimePicker';
import { Dropdown, IDropdownOption, IDropdownStyles } from 'office-ui-fabric-react/lib/Dropdown';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { PeoplePicker, PrincipalType } from "#pnp/spfx-controls-react/lib/PeoplePicker";
import { WebPartContext } from '#microsoft/sp-webpart-base';
import { DayOfWeek } from 'office-ui-fabric-react';
import { Web } from '#pnp/sp';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { IDictionary } from '../IDictionary';
export interface ITableRowControlProps {
RowId: Number;
PozFaktury: IPozFaktury;
FormType: String;
Context: WebPartContext;
web: Web;
Dictionary: IDictionary;
onAddRowCallback?: (...event: any[]) => void;
onDeleteRowCallback?: (...event: any[]) => void;
onErrorCallback?: (...event: any[]) => void;
}
const TableRowControl = ({RowId, Context, PozFaktury, FormType, onAddRowCallback, onDeleteRowCallback, web, Dictionary, onErrorCallback} : ITableRowControlProps) => {
const [oddzialRozliczeniaOptions, setOddzialRozliczeniaOptions] = React.useState<IDropdownOption[]>([]);
const [vatOptions, setVatOptions] = React.useState<IDropdownOption[]>([]);
const [mpkOptions, setMpkOptions] = React.useState<IDropdownOption[]>([]);
const [kontoGlOptions, setKontoGlOptions] = React.useState<IDropdownOption[]>([]);
const [zaliczkaOptions, setZaliczkaOptions] = React.useState<IDropdownOption[]>([]);
const [isPZrequired, setIsPZrequired] = React.useState(false);
return(
<tr>
<td className={styles.cell}>
<IconButton onClick={()=> onAddRowCallback(RowId)} iconProps={{ iconName: 'Add' }} title="Dodaj" ariaLabel="Dodaj" />
<IconButton onClick={()=> onDeleteRowCallback(RowId)} iconProps={{ iconName: 'Delete' }} title="Usuń" ariaLabel="Usuń" />
</td>
{/* Oddzial rozliczenia */}
<td className={styles.cell}>
<Dropdown options={Dictionary.organizacja.map(i => {return { key: i.Title, text: i.Organizacja }})} selectedKey={PozFaktury.OddzialRozliczenia} />
</td>
</tr>
);
}
export default TableRowControl;
I'm getting no options in dropdowns (works when bound to static array), and when debugging, the Dictionary object in child looks like in it's initial state in parent (with empty arrays).
EDIT:
OK, i seem to have solved the first issue, but now I've got another similar one. When I press 'Add', it looks like the code is adding a copy of a row to array dataRows (correctly), but on screen it's still one row. Where is the problem?

Collect checkbox values as an array React

I have a checkbox component, I want my user to be able to check multiple items, and then the items to be saved in the state as an array.
If I select a checkbox my handleChange function seems to set my array to undefined, I'm not sure if it's the way I am sending the data or If I've setup my checkbox wrong, I'm quite new to React.
My main component is
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
this.setState({[input]: event.target.value})
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
My event form component
export default class EventFormat extends Component {
state = {
eventFormats: [
{id: 1, value: 1, label: "Virtual", isChecked: false},
{id: 2, value: 2, label: "Hybrid", isChecked: false},
{id: 3, value: 3, label: "Live", isChecked: false},
]
}
saveAndContinue = (e) => {
e.preventDefault()
}
render() {
return (
<Form>
<h1 className="ui centered">Form</h1>
<Form.Field>
{
this.state.eventFormats.map((format) => {
return (<CheckBox handleChange={this.props.handleChange} {...format} />)
})
}
</Form.Field>
<Button onClick={this.saveAndContinue}>Next</Button>
</Form>
)
}
}
And finally my checkbox component
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
export default CheckBox
The error is in your handleChange function, which sets state to a dictionary while you said you want the checkbox's value to be added to the eventFormats array in the state.
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
if (event.target.checked) {
this.setState({eventFormats: this.state.eventFormats.concat([event.target.value])});
} else {
const index = this.state.indexOf(event.target.value);
if (index === -1) {
console.error("checkbox was unchecked but had not been registered as checked before");
} else {
this.setState({eventFormats: this.state.eventFormats.splice(index, 1);
}
}
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
There are a few things to fix:
this.setState({[input]: event.target.value})
this will always overwrite the array(eventFormats) with event.target.value.
<CheckBox handleChange={this.props.handleChange} {...format} />
in the above line, you're passing all the properties in each format object
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
but here you're only using label and handleChange.
Here's a React StackBlitz that implements what you're looking for. I used <input type="checkbox" />, you can replace this with the Checkbox component you want. See the console logs to know how the state looks after toggling any of the checkboxes.
Also, added some comments to help you understand the changes.
const Checkbox = ({ id, checked, label, handleChange }) => {
return (
<>
<input
type="checkbox"
id={id}
value={checked}
// passing the id from here to figure out the checkbox to update
onChange={e => handleChange(e, id)}
/>
<label htmlFor={id}>{label}</label>
</>
);
};
export default class App extends React.Component {
state = {
checkboxes: [
{ id: 1, checked: false, label: "a" },
{ id: 2, checked: false, label: "b" },
{ id: 3, checked: false, label: "c" }
]
};
handleChange = inputsType => (event, inputId) => {
const checked = event.target.checked;
// Functional update is recommended as the new state depends on the old state
this.setState(prevState => {
return {
[inputsType]: prevState[inputsType].map(iT => {
// if the ids match update the 'checked' prop
return inputId === iT.id ? { ...iT, checked } : iT;
})
};
});
};
render() {
console.log(this.state.checkboxes);
return (
<div>
{this.state.checkboxes.map(cb => (
<Checkbox
key={cb.id}
handleChange={this.handleChange("checkboxes")}
{...cb}
/>
))}
</div>
);
}
}

ReactJS | Cannot update during an existing state transition

I am trying to add Delete Functionality, to my React Application. So, I created a Delete Model Component. And I am using it in my main page.
Main-Page Component:
import IUser from '../../dto/IUser';
import DeleteUser from '../../components/DeleteUser';
import { listUsers, getUser, deleteUser } from '../../config/service';
interface UserDetailsProps extends RouteComponentProps<RouteUserInfo> {
notify(options: object): any;
actualValue: string;
callBack: any;
label: string;
}
interface RouteUserInfo {
username: string;
}
export interface IState {
errorMessage: LensesHttpResponseObj | null;
isUserDeleteModalOpen: boolean;
isLoading: boolean;
user: IUser | null;
}
const UserToolTip = (props: any): JSX.Element => (
<LensesTooltip id="isActive" place="right" {...props} />
);
export class UserDetailsPage extends Component<UserDetailsProps, IState> {
hasBeenMounted = false;
state: IState = {
isUserDeleteModalOpen: false,
errorMessage: null,
isLoading: false,
user: null
};
componentDidMount(): any {
this.hasBeenMounted = true;
this.onFetchData();
}
componentWillUnmount(): void {
this.hasBeenMounted = false;
}
getUserUsername = (): string => {
const { match } = this.props;
return match.params.username;
};
onFetchData = () => {
this.setState({
isLoading: true
});
return this.onFetchUser();
};
onFetchUser = () =>
getUser(this.getUserUsername())
.then(username => {
if (this.hasBeenMounted && typeof username !== 'undefined') {
this.setState({
isLoading: false,
user: username.data
});
}
})
.catch((errorResponse: HttpResponseObj | null = null) => {
if (this.hasBeenMounted) {
this.setState({
isLoading: false,
user: null,
errorMessage: errorResponse
});
}
});
openUserDeleteModal = () => {
this.setState(prevState => ({
...prevState,
isUserDeleteModalOpen: true
}));
};
closeUserDeleteModal = () => {
this.setState(prevState => ({
...prevState,
isUserDeleteModalOpen: false
}));
};
// Dropdown Render Method:
<Item onClick={this.openUserDeleteModal()}> // The error appears when I add the onClik
<Icon icon="trash-o" className=" pl-0 py-2 col-1" />
<span className="col pr-0 mr-0">Delete User</span>
</Item>
Of course I call the dropdown render Method inside the main render(), along with the render method for the Delete Component:
renderUserDeleteModal = (): JSX.Element | null | void => {
const { isUserDeleteModalOpen, user } = this.state;
if (!user || !user.username) {
return null;
}
return (
<DeleteUser
isModalOpen={isUserDeleteModalOpen}
user={user}
onSuccess={this.closeDeleteModalSuccess}
onCloseModal={this.closeUserDeleteModal}
/>
);
};
But I get this ERROR: warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
I am not sure what am I doing wrong here. To me, it seems legit. Can you explain what am I doing wrong. Thank you!!
you are making call to openUserDeleteModal onClick={this.openUserDeleteModal()} which is causing update of state while rendering the component try the following :
<Item onClick={this.openUserDeleteModal}>
onClik
<Icon icon="trash-o" className=" pl-0 py-2 col-1" />
<span className="col pr-0 mr-0">Delete User</span>
</Item>
You need not invoke the callback to your onClick as that will end up being immediately called upon render.
Remove the parenthesis following onClick={openUserDelete()}.
Your openUserDelete is being called straight away (upon render) and changes the state Object.
changing the state causes a re-render and you can imagine how this would get out of hand...
render > change > render > change...etc

Removing from react array

How do I remove an item from an array in react? I've tried a couple of things and it didnt work out. Just trying to make a basic todo app. I've updated my post to provide the render method to show where the deleteTodo is going. I've also updated my deleteTodo with an answer I got from this post. It kind of works, the only problem is it deletes all of the items in the todo list rather than just the single one.
class App extends Component {
state = {
inputValue: "",
todos: [{
value: "walk the dog",
done: false
},
{
value: "do the dishes",
done: false
}
]
}
addTodo = (e) => {
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
// console.log('clicked')
const newTodo = {
value: this.state.inputValue,
done: false
}
const todos = this.state.todos;
todos.push(newTodo);
this.setState({
todos,
inputValue: ''
})
}
deleteTodo = (value) => {
// Take copy of current todos
const todos = [this.state.todos];
const filteredTodos = todos.filter((item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
render() {
return (
<div className="App">
<Form
addTodo={this.addTodo}
handleSubmit={this.handleSubmit}
/>
<List
todos={this.state.todos}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
export default App;
You just need to filter that value from array and set new filtered array in the setState.
deleteTodo = (value) => {
// Take copy of current todos
const todos = [...this.state.todos];
const filteredTodos = todos.filter( (item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
Your use of filter appears to be the problem.. To create a new array of elements without the value using filter, you can try something like this:
EDIT: Updated answer with a full working demo
import React, {Component} from 'react';
import './App.css';
class Form extends Component {
constructor(props) {
super(props);
this.textRef = React.createRef();
}
render() {
return (
<form onSubmit={(e)=>{e.preventDefault(); this.props.handleSubmit(this.textRef.current.value)}}>
<input type="text" ref={this.textRef}/>
<input type="submit" value="add"/>
</form>
);
}
}
class List extends Component {
render() {
return (
<ul>
{
this.props.todos.map((todo) => (
<li key={todo.value}>
<p><input type="checkbox" checked={todo.done}/>{todo.value} </p>
<input type="button" onClick={() => (this.props.deleteTodo(todo.value))} value="delete"/>
</li>
))
}
</ul>
);
}
}
class App extends Component {
state = {
inputValue: "",
todos: [{
value: "walk the dog",
done: false
},
{
value: "do the dishes",
done: false
}
]
}
addTodo = (e) => {
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (value) => {
const newTodo = {
value,
done: false
}
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({
todos,
inputValue: ''
})
}
deleteTodo = (value) => {
const todos = this.state.todos;
const filteredTodos = todos.filter((item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
render() {
return (
<div className="App">
<Form
addTodo={this.addTodo}
handleSubmit={this.handleSubmit}
/>
<List
todos={this.state.todos}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
export default App;
Also, I changed your handleSubmit method to create a new Array to keep in line with React's functional paradigm
i have used lodash for such this.
lodash is a library for doing such thing https://lodash.com/
if you can get the same value object that you inserted while adding the rest is quite easy
you lodash to find the index in which you have the object in the array
on your delete function
const todos = this.state.todos;
const itemToRemove = {value: "walk the dog",done: false};
var index = _.findIndex(todos, itemToRemove);
const filteredTodos = todos.splice(index, 1)
this.setState({
todos: filteredTodos
})
Hope this will help

Converting React Component to Typescript?

I'm trying to copy the example of React+Redux component to typescript: https://medium.com/#stowball/a-dummys-guide-to-redux-and-thunk-in-react-d8904a7005d3
I'm hitting a deadend with the function:
export default connect(mapStateToProps, mapDispatchToProps)(ItemList);
I'm getting an error about how it's not mappable to ItemList.
One way around this I believe is to change the class declaration to:
class ItemList extends React.Component<{proper component map}, {proper state map}> {
However if I do this because the props have now been mapped I cannot simply include ItemList as and am now expected to provide the params.
Another option might be to: (props as any).fetchData() however this feels wrong.
Is there a way around this? Am I doing React+Redux wrong in typescript?
After you create everything, you export it together with connect.
interface PassedProps {
productId: number;
}
interface StateToProps {
addedProductIds: number[];
quantityById: { [key: string]: number };
quantity: number;
}
interface DispatchToProps {
addToCart: (productId: number) => void;
removeFromCart: (productId: number) => void;
}
// Needs to be added to src/store:GlobalStore interface with the correct prop name created from the name of the reducer
export interface CartState {
addedProductIds: number[];
quantityById: { [key: string]: number };
}
const mapStateToProps = (globalState: GlobalState): StateToProps => {
const state: CartState = globalState.cart;
return {
addedProductIds: state.addedProductIds,
quantityById: state.quantityById,
quantity: Object.keys(state.quantityById).reduce( (sum: number, key: string) => state.quantityById[key] + sum, 0)
};
};
const mapDispatchToProps = (dispatch: Dispatch<any>): DispatchToProps => {
return {
addToCart: (productId: number) => dispatch({ type: 'ADD_TO_CART', productId } as AddToCartAction),
removeFromCart: (productId: number) => dispatch({ type: 'REMOVE_FROM_CART', productId } as RemoveFromCartAction),
};
}
export type Props = PassedProps & StateToProps & DispatchToProps;
class CartButton extends Component<Props, CartState> {
render() {
const { quantity } = this.props;
return (
<View>
<Text>
{ this.props.addedProductIds.length } type item is in the cart, totals to { quantity } item.
</Text>
<View>
<Button
onPress={this.onPressAdd.bind(this)}
title="Add to Cart"
color="#841584"
/>
<Button
onPress={this.onPressRemove.bind(this)}
title="Removefrom Cart"
color="#841584"
/>
</View>
</View>
);
}
onPressAdd() {
this.props.addToCart(this.props.productId);
}
onPressRemove() {
this.props.removeFromCart(this.props.productId);
}
}
export default connect<StateToProps, DispatchToProps, PassedProps>(mapStateToProps, mapDispatchToProps)(CartButton);
Then you can use it with specifying with the required props to be passed (PassedProps interface):
import CartButton from '../components/CartButton';
// ....
render() {
return(<CartButton productId={5} />)
}

Categories