React modal always take the last element of map function - reactjs

I am currently creating a React todo application. So basically I have two component TodoList and TodoItem
TodoList component will receive an array of objects consisting title as the property and map through this with TodoItem component
In my TodoItem component, the user can choose to edit or delete the item. If the user chose to edit, a modal will show up with the existing title using a textarea. However, I have trouble implementing this function as the modal will always show up with the last element of the array.
TodoList Component
import React, { Component } from 'react'
import TodoItem from './TodoItem'
import { connect } from 'react-redux'
import { clear_todo } from '../store/actions/todoActions'
class Todolist extends Component {
clearList = (e) => {
e.preventDefault()
this.props.clearList(clear_todo());
}
handleChange = (index, title) => {
this.setState({
[index]: title
})
}
render() {
const { items, editItem } = this.props.todo
return (
<ul className="list-group my-5">
<h3 className="text-capitalize text-center">
Todo List
</h3>
{
items.map((item, index) => {
const editedTitle = item.title
return (<TodoItem key={item.id} title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} />)
})
}
<button className="btn btn-danger btn-block text-capitalize mt-5" onClick={this.clearList}> Clear List </button>
</ul>
)
}
}
const mapStateToProps = state => {
return {
todo: state.todo
}
}
const mapDispatchToProps = dispatch => {
return {
clearList: (clear_todo) => { dispatch(clear_todo) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todolist)
TodoItem Component
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { delete_todo, edit_todo, toggle_edit } from '../store/actions/todoActions'
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input } from 'reactstrap';
import TodoEditItem from './TodoEditItem'
class Todoitem extends Component {
// constructor(props) {
// super(props)
// this.state = {
// [props.id]: props.title
// }
// }
handleEdit = (id, title) => {
this.props.editTodo(edit_todo(id, title))
}
toggleEdit = (editItem, title) => {
this.props.toggleEdit(toggle_edit(!editItem))
// this.initializeTitle(title)
}
handleDelete = (id) => {
this.props.deleteTodo(delete_todo(id))
}
// onChange = (e, id) => {
// this.setState({
// [id]: e.target.value
// })
// }
componentDidMount() {
// console.log(this.props)
// this.initializeTitle(this.props.title)
this.setState({
[this.props.id]: this.props.editedTitle
})
console.log(this.state)
}
render() {
// console.log(this.state)
let { id, title, editItem, editedTitle, index } = this.props
console.log(id)
// console.log(index)
// let { item } = this.state
return (
<div>
<li className="list-group-item text-capitlize d-flex justify-content-between my-2">
<h6>{title}</h6>
<div className="todo-icon">
<span className="mx-2 text-success" onClick={this.toggleEdit.bind(this, editItem)} >
<i className="fas fa-pen"></i>
</span>
<span className="mx-2 text-danger" onClick={this.handleDelete.bind(this, id)}>
<i className="fas fa-trash"></i>
</span>
</div>
<Modal isOpen={editItem}>
<ModalHeader>Edit Todo Item</ModalHeader>
<ModalBody>
<Form>
<FormGroup row>
<Input type="textarea" name="text" value={this.state ? this.state[id] : ""} onChange={this.props.onChange} />
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.handleEdit.bind(this, id, editedTitle)}>Save</Button>{' '}
<Button color="secondary" onClick={this.toggleEdit.bind(this, editItem)}> Cancel</Button>
</ModalFooter>
</Modal>
</li>
{/* {editItem ? <TodoEditItem title={title} editItem={editItem} /> : ''} */}
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
deleteTodo: (delete_todo) => { dispatch(delete_todo) },
editTodo: (edit_todo) => { dispatch(edit_todo) },
toggleEdit: (toggle_edit) => { dispatch(toggle_edit) },
}
}
export default connect(null, mapDispatchToProps)(Todoitem)
Sample Image
Image of TodoItem
Image of Modal
As you can see from the image of TodoItem, I am trying to edit "Take out the garbage" but my textarea has been prepopulated as the last element.

You're using the same variable to determine all of the TodoItem's open state. The result is it appears that it is only taking the last value from the array, but really each modal is opening at once and the last one is the only visible modal.
// editItem here is used to determine the open state of both modals
const { items, editItem } = this.props.todo
...
{
items.map((item, index) => {
const editedTitle = item.title
return (
<TodoItem
key={item.id}
title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} // Same value
/>
)
})
}
...
let { editItem } = this.props
<Modal isOpen={editItem}> // All will open and close at the same time
Instead, use a different flag, or just manage the open state in each child like this:
<span className="mx-2 text-success" onClick={() => this.setState({open: true})} >
<i className="fas fa-pen"></i>
</span>
...
<Modal isOpen={this.state.open}>

Related

Using one react state for multiple components

I have a simple counter that I render an Items component that gets props and state as props. I want multiple renders of that same component to use just one counter state instead of counter1 counter2 etc...
Also I want to have just increase and decrease function instead of multiple. Hope the code explains what im trying to say. Is there anyway to do this?
import React from "react";
import Navbar from "./NavBar";
import Items from "./Items";
import "./App.css";
class App extends React.Component {
constructor(props) {
super();
this.state = {
counter1: 0,
counter2: 0,
counter3: 0,
};
}
render() {
//Counter 1
const Increase1 = () => {
this.setState({ counter1: this.state.counter1 + 1 });
};
const Decrease1 = () => {
if (this.state.counter > 0) {
this.setState({ counter1: this.state.counter1 - 1 });
}
};
//Counter 2
const Increase2 = () => {
this.setState({ counter2: this.state.counter2 + 1 });
};
const Decrease2 = () => {
if (this.state.counter > 0) {
this.setState({ counter2: this.state.counter2 - 1 });
}
};
//Counter 3
const Increase3 = () => {
this.setState({ counter3: this.state.counter3 + 1 });
};
const Decrease3 = () => {
if (this.state.counter > 0) {
this.setState({ counter3: this.state.counter3 - 1 });
}
};
return (
<div>
<Navbar />
<div className="container mt-4">
<button className="btn btn-primary mr-4">Redo</button>
<button
className="btn btn-secondary"
onClick={() => window.location.reload()}
>
Reload
</button>
<Items
amount={this.state.counter1}
increase={Increase1}
decrease={Decrease1}
/>
<Items
amount={this.state.counter2}
increase={Increase2}
decrease={Decrease2}
/>
<Items
amount={this.state.counter3}
increase={Increase3}
decrease={Decrease3}
/>
</div>
</div>
);
}
}
export default App;
And here is the Items Component
import React from "react";
function Items(props) {
console.log(props);
return (
<div>
<h6 className="mt-3">{props.amount}</h6>
<button className="btn btn-success mr-3" onClick={props.increase}>
+
</button>
<button className="btn btn-info mr-3" onClick={props.decrease}>
-
</button>
<button className="btn btn-danger mr-3">Delete</button>
</div>
);
}
export default Items;
Sorry for the confusing code it was weird to get into a code block
About the idea you can use only one increase and decrease function for multiple Items by specific a key for each Item
import React from 'react';
import Navbar from './NavBar';
import Items from './Items';
import './App.css';
class App extends React.Component {
constructor(props) {
super();
this.state = {
counter1: 0,
counter2: 0,
counter3: 0,
};
}
increase = (key) => {
this.setState((prevState) => ({ ...prevState, [key]: prevState[key] + 1 }));
};
decrease = (key) => {
this.setState((prevState) => ({ ...prevState, [key]: prevState[key] - 1 || 0}));
};
render() {
return (
<div>
<Navbar />
<div className="container mt-4">
<button className="btn btn-primary mr-4">Redo</button>
<button
className="btn btn-secondary"
onClick={() => window.location.reload()}
>
Reload
</button>
<Items
amount={this.state.counter1}
increase={() => this.increase('counter1')}
decrease={() => this.decrease('counter1')}
/>
<Items
amount={this.state.counter2}
increase={() => this.increase('counter2')}
decrease={() => this.decrease('counter2')}
/>
<Items
amount={this.state.counter3}
increase={() => this.increase('counter3')}
decrease={() => this.decrease('counter3')}
/>
</div>
</div>
);
}
}
export default App;

how to delete item from todo list by clicking on it?

Problem while creating this app, please advise:
How to delete an item that I add to the list
by clicking on the item itself ?
Please advise on how to improve this part.
The app was created as a try to solve some problems which I have with react and to learn this field.
Any advice or any guides in this field will be appreciated.
import React from "react";
import InputBar from "./InputBar";
import ListofItems from "./ListofItems";
class App extends React. Component {
state = {
listOfinputs: [] //name: ""
};
render() {
return (
<div className="ui segment ui container">
<InputBar
//name={"mocahel"}
onSubmitPress={
input =>
this.setState({
listOfinputs: [...this.state.listOfinputs, input]
// name: "matan"
}) //creare new pointer to array
//this.state.listOfinputs.push(input)
}
/>
<ListofItems data={this.state.listOfinputs} />
</div>
);
}
}
export default App;
import React from "react";
class InputBar extends React.Component {
state = { input: "" };
onInputChanged = event => {
//console.log(event)
this.setState({ input: event.target.value });
};
render() {
const { name, onSubmitPress } = this.props;
return (
<form
onSubmit={event => {
onSubmitPress(this.state.input);
event.preventDefault();
}}
>
<label>
Name:
<input
type="text"
value={this.state.input}
onChange={event => this.onInputChanged(event)}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default InputBar;
import React from "react";
const ListofItems = (props) => {
const { data } = props;
console.log(data, 'test2')
return (
<div className="ui segment">
<ul>
{data.map((input, index) =>
<li key={index}>{input}</li>)}
</ul>
</div>
);
};
export default ListofItems;
const ListofItems = (props) => {
const { data } = props;
console.log(data, 'test2')
return (
<div className="ui segment">
<ul>
{data.map((input, index) =>
<a onClick={() => props.deleteItem(index)}><li key={index}>{input}</li></a>)}
</ul>
</div>
);
};
class App extends React. Component {
state = {
listOfinputs: [] //name: ""
};
handleDelete(index) {
// HERE COMES THE DELETE LOGIC FROM THE STATE WITH INDEX
}
render() {
return (
<div className="ui segment ui container">
<InputBar
//name={"mocahel"}
onSubmitPress={
input =>
this.setState({
listOfinputs: [...this.state.listOfinputs, input]
// name: "matan"
}) //creare new pointer to array
//this.state.listOfinputs.push(input)
}
/>
<ListofItems deleteItem={this.handleDelete} data={this.state.listOfinputs} />
</div>
);
}
}

Passing state between nested components

I am trying to open up a Modal component on a onClick from a listItem in a listGroup component. However, the setup I currently have either causes my application to hang and I am not able to click anything on the application or the state does not get updated and the modal does not render.
Another weird that thing that occurs is when I console log to see what the showModalState is, the state changes but when I check the react developer tools to see if it changed, it's always at the initial state which is false.
The error more than likely comes from the ModalActions.ts or ModalReducer.ts.
Note: All the code provided below are just snippets. I omitted alot of stuff and left only what I thought could be the issue.
This is my ModalTypes.ts
export const SHOW_MODAL = "SHOW_MODAL";
interface ShowModal {
type: typeof SHOW_MODAL;
payload: boolean;
}
export type ModalActionTypes = ShowModal;
This is my ModalActions.ts
import { SHOW_MODAL, ModalActionTypes } from "./ModalTypes";
export function UpdateModal(modal: boolean): ModalActionTypes {
return {
type: SHOW_MODAL,
payload: modal
};
}
This is my IModalState.ts
export interface IModalState {
showModal: boolean;
}
This is my ModalReducer.ts. **I will probably make actions and types to hide the modal as well
import { ModalActionTypes, SHOW_MODAL } from "../actions/ModalTypes";
import { IModalState } from "../models/IModalState";
const initialState: IModalState = {
showModal: false
};
export function modalReducer(state = initialState, action: ModalActionTypes) {
switch (action.type) {
case SHOW_MODAL:
return {
...state,
showModal: action.payload
};
default:
return state;
}
}
This is my App.tsx
<ListGroup
onUpdateModal={this.props.onUpdateModal}
showModalState={this.props.showModalState}
/>
const mapStateToProps = (state: AppState) => ({
showModalState: state.modal
});
const mapDispatchToProps = (dispatch: any) => {
return {
onUpdateModal: bindActionCreators(UpdateModal, dispatch)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
This is my ListGroup.tsx
import { UpdateModal } from "../actions/ModalActions";
import { IModalState } from "../models/IModalState";
interface IProps {
onUpdateModal: typeof UpdateModal;
showModalState: IModalState;
}
// interface IState {
// showModal: boolean;
// }
export class ListGroup extends React.Component<IProps> {
// IState
// public state: IState = {
// showModal: false
// };
// showModal = () => {
// // Show the modal
// this.setState({ showModal: true });
// };
public render() {
// const { showModal } = this.state;
return (
<div>
<ul
className="list-group"
style={{
marginTop: "20px",
display: "inline-block"
}}
>
{filterTests.map(filterTest => (
<li
className="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
onClick={() => {
this.props.onUpdateModal(true);
console.log(this.props.onUpdateModal(true));
console.log(this.props.showModalState);
this.props.onUpdateSelectedTest(filterTest);
// this.showModal();
}}
>
{filterTest.companyPN}: {filterTest.description}
</li>
))}
</ul>
{/* Show the modal if showModal is true */}
{this.props.showModalState && (
<TestGroup
testState={this.props.testState}
onUpdateSelectedWedge={this.props.onUpdateSelectedWedge}
/>
)}
</div>
);
}
}
This my TestGroup.tsx
interface IProps {
onUpdateModal: typeof UpdateModal;
showModalState: IModalState;
}
export class TestGroup extends React.Component<IProps> {
// hideModal = () => {
// this.setState({
// showModal: !this.props.showModal
// });
// };
public render() {
return (
<div>
<div className="modal" style={{ display: "inline-block" }}>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title"></h5>
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
</div>
<div className="modal-footer">
<button
// onClick={() => {
// this.hideModal();
// }}
type="button"
className="btn btn-secondary"
data-dismiss="modal"
>
Close
</button>
</div>
</div>
))}
</div>
</div>
</div>
);
}
}
export default TestGroup;
I'd rather leave this as a comment, but I don't have the privilege at the moment.
In your app.ts mapStateToProps function, I think you want showModalState to be
showModalState: state.modal.showModal
In Apps.tsx instead of this.props.showModalState it should have been this.props.showModalState.showModal
{this.props.showModalState.showModal && (
<TestGroup
testState={this.props.testState}
onUpdateSelectedWedge={this.props.onUpdateSelectedWedge}
/>
)}

Importing custom toolbar component doesn't fire methods in react-big-calendar

When I create a custom toolbar in the same file as the Calendar I can use the onView('day') method totally fine. It changes the views. However, when I put the same CalendarToolbar in a different file, and import it in the Calendar file it, doesn't update or change the view. I get the methods as props but it doesn't change anything.
// CustomToolbar
const CalendarToolbar = ({ onView, label, views }) => (
<div>
<h2>
{label}
</h2>
{views.map(view => (
<button
key={view}
type="button"
onClick={() => onView(view)}
>
{view}
</button>
))}
</div>
);
<Calendar
localizer={localizer}
defaultDate={new Date()}
defaultView="day"
events={mockEvents}
style={{ height: '100vh' }}
onSelectEvent={this.handleSelectEvent}
components={{
toolbar: CalendarToolbar,
}}
/>
Just wondered what I'm doing wrong?
I recently wrote my own custom Toolbar component. I copied the original Toolbar, from the repository, and then replaced the render() method with my own, copying what they had done and including my own stuff. My implementation stuff isn't completely important, but if you look at the onClick bits below, it may help you in doing what you want to do:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import ToolbarDateHeader from './ToolbarDateHeader.component';
import { Icon, Button, ButtonGroup, ButtonToolbar } from '../app';
const navigate = {
PREVIOUS: 'PREV',
NEXT: 'NEXT',
TODAY: 'TODAY',
DATE: 'DATE'
};
const propTypes = {
view: PropTypes.string.isRequired,
views: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.node.isRequired,
localizer: PropTypes.object,
onNavigate: PropTypes.func.isRequired,
onView: PropTypes.func.isRequired
};
export default class Toolbar extends Component {
static propTypes = propTypes;
render() {
let {
localizer: { messages },
label,
date
} = this.props;
return (
<ButtonToolbar>
<ButtonGroup>
<Button onClick={this.navigate.bind(null, navigate.TODAY)}>
{messages.today}
</Button>
<Button onClick={this.navigate.bind(null, navigate.PREVIOUS)}>
<Icon glyph="caret-left" />
</Button>
<Button onClick={this.navigate.bind(null, navigate.NEXT)}>
<Icon glyph="caret-right" />
</Button>
</ButtonGroup>
<ToolbarDateHeader date={date} onChange={this.toThisDay}>
{label}
</ToolbarDateHeader>
<ButtonGroup className="pull-right">
{this.viewNamesGroup(messages)}
</ButtonGroup>
</ButtonToolbar>
);
}
toThisDay = date => {
this.props.onView('day');
// give it just a tick to 'set' the view, prior to navigating to the proper date
setTimeout(() => {
this.props.onNavigate(navigate.DATE, date);
}, 100);
};
navigate = action => {
this.props.onNavigate(action);
};
view = view => {
this.props.onView(view);
};
viewNamesGroup(messages) {
let viewNames = this.props.views;
const view = this.props.view;
if (viewNames.length > 1) {
return viewNames.map(name => (
<Button
key={name}
className={cn({
active: view === name,
'btn-primary': view === name
})}
onClick={this.view.bind(null, name)}
>
{messages[name]}
</Button>
));
}
}
}
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import moment from "moment";
function RBCToolbar(props) {
const { label, date, view, views, onView, onNavigate } = props;
const [month, setMonth] = useState("January");
const mMonth = moment(date).format("MMMM");
const months = moment.months();
useEffect(() => {
setMonth(mMonth);
}, [mMonth]);
const onChange = (event) => {
const current = event.target.value;
onNavigate("DATE", moment().month(current).toDate());
setMonth(current);
};
const goToView = (view) => {
onView(view);
};
const goToBack = () => {
onNavigate("PREV");
};
const goToNext = () => {
onNavigate("NEXT");
};
const goToToday = () => {
onNavigate("TODAY");
};
return (
<div className="rbc-toolbar">
<div className="rbc-btn-group">
<button onClick={goToToday}>Today</button>
<button onClick={goToBack}>Back</button>
<button onClick={goToNext}>Next</button>
</div>
<div className="rbc-toolbar-label">
{view === "month" ? (
<>
<select className="rbc-dropdown" value={month} onChange={onChange}>
{months?.map((month) => (
<option
value={month}
className="rbc-dropdown-option" //custom class
key={month}
>
{month}
</option>
))}
</select>
<span className="rbc-year"> {moment(date).format("YYYY")}</span>
</>
) : (
label
)}
</div>
<div className="rbc-btn-group">
{views?.map((item) => (
<button
onClick={() => goToView(item)}
key={item}
className={clsx({ "rbc-active": view === item })}
>
{item}
</button>
))}
</div>
</div>
);
}
export default RBCToolbar;

Multiple buttons do the same

I am building an app in React, that is connected to an API I have written before. Buttons are renderizing but all of them change at the same time. I need advice about how can I write my code in order to separate the functionality.
My app renderize with a .map the same number of Buttons as appointments which is an array. All of them change when this.state.shown change but I need to separate all the buttons in order to only show the one that I clicked. Right now, when I clicked in one of them, this.state.shown change its value so all the buttons change because all depends of the same variable. I am looking for advices about how I can separate this.
class AppointmentsList extends Component {
constructor(props) {
super(props);
this.state = {
appointments: [],
isLoading: false,
shown: false, //Variable to know if a button need to change and render the component
customerUp: false
}
this.toggleCustomer = this.toggleCustomer.bind(this);
//this.showCustomer = this.showCustomer.bind(this);
}
toggleCustomer() {
this.setState({
shown: !this.state.shown
})
} //This function change the value of shown when a Button is clicked.
render() {
const {appointments, isLoading} = this.state;
if(isLoading) {
return <p>Loading...</p>;
}
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown ? <CustomerView id={app.customer.id} /> : null }
</div>
)}
</div>
)
}
How can I reorganize my code in order to render the Buttons separately?
Thanks in advance.
Method 1: You can make shown state a object like:
state = {
shown:{}
}
toggleCustomer(id) {
const updatedShownState = {...this.state.shown};
updatedShownState[id] = updatedShownState[id] ? false : true;
this.setState({
shown: updatedShownState,
})
} //This function change the value of shown when a Button is clicked.
render() {
const {appointments, isLoading} = this.state;
if(isLoading) {
return <p>Loading...</p>;
}
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown[app.customer.id] ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer(app.customer.id) }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown[app.customer.id] ? <CustomerView id={app.customer.id} /> : null }
</div>
)}
</div>
)
}
Method 2: Make a separate component for Button and Customer
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<Appointment key = {app.id} app = {app} />
)}
</div>
)
}
class Appointment extends Component {
state = {
shown: false,
}
toggleCustomer() {
this.setState({
shown: !this.state.shown
})
}
render() {
const { app } = this.props;
return (
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown ? <CustomerView id={app.customer.id} /> : null }
</div>
)
}
}
Let me know if it works and the method you prefer.
You can create a separate component for your button (buttonComponent) inside your AppointmentsList component and pass the shown has props and the in componentDidMount of buttonComponent copy the props to the state of buttonComponent.
This way each button will have its own state, which manages shown.
Button component:
import react from 'react';
interface buttonComponentProps{
shown: boolean;
}
interface buttonComponentState{
shown: boolean;
}
class buttonComponent extends react.Component<buttonComponentProps,{}>{
constructor(props:buttonComponentProps){
super();
this.state{
shown:props.shown
}
}
....
}
export default buttonComponent;

Resources