I'm trying to get the structure below.
https://imgur.com/NBRGlhM
Three columns, with titles, and column cards that can be moved within only one column. this.state.item ['lists'] moving to the component SortableList. Then iterates after items.map ((item, index) => and moves to the component SortableItem. Then iterates aftervalue.listItems and wants to display the title of columns and cards in the column. I get the error:
Cannot read property ' indexOf 'of undefined
Demo here: https://stackblitz.com/edit/react-jpszoq
import {SortableContainer, SortableElement} from 'react-sortable-hoc';
import arrayMove from 'array-move';
const SortableItem = SortableElement(({value}) => {
return(
value.listItems.map((it, index) => {
<li>{it.title}</li>
})
)
})
const SortableList = SortableContainer(({items}) => {
console.log(items)
return (
<ul>
{
items.map((item, index) =>
<div>{item.name}
<SortableItem key={`item-${item.id}`} index={index} value={item} />
</div>
)
}
</ul>
);
});
class App extends Component {
constructor() {
super();
this.state = {
item: {
id: "abc123",
name: "AAA",
lists: [
{
id: "def456",
list_id: "654wer",
title: 'List1',
desc: "description",
listItems: [
{
id: "ghj678",
title: "ListItems1",
listItemsId: "88abf1"
},
{
id: "poi098",
title: "ListItems2",
listItemsId: "2a49f25"
}
]
},
{
id: "1ef456",
list_id: "654wer",
title: 'List 2',
desc: "description",
listItems: [
{
id: "1hj678",
title: "ListItems3",
listItemsId: "18abf1"
},
{
id: "1oi098",
title: "ListItems4",
listItemsId: "1a49f25"
}
]
},
{
id: "2ef456",
title: 'List 3',
list_id: "254wer",
desc: "description",
listItems: [
{
id: "2hj678",
title: "ListItems5",
listItemsId: "28abf1"
},
{
id: "2oi098",
title: "ListItems6",
listItemsId: "234a49f25"
}
]
}
]
}
};
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState(({lists}) => ({
lists: arrayMove(lists, oldIndex, newIndex),
}));
};
render() {
return <SortableList items={this.state.item['lists']} onSortEnd={this.onSortEnd} />;
}
}
you're missing a return on L14 in the function passed to listItems.map. Alternatively, you could remove the braces.
Related
Hi I am developing a Table components where I need to populate the data in table body dynamically. I am maintaining the bodyData in arrayOf:[{}]. Cells will be created dynamically based on the values in the mock data. I need to pass four different data in body from mockdata. Now all are getting passed as children. Now I need to display one of the data in table cell in badge format. For this I am trying to pass multiple children so that I can render one of the children in badge format. But When I am passing more than one children component is rendering last children value only. Could any one help me how to pass (Brand value in the table in form of badge). Thanks in advance. I have wrote down the working code and mockdata.
import mockData from './mock-data/';
const createCell = cell => ({ key: cell.key, children: cell.children});
//tried to pass like cell => ({ key: cell.key, children: cell.customerName,
//children:cell.customerAddr})
const createCellsForRow = cells => cells.map(cell => createCell(cell));
const StTable = () => {
const [selectedKey, setSelectedKey] = useState([]);
const handleRowToggle = (event, metaData) => {
event.preventDefault();
if (selectedKey !== metaData.key) {
setSelectedKey(metaData.key);
}
};
const createRow = rowData => (
{
key: rowData.key,
cells: createCellsForRow(rowData.cells),
toggleAction: {
metaData: { key: rowData.key },
onToggle: handleRowToggle,
isToggled: selectedKey === rowData.key,
toggleLabel: rowData.toggleText,
},
}
);
const createRows = data => data.map(childItem => createRow(childItem));
return (
<Table
summaryId="example-single-select"
summary="This table shows an implementation of single row selection."
numberOfColumns={4}
cellPaddingStyle="standard"
rowStyle="toggle"
dividerStyle="horizontal"
headerData={{
selectAllColumn: {
checkLabel: 'Single Selection',
},
cells: [
{ key: 'cell-0', id: 'toggle-0', children: 'Name' },
{ key: 'cell-1', id: 'toggle-1', children: 'Address' },
{ key: 'cell-2', id: 'toggle-2', children: 'Phone Number' },
{ key: 'cell-3', id: 'toggle-3', children: 'Brand' },//I need to pass this is as
//badge format.
],
}}
bodyData={[
{
rows: createRows(mockData),
},
]}
/>
);
};
export default StTable;
//mockData Sample
[
{
"key":"row-0",
toggleText:"txt",
"cells":[
{ key: 'cell-0', id: 'toggle-0', children: 'ABC' },
{ key: 'cell-1', id: 'toggle-1', children: 'ABC123' },
{ key: 'cell-2', id: 'toggle-2', children: 'P1234567890' },
{ key: 'cell-3', id: 'toggle-3', children: ['A', 'B'] },
]
]
//I am trying to change the structure because (I need to display the brand in badge format)
[
{
"key":"row-0",
toggleText:"txt",
"cells":[
{ key: 'cell-0', id: 'toggle-0', customerName: 'ABC' },
{ key: 'cell-1', id: 'toggle-1', customerAddr: 'ABC123' },
{ key: 'cell-2', id: 'toggle-2', customerPhNo: 'P1234567890' },
{ key: 'cell-3', id: 'toggle-3', customerBrand: ['A', 'B'] },
]
]
//Table Component
const createCell = cell => ({ key: cell.key, children: cell.title });
const createCellsForRow = cells => cells.map(cell => createCell(cell));
const Table = () => {
const [selectedKey, setSelectedKey] = useState([]);
const handleRowToggle = (event, metaData) => {
event.preventDefault();
if (selectedKey !== metaData.key) {
setSelectedKey(metaData.key);
}
};
const createRow = rowData => (
{
key: rowData.key,
cells: createCellsForRow(rowData.cells),
toggleAction: {
metaData: { key: rowData.key },
onToggle: handleRowToggle,
isToggled: selectedKey === rowData.key,
toggleLabel: rowData.toggleText,
},
}
);
const createRows = data => data.map(childItem => createRow(childItem));
return (
<Table
numberOfColumns={4}
cellPaddingStyle="standard"
rowStyle="toggle"
dividerStyle="horizontal"
headerData={{
selectAllColumn: {
checkLabel: 'Single Selection',
},
cells: [
{ key: 'cell-0', id: 'toggle-0', children: 'Name' },
{ key: 'cell-1', id: 'toggle-1', children: 'Address' },
{ key: 'cell-2', id: 'toggle-2', children: 'Phone Number' },
{ key: 'cell-3', id: 'toggle-3', children: 'Email Id' },
],
}}
bodyData={[
{
rows: createRows(mockData),
},
]}
/>
);
};
export default Table;
I am trying to split up an array of components into individual arrays depending on if there is a list breaking the pattern of heading, paragraph or hr. Here is what i have tried:
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
const richText = [];
const list = [];
components.forEach((component, index) => {
switch (component.name) {
case 'heading': case 'hr': case 'paragraph':
richText.push(component.name)
break
case 'list':
list.push(`${component.name}-index`)
break
}
})
console.log(richText)
Current output is:
[
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: ‘hr’ },
{ name: 'paragraph' }
]
Desired output is:
[
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' }
],
[
{ name: ‘hr’ },
{ name: 'paragraph' }
]
Considering list as a break, you can make initialization for richText as [[]]. You can keep pushing to last index of richText until a list is found. If a list is found push an empty array [] to your richText array.
Working Fiddle
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
const richText = [[]];
const list = [];
components.forEach((component, index) => {
switch (component.name) {
case 'heading': case 'hr': case 'paragraph':
richText[richText.length - 1].push(component);
break
case 'list':
list.push({name: `${component.name}-${richText.length}`})
index < components.length - 1 ? richText.push([]) : {};
break
}
})
console.log(richText);
console.log(list);
Same logic Array.reduce implementation
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
const richText = components.reduce((acc, curr, index) => {
if(curr.name === 'list' && index < components.length - 1) {
acc.push([]);
} else {
acc[acc.length - 1].push(curr);
}
return acc;
}, [[]]);
console.log(richText);
How about
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
let richText = [];
let list = [];
components.forEach((component, index) => {
switch (component.name) {
case 'heading': case 'hr': case 'paragraph':
richText.push({'name': component.name})
break
case 'list':
list.push(richText);
richText = [];
break
}
})
console.log(list);
Here's a generic function akin to python's groupby. Given an iterable and a key function, it yields pairs [key, chunk] where each chunk contains sequential items that have the same key:
function* groupBy(iter, keyFn) {
let last = null, buf = []
for (let item of iter) {
let key = keyFn(item)
if (key !== last) {
if (buf.length)
yield [last, buf]
buf = []
}
buf.push(item)
last = key
}
yield [last, buf]
}
Applied to your problem:
for (let [isList, tags] of groupBy(components, c => c.name === 'list'))
if (isList) ... else ...
index.tsx
data = {
room: [
{
id: 1,
name: 'room1'
},
{
id: 2,
name: 'room2'
},
{
id: 3,
name: 'room3'
}
],
student: [
{
id: 1,
room: 'room1',
name: 'josh'
},
{
id: 2,
room: 'room1',
name: 'jake'
}
]
}
const _ROOM = data['room'];
const _STUDENT = data['student'];
const form = {
config: [
{
label: "Room",
name: "room",
type: "select",
rule: yup.string().required(),
renderer: (data: any) => {
const { control, register, errors } = useFormContext();
return (
<SelectPicker
placeholder="Room"
data={
_ROOM && _ROOM.length > 0 ?
_ROOM.map(x => ({ label: x.name, value: x.id })) : []
}
style={{ width: '100%' }}
onChange={(val) => {
control.setValue('room', val);
}}
value={control.getValues()['room']}
/>
);
}
},
{
label: "Student",
name: "student",
type: "select",
rule: yup.string().required(),
renderer: (data: any) => {
const { control, register, errors } = useFormContext();
return (
<SelectPicker
placeholder="Student"
data={
_STUDENT && _STUDENT.length > 0 ?
_STUDENT.map(x => ({ label: x.name, value: x.id })) : []
}
style={{ width: '100%' }}
onChange={(val) => control.setValue('student', val)}
value={control.getValues()['student']}
/>
);
}
}]}
How to filter the student based on the room. for example I select the room1 then on the student it will filter which it has value room1. I try to filter inside the onchange in room but it doesn't work also not filtering or display the console log. also I used the state then set inside the onChange but it doesn't work also.
Take a look at this: https://react-hook-form.com/advanced-usage/#ConditionalControlledComponent
Basically, you can watch for changes in room and filter out the options in the student field.
const {room} = watch();
And in the SelectPicker, modify the data prop to:
data={
_STUDENT && _STUDENT.length > 0 ?
_STUDENT.filter(s => s.room === room).map(x => ({ label: x.name, value: x.id })) : []
}
I'm a learner developer, and I'm build a app with a tree menu(react + redux + sagas), but I'm getting some errors of Mutation State, I saw what best practices is stay de state flat as possible, but I didn't finded one menu tree what work with a flat state, so my data is look this:
menuTree: [{
id: 'id-root',
name: 'root',
toggled: true,
children: [
{
id: 'id-parent1',
name: 'parent1',
toggled: true,
children: [
{
id: '123',
name: 'parent1_child1'
},
{
id: '234',
name: 'parent1_child2'
}
]
},
{
id: 'id-loading-parent',
name: 'loading parent',
loading: true,
children: []
},
{
id: 'id-parent2',
name: 'parent2',
toggled: true,
children: [
{
id: 'parent2_children1',
name: 'nested parent2',
children: [
{
id: '345',
name: 'parent2 child 1 nested child 1'
},
{
id: '456',
name: 'parent2 child 1 nested child 2'
}
]
}
]
}
]
}],
And my redux action:
case types.SOLUTION__MENUCURSOR__SET:
// console.log('action payload', action.payload);
// console.log('state', state);
const cursor = action.payload.cursor;
// console.log('set menu cursor action', cursor);
return {
...state,
menuTree: state.menuTree.map(
function buscaIdMenuTree(currentValue, index, arr){
if(currentValue.id){
if(currentValue.id.includes(cursor.id)){
currentValue.toggled = action.payload.toggled;
return arr;
}else{
if(currentValue.children)
{
currentValue.children.forEach(function(currentValue, index, arr){
return buscaIdMenuTree(currentValue, index, arr);
});
}
}
return arr;
}
}
)[0]
};
The code works but I get Mutation State Error, so someone can help me to fix it ?
You can rebuild your menu as a plain list:
let menuTree = [{
id: 'id-root',
name: 'root',
toggled: true,
parent: null
},{
id: 'id-parent1',
name: 'parent1',
toggled: true,
parent: 'id-root'
},{
id: '123',
name: 'parent1_child1',
parent: 'id-parent1'
},{
id: '234',
name: 'parent1_child1',
parent: 'id-parent1'
},
{
id: 'id-loading-parent',
name: 'loading parent',
loading: true,
parent: 'id-root'
},{
id: 'id-parent2',
name: 'parent2',
toggled: true,
parent: 'id-root'
},{
id: 'parent2_children1',
name: 'nested parent2',
parent: 'id-parent2'
},{
id: '345',
name: 'parent2 child 1 nested child 1',
parent: 'parent2_children1'
},
{
id: '456',
name: 'parent2 child 1 nested child 2',
parent: 'parent2_children1'
}]
then if your menu renderer require a tree you can convert the list to a tree so inside the component renderer this.menuTree will be a tree:
const buildTree = (tree, cParent = null) => {
return tree.filter(cNode => cNode.parent == cParent).reduce((curr, next) => {
let cNode = {...next, children: buildTree(tree, next.id)}
delete cNode.parent
return [...curr, cNode]
}, [])
}
function mapStateToProps(state) {
return {
mapTree: builTree(state.mapTree)
};
}
export default connect(mapStateToProps)(YourComponent);
Inside the mutation now you just need to create a list of node that needs to be toggled and then map the state accordingly
case types.SOLUTION__MENUCURSOR__SET:
// console.log('action payload', action.payload);
// console.log('state', state);
const cursor = action.payload.cursor;
// console.log('set menu cursor action', cursor);
const getToggleList = (tree, cursor) => {
let target = tree.find(cNode => cNode.id == cursor.id)
if(target.parent != null){
let parent = tree.find(cNode => cNode.id == target.parent)
return [target.parent, ...getToggleList(tree, parent)]
}else{
return []
}
}
let toggleList = [cursor.id, ...getToggleList(state.menuTree, cursor.id)]
return {
...state,
menuTree: state.menuTree.map(node => ({...node, toggle: toggleList.includes(node.id)}))
};
I'm trying to use filter to remove an object from the array. When I click on the recently added item it will console.log the items id but doesn't remove the item from the array, not sure where I'm going wrong?
import React, { Component } from 'react'
import { reduxForm } from 'redux-form'
// import Input from '../forms/Input/Input'
import actions from './actions'
import { connect } from 'react-redux'
import styles from './Catalogue.styl'
// import Checklist from './Checklist/Checklist'
#reduxForm({
form: 'orderReview',
})
#connect(null, actions)
export default class Catalogue extends Component {
constructor() {
super()
this.state = {
filterText: '',
favourites: [],
data: [
{ id: 1, label: 'baguettes' },
{ id: 2, label: 'bread' },
{ id: 3, label: 'potatoes' },
{ id: 4, label: 'rice' },
{ id: 5, label: 'pasta' },
{ id: 6, label: 'chicken' },
{ id: 7, label: 'beans' },
{ id: 8, label: 'apples' },
{ id: 9, label: 'oranges' },
{ id: 10, label: 'grapes' },
{ id: 11, label: 'biscuits' },
],
}
}
handleFilterUpdate = event => {
this.setState({
filterText: event.target.value,
})
}
addFavourite = (id) => {
const favList = this.state.favourites.concat([id])
this.setState({
favourites: favList,
})
console.log(id)
}
removeFavourite = (id) => {
console.log(id)
const removeFav = this.state.favourites.filter((_, i) => i !== id)
this.setState({
favourites: removeFav,
})
}
render() {
const {
data,
filterText,
favourites,
} = this.state
const NamesList = props => (
<div>
{props.data.filter(items => {
return items.label.toLowerCase().indexOf(filterText.toLowerCase()) >= 0
})
.map(item => {
return (
<div
key={item.id}
onClick={() => props.addFavourite(item.id)}
>
{item.label}
</div>
)
}
)
}
</div>
)
const SaveName = props => {
const idList = props.favourites.map(id => {
const { label } = data[id]
return (
<div>
<br />
<li key={id} onClick={() => props.removeFavourite(data[id])}>{label}</li>
</div>
)
})
return (
<div>{idList}</div>
)
}
return (
<div>
<div className={styles.filtersList}>
<ul className={styles.filtersUl}>
<li className={styles.filtersLi}>znacky</li>
<li className={styles.filtersLi}>zeme</li>
<li className={styles.filtersLi}>Specialni</li>
</ul>
</div>
<input
type="text"
value={filterText}
onChange={this.handleFilterUpdate}
placeholder="Hledat podle nazvu"
/>
<NamesList data={data} addFavourite={this.addFavourite}/>
{filterText}
<SaveName favourites={favourites} removeFavourite={this.removeFavourite} />
</div>
)
}
}
You are iterating through your entire array, and you compare the parameter id with the index of the currently processed item of the array.
Instead, compare the item.id with the parameter, not with i:
class MyApp extends React.Component {
constructor() {
super()
this.state = {
favourites: [
{ id: 1, label: 'baguettes' },
{ id: 2, label: 'bread' },
{ id: 3, label: 'potatoes' },
{ id: 4, label: 'rice' },
{ id: 5, label: 'pasta' },
{ id: 6, label: 'chicken' },
{ id: 7, label: 'beans' },
{ id: 8, label: 'apples' },
{ id: 9, label: 'oranges' },
{ id: 10, label: 'grapes' },
{ id: 11, label: 'biscuits' },
],
}
}
removeFavourite = (id) => {
const removeFav = this.state.favourites.slice();
removeFav.splice(id, 1);
this.setState({
favourites: removeFav
})
}
render() {
return(
<ul>
{this.state.favourites.map((item, i) => <li key={item.id}>{item.label} <button onClick={this.removeFavourite.bind(this, i)}>Remove</button></li>)}
</ul>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("myApp"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="myApp"></div>
Alternatively, you could also just use splice() to remove the item directly:
class MyApp extends React.Component {
constructor() {
super()
this.state = {
favourites: [
{ id: 1, label: 'baguettes' },
{ id: 2, label: 'bread' },
{ id: 3, label: 'potatoes' },
{ id: 4, label: 'rice' },
{ id: 5, label: 'pasta' },
{ id: 6, label: 'chicken' },
{ id: 7, label: 'beans' },
{ id: 8, label: 'apples' },
{ id: 9, label: 'oranges' },
{ id: 10, label: 'grapes' },
{ id: 11, label: 'biscuits' },
],
}
}
removeFavourite = (id) => {
const removeFav = this.state.favourites.filter(item => item.id-1 != id)
this.setState({
favourites: removeFav
})
}
render() {
return(
<ul>
{this.state.favourites.map((item, i) => <li key={item.id}>{item.label} <button onClick={this.removeFavourite.bind(this, i)}>Remove</button></li>)}
</ul>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("myApp"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="myApp"></div>