repeater field repeating incorrectly - reactjs

I want to click a button to add a tab and then have content inside the tab that can also be repeated. Currently the part where I click on the button to add a new tab is working however entering content in tab one duplicates it in tab 2 once I create another tab ie: the inside content of tab 1 and 2 seems linked when it shouldn't be. I should be able to create multiple tabs and then enter unique data inside each tab.
If I have explained this poorly please let me know and I will elaborate further. I think it perhaps needs to be an array of objects within an array of objects.
registerBlockType("blocks/tabs", {
title: __("Tabbed Blocks", "custom-blocks"),
description: __("Tabbed content blocks", "custom-blocks"),
icon: "smiley",
category: "custom-category",
keywords: [__("tabs", "custom-blocks"), __("repeat", "custom-blocks")],
attributes: {
tabs: {
type: "array",
default: [""],
},
tiles: {
type: "array",
default: [""],
},
},
edit: (props) => {
const {
attributes: { tabs, tiles },
setAttributes,
} = props;
const [showTab, setShowTab] = useState("");
const handleTabClick = (index) => {
console.log(index);
setShowTab(index);
};
return (
<>
<div className="tabs">
<ul id="tabs-nav">
{tabs.map((t, index) => (
<li>
<span onClick={() => handleTabClick(index)} id={`#tab${index}`}>
<RichText
placeholder="Tab title"
onChange={(tabTitle) => {
const newObject = Object.assign({}, t, {
tabTitle: tabTitle,
});
setAttributes({
tabs: [
...tabs.filter((item) => item.index != t.index),
newObject,
],
});
}}
value={t.tabTitle}
/>
</span>
</li>
))}
</ul>
{tabs.map((t, index) => (
<div
id={`tab${index}`}
className={
showTab == index ? `tab-content show` : `tab-content hide`
}
>
<div className="home-tabs">
{tiles.map((tile, index) => (
<div
className="tab-block"
>
<div className="copy">
<RichText
tagName="h3"
placeholder="Tab Content Title"
onChange={(tabTileTitle) => {
const newObject = Object.assign({}, tile, {
tabTileTitle: tabTileTitle,
});
setAttributes({
tiles: [
...tiles.filter(
(item) => item.index != tile.index
),
newObject,
],
});
}}
value={tile.tabTileTitle}
/>
<p>
Some content...
</p>
</div>
</div>
))}
</div>
</div>
))}
<Button
onClick={() => {
setAttributes({
tiles: [...tiles, { index: tiles.length }],
});
}}
>
Add Tile
</Button>
</div>
<Button
onClick={() => {
setAttributes({
tabs: [...tabs, { index: tabs.length }],
});
console.log(tabs);
}}
>
Add Tab
</Button>
</>
);
},
save: (props) => {
return null;
},
});

Related

Filter books according to the book types and advanced types (React.js, MongoDB, Express)

I am creating a full-stack comic book system, in the main page, it will show all the books in the database, there is an input box for the user to search the book name, and there are category buttons for the user to filter the books by the book types. I had made the rendering of all books and search for book name features work, but I cannot filter the books by categories, it just did nothing. I am just doing the filter category feature on the frontend but not the backend, which I watched some Youtube tutorials, they are all doing it on the frontend-side only. Please have a look at my codes, what I have done wrong.
Database model
const bookSchema = new mongoose.Schema({
bookName: {
type: String
},
author: {
type: String
},
publisher: {
type: String
},
yearReleased: {
type: Number
},
type: {
type: String
},
advancedBookType: {
type: String
},
bookDescription: {
type: String
},
rentalPrice: {
type: Number
},
bookStatus: {
type: String,
enum: ['Available', 'Pending to pickup', 'Lent out', 'Overdue'],
default: "Available"
},
bookImage: {
type: String
}
})
module.exports = mongoose.model('Book', bookSchema)
I am just using string as datatype of the book type, but not an enum, would that be a problem?
React main page component
class BookTableUser extends Component {
constructor(props) {
super(props)
this.state = {
books: [],
searchText: '',
typeButton: ''
}
this.changeSearchText = this.changeSearchText.bind(this);
this.filterItem = this.filterItem.bind(this);
}
filterItem = (typeButton) => {
this.updatedItems = this.state.books.filter((book) => {
return book.type === typeButton;
});
this.setState(this.updatedItems);
}
render() {
const { typeButton } = this.state;
return (
<div className='layout-container'>
<div className='sidebar'>
<input className='searchbar' type="text" placeholder='Search book....' onChange={this.changeSearchText} value={this.state.searchText} />
<div className='all-types-container'>
<div className='all-container'>
<button className='type-button' onClick={() => this.setState(this.state.books)}>All</button>
</div>
<div className='types-container'>
<h5>Book type</h5>
<button className='type-button' onClick={() => this.filterItem('Comedy')}>Comedy</button>
<button className='type-button' onClick={() => this.filterItem('Love')}>Love</button>
<button className='type-button' onClick={() => this.filterItem('Horror')}>Horror</button>
<button className='type-button' onClick={() => this.filterItem('Detecting')}>Detecting</button>
<button className='type-button' onClick={() => this.filterItem('Fiction')}>Fiction</button>
<button className='type-button' onClick={() => this.filterItem('Adventure')}>Adventure</button>
<button className='type-button' onClick={() => this.filterItem('Action')}>Action</button>
<button className='type-button' onClick={() => this.filterItem('Youth')}>Youth</button>
</div>
<div className='advanced-types-container'>
<h5>Advanced book type</h5>
<button className='type-button' onClick={() => this.filterItem('Popular')}>Popular</button>
<button className='type-button' onClick={() => this.filterItem('New release')}>New release</button>
</div>
</div>
</div>
<div className="card-group">
<div className="container">
if (!typeButton) {
<div className="row row-cols-3">
{this.state.books.filter((book) => {
return book.bookName.toLowerCase().indexOf(this.state.searchText.toLowerCase()) >= 0
}).map(book => (
<div className="card" style={{ height: 500 + 'px', width: 300 + 'px' }} key={book._id}>
<img src={`http://localhost:3001/${book.bookImage}`} className="card-img-top" alt="comic book coverpage" />
<div className="card-body">
<Link to={`/books/${book._id}`}><h5 className="card-title">{book.bookName}</h5></Link>
<p className="card-text">{book.bookDescription}</p>
</div>
</div>))}
</div>
} else {
<div>
{this.state.books.filter(book => {
return book.type == typeButton;
})}
</div>
}
You have to modify the way state has been updated. State have three properties, you have to modify properties that you are intended to do, remaining properties should be kept untouched.
filterItem = (typeButton) => {
let updatedItems = this.state.books.filter((book) => {
return book.type === typeButton;
});
this.setState((prevState, prevProps) => {
...prevState,
['books']: updatedItems
})
this.setState(this.updatedItems);
}
You can improve the design of this component. If this doesn't solve your problem, Could you please create a sandbox for the same(only frontend).

Setting the state on one item only

How can I reverse or toggle the state only the item clicked. So far all of the tab classes are toggling to active. But I need only the one clicked. I'm teaching myself hooks. so no class component solutions please
const App = () => {
const tabItems = [
{
id:"1",
tabname: "Unsplash",
datatab: "unsplash",
template: <Unsplash/>
},
{
id:"2",
tabname: "YouTube",
datatab: "youtube",
template: <Youtube/>
},
{
id:"3",
tabname: "Wikipedia",
datatab: "wiki",
template: <Wikipedia/>
},
{
id:"4",
tabname: "DropdownApp",
datatab: "dropdownapp",
template: <DropdownApp/>
},
]
const [activeTab, setActiveTab] = useState(false)
const tabs = tabItems.map((tab, i) => {
return (
<span
className={`item ${activeTab ? 'active' : ''}`}
key={tab.id}
data-tab={tab.datatab}
onClick={() => setActiveTab(!activeTab)}>
{tab.tabname}
</span>
)
})
const tabPanels = tabItems.map((tabPanel) => {
return (
<div
key={tabPanel.id}
className={`ui bottom attached tab segment ${activeTab ? 'active' : ''}`}
data-tab={tabPanel.datatab}>
{tabPanel.template}
</div>
)
})
return (
<div className="App">
<div className="ui main text" style={{padding: '20px'}}>
<div className="ui top attached tabular menu">
{tabs}
</div>
{tabPanels}
</div>
</div>
);
}
You are only tracking if the tab are selected or not, not which one, since you have one state for all tabs. You need to track, which tab is selected:
const App = () => {
const tabItems = [
{
id:"1",
tabname: "Unsplash",
datatab: "unsplash",
template: <Unsplash/>
},
{
id:"2",
tabname: "YouTube",
datatab: "youtube",
template: <Youtube/>
},
{
id:"3",
tabname: "Wikipedia",
datatab: "wiki",
template: <Wikipedia/>
},
{
id:"4",
tabname: "DropdownApp",
datatab: "dropdownapp",
template: <DropdownApp/>
},
]
const [activeTab, setActiveTab] = useState("") // Track the id
const tabs = tabItems.map((tab, i) => {
return (
<span
className={`item ${activeTab === tab.id ? 'active' : ''}`} // Check if the tab ids are the same
key={tab.id}
data-tab={tab.datatab}
onClick={() => setActiveTab(tab.id)}> // Save the id instead of a boolean
{tab.tabname}
</span>
)
})
const tabPanels = tabItems.map((tabPanel) => {
return (
<div
key={tabPanel.id}
className={`ui bottom attached tab segment ${activeTab === tab.id ? 'active' : ''}`}
data-tab={tabPanel.datatab}>
{tabPanel.template}
</div>
)
})
return (
<div className="App">
<div className="ui main text" style={{padding: '20px'}}>
<div className="ui top attached tabular menu">
{tabs}
</div>
{tabPanels}
</div>
</div>
);
}
If you want to unselect a tab, you would need to modify the onClick:
setTab = (id) => {
setActiveTab(tab => tab === id ? "" : id);
}
and invoke it with:
<span
onClick={() => setTab(tab.id)}>

How to toggle to show and hide in elements in Reactjs?

how to expand the elements present inside li . I used toggle on selected
I tried to add a default propertied selected false then onClick of the li I ensures if the values matches then selected should be true and displays that particular section ,and onClick of same it should collapse
Is it possible to open always the first dropdown remaining should be closed and when clicked other it open another li dropdown.
Currently onClick is not working
Here is my code
Demo
export default function App() {
const filterAddition = X.map((item) => ({
...item,
menus: item.menus.map((items) => ({
...items,
selected: false
}))
}));
const handleOnClick = (event) => {
filterAddition.map((item) => {
item.menus.map((items) => {
if (items.El=== event.target.value) {
return {
...items,
selected: !item.selected
};
}
});
});
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<nav className="animated bounceInDown">
{filterAddition.map(({ menus }, idx) => {
return menus.map(({ El, subSection, selected }, idx) => (
<ul key={idx}>
<li className="sub-menu" value={El} onClick={handleOnClick}>
<a href="#settings">
{El}
<div class="fa fa-caret-down right"></div>
</a>
{selected && (
<ul>
{subSection.map(({ E }, i) => (
<li key={E}>
{E}
</li>
))}
</ul>
)}
</li>
</ul>
));
})}
</nav>
</div>
);
}
So much to change.
First, you have to use useState to make component reactive when data is change.
Second, onClick have to change.
Third, try to read documentation especially about react hooks, state and lifecycle.
see this https://codesandbox.io/s/pensive-cloud-9mco1?file=/src/App.js
import React, { useState, useEffect } from "react";
import "./styles.css";
const X = [
{
detail1: "FirstJob",
menus: [
{
Order: 1,
El: " Record Management",
subSection: [
{
E: "Check Notification",
Order: "CheckNotification"
},
{
E: "Check Record",
Order: "CheckRecord"
}
]
},
{
Order: 2,
El: "Management",
subSection: [
{
E: "Notification",
Order: "Notification"
},
{
E: "Check",
Order: "Check"
}
]
}
]
}
];
export default function App() {
const [state, setState] = useState(X);
useEffect(() => {
const filterAddition = X.map((item) => ({
...item,
menus: item.menus.map((items) => ({
...items,
selected: false
}))
}));
setState(filterAddition);
}, []);
const handleOnClick = (label) => {
const temp = JSON.parse(JSON.stringify(state));
for(let i in temp) {
const menus = temp[i].menus;
for(let j in menus) {
const item = temp[i].menus[j];
if(item.El === label) {
item.selected = !item.selected;
}
}
}
setState(temp);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<nav className="animated bounceInDown">
{state.map(({ menus }, idx) => {
return menus.map(({ El, subSection, selected }, idx) => (
<ul key={idx}>
<li className="sub-menu" value={El} onClick={() => handleOnClick(El)}>
<a href="#settings">
{El}
<div className="fa fa-caret-down right"></div>
</a>
{selected && (
<ul>
{subSection.map(({ E }, i) => (
<li key={E}>
{E}
</li>
))}
</ul>
)}
</li>
</ul>
));
})}
</nav>
</div>
);
}

Creating a custom Modal for Material-Table

I am trying to create a custom button on my Material-Table that will pop up a model that will allow users to make changes. The issue I am running into is the button is not toggling to true when clicked upon. I am using a custom Modal hook. The modal itself work, because when it is explicitly set to true it pops up on the screen
here is the modal hook
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
function toggle() {
setIsShowing(!isShowing);
}
return {
isShowing,
toggle,
}
};
the modal component
const Modal = ({ isShowing, hide }) => isShowing ? ReactDOM.createPortal(
<React.Fragment>
<div className="modal-overlay"/>
<div className="modal-wrapper" aria-modal aria-hidden tabIndex={-1} role="dialog">
<div className="modal">
<div className="modal-header">
<button type="button" className="modal-close-button" data-dismiss="modal" aria-label="Close" onClick={hide}>
<span aria-hidden="true">×</span>
</button>
</div>
<p>
Hello, I'm a modal.
</p>
</div>
</div>
</React.Fragment>, document.body
) : null;
and finally the actual table component
columns: [
{ title: "Department", field: "department" },
{ title: "Security", field: "security", type: "numeric" },
{ title: "Status", field: "status" },
{
title: "Actions",
field: "actions",
render: rowData => (
<div className="edit-button">
<span className={" fas fa-pencil-alt"} onClick={toggle} />
{console.log(isShowing)}
<Modal isShowing={isShowing} hide={toggle} />
</div>
)
}
],
data: [{ department: "Admin", security: 10, status: "Active" }]
});
<MaterialTable
title="Departments"
columns={state.columns}
data={state.data}
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
})
}}
/>
I need the isShowing to toggle between true and false

Update list of displayed components on deletion in React

in the beginning on my path with React I'm creating simple to-do app where user can add/remove task which are basically separate components.
I create tasks using:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
I render list of components (tasks) using following method:
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask = {(id) => this.removeTask(id)}
key = {index}/>;
})
);
}
method to remove specific task takes unique ID of task as an argument and based on this ID I remove it from the tasks list:
removeTask(uID){
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != uID )
}));
}
But the problem is, when I delete any item but the last one, it seems like the actual list of components is the same only different objects are passed to those components.
For example:
Lets imagine I have 2 created componentes, if I set state.Name = 'Foo' on the first one, and state.Name='Bar' on the second one. If I click on remove button on the first one, the object associated to this component is removed, the second one becomes first but it's state.Name is now 'Foo' instead of 'Bar'.
I think I'm missing something there with correct creation/removing/displaying components in react.
Edit:
Method used to remove clicked component:
removeCurrentTask(){
this.props.removeTask(this.props.taskObj.id);
}
SingleTask component:
class SingleTask extends Component{
constructor(props) {
super(props);
this.state={
showMenu : false,
afterInit : false,
id: Math.random()*100
}
this.toggleMenu = this.toggleMenu.bind(this);
}
toggleMenu(){
this.setState({showMenu : !this.state.showMenu, afterInit : true});
}
render(){
return(
<MDBRow>
<MDBCard className="singleTaskContainer">
<MDBCardTitle>
<div class="priorityBadge">
</div>
</MDBCardTitle>
<MDBCardBody className="singleTaskBody">
<div className="singleTaskMenuContainer">
<a href="#" onClick={this.toggleMenu}>
<i className="align-middle material-icons">menu</i>
</a>
<div className={classNames('singleTaskMenuButtonsContainer animated',
{'show fadeInRight' : this.state.showMenu},
{'hideElement' : !this.state.showMenu},
{'fadeOutLeft' : !this.state.showMenu && this.state.afterInit})}>
<a
title="Remove task"
onClick={this.props.removeTask.bind(null, this.props.taskObj.id)}
className={
classNames(
'float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightRed'
)
}
>
<i className="align-middle material-icons">remove</i>
</a>
<a title="Edit title"
className={classNames('show float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightBlue')}
>
<i className="align-middle material-icons">edit</i>
</a>
</div>
</div>
{this.props.taskObj.description}
<br/>
{this.state.id}
</MDBCardBody>
</MDBCard>
</MDBRow>
);
}
}
Below visual representation of error, image on the left is pre-deletion and on the right is post-deletion. While card with "22" was deleted the component itself wasn't deleted, only another object was passed to it.
Just to clarify, the solution was simpler than expected.
In
const showTasks = () => taskList.map((item, index) => (
<SingleTask
taskObj={item}
removeTask ={removeTask}
key = {item.id}
/>
)
)
I was passing map index as a key, when I changed it to {item.id} everything works as expected.
In short, in the statement tasksList.push(<SingleTask taskObj={taskObj} removeTask ={this.removeTask}/>);, removeTask = {this.removeTask} should become removeTask = {() => this.removeTask(taskObj.id)}.
However, I would reconsider the way the methods addTask and showTasks are written. While the way you have written isn't wrong, it is semantically unsound. Here's what I would do:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
const SingleTask = (task) => {
const { taskObj } = task;
return <div onClick={task.removeTask}>
{ taskObj.title }
</div>
}
// Example class component
class App extends React.Component {
state = {
tasksList: [
{ id: 1, title: "One" },
{ id: 2, title: "Two" },
{ id: 3, title: "Three" },
{ id: 4, title: "Four" }
]
}
addTask = (taskObj) => {
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks = () => {
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
key={index}
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
removeTask(id) {
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != id )
}));
}
render() {
return (
<div className="App">
<div> {this.showTasks()} </div>
</div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.body
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Resources