I have a component to display data on a material-ui table called UserRow.
This component is used on another component called users.
But in order to display the data properly in each field the only way I found was to create a conditional rendering, so i could just render the data that i wanted in each field, otherwise it would be duplicated.
Is there a way to just call once the <UserRow/> component and get the same results as i get in the image bellow?
UserRow:
export default function UserRow( props, {name, details}) {
const style = styles();
function UserName(props) {
const showUserRow = props.showUserRow;
if (showUserRow) {
return <ListItem>
<ListItemIcon >
<PeopleIcon className={style.iconColor}/>
</ListItemIcon>
<ListItemText className={style.text}>{props.userRow}</ListItemText>
</ListItem>
}
return<div></div>;
}
function IconDetails(props) {
const showDetailsRow = props.showDetailsRow;
if (showDetailsRow) {
return <Link to={`/users/${props.detailsRow}`}>
<ListItemIcon >
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>;
}
return<div></div>;
}
return (
<List>
<ListItem>
<UserName userRow={props.name} showUserRow={props.showUserRow}/>
<IconDetails detailsRow={props.details} showDetailsRow={props.showDetailsRow}/>
</ListItem>
</List>
)
}
users:
export default function User({ data }) {
const style = styles();
const userList = data.map((row) => {
return { name: row, details: row };
});
const [state] = React.useState({
users: [
...userList,
]
});
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<UserRow showUserRow={true} showDetailsRow={false} name={rowData.name} />
)
},
{
title: "Details",
field: "details",
render: rowData => (
<UserRow showUserRow={false} showDetailsRow={true} details={rowData.details} />
)
},
]}
data={state.users}
options={{
search: true
}}
/>
</div>
)
}
What i had before:
UserRow:
export default function UserRow( props, {name, details}) {
const style = styles();
return (
<List>
<ListItem>
<ListItemIcon >
<PeopleIcon className={style.color}/>
</ListItemIcon>
<ListItemText className={style.text}>{name}</ListItemText>
<Link to={`/users/${details}`}>
<ListItemIcon >
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>
</ListItem>
</List>
)
}
users:
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<UserRow name={rowData.name} details={rowData.details} />
)
},
{
title: "Details",
},
]}
data={state.users}
options={{
search: true
}}
/>
</div>
)
}
The problem here, in the previous solution, is that if we create a title Details, the material-ui table creates a div for the details and I can't place my icon there, and this would be a problem if i had more data and need to place the data in the respective position.
What i was trying to achieve with the previous solution was to cut down some code, because if i have many fields i will repeat too much code.
Link that might be useful: https://material-table.com/#/docs/get-started
Related
How do I access the dataProvider directly in react-admin? I tried to use a custom dataProvider but I do not know how to use it. My goal is to show a list in the output using the map.
//Contacts.js
/// --- List ---
export const ContactList = (props) => {
const classes = useStyles();
const {data} = useGetList('contacts', props.id);
return (
<List className={classes.list}>
{data.map(({id, name, person}) => (
<React.Fragment key={id}>
<ListItem button>
<ListItemAvatar>
<Avatar alt="Profile Picture" src={person}/>
</ListItemAvatar>
<ListItemText primary={name}/>
</ListItem>
</React.Fragment>
))}
</List>
)
};
//App.js
const App = () => (
<Admin dataProvider={dataProvider} layout={MyLayout}>
<Resource
name="contacts"
list={ContactList}
show={ShowContact}
edit={EditGuesser}
create={CreateContact}
icon={ContactPhoneIcon}/>
</Admin>
);
export default App;
//DataProvider.js
import fakeDataProvider from 'ra-data-fakerest';
const dataProvider = fakeDataProvider({
contacts: [
{
id: 1,
name: "Tom",
numbers: {number: '09367371111', type: 'Mobile'},
person: '/static/images/avatar/5.jpg',
},
{
id: 2,
name: "Sara",
numbers: {number: '0936737999', type: 'Home'},
person: '/static/images/avatar/1.jpg',
},
],
});
export default dataProvider;
Console.log
You must create a custom list renderer, alternative to <Datagrid>, and pass it as child of <List>:
export const ContactList = (props) => {
const classes = useStyles();
return (
<List className={classes.list}>
<ContactSimpleList /<
</List>
)
};
The <List> component creates a ListContext, and places the ids, data and total in it. So you can access this data through the context in the list renderer:
import { useListContext } from 'react-admin';
const ContactSimpleList = () => {
const { ids, data } = useListContext();
return (
<>
{ids.map(id => (
<ListItem key={id} button>
<ListItemAvatar>
<Avatar alt="Profile Picture" src={data[id].person}/>
</ListItemAvatar>
<ListItemText primary={data[id].name}/>
</ListItem>
))}
</>
);
}
Also, the SimpleList you're trying to render looks a lot like a native component provide by react-admin: the <SimpleList> component. You probably don't need to write custom renderer.
I'm trying to implement a horizontal menu with antd components.
When clicking the nav items the submenu is not showing correctly.
Codesandbox demo.
const MenuList = [
{
name: "Navigation two - Submenu",
subMenuRoutes: [
{
name: "A- item1",
url: "/item1Url1"
},
{
name: "A - item2",
url: "/item1Url2"
}
]
},
{
name: "Navigation Three - Submenu",
subMenuRoutes: [
{
name: "B- item1",
url: "/item1Url1"
},
{
name: "B - item2",
url: "/item1Url2"
}
]
}
];
function TextAreaManager() {
const [showMenu, setShowMenu] = useState(false);
return (
<Tabs onTabClick={() => setShowMenu(prev => !prev)}>
{MenuList.map(item => {
return (
<TabPane
tab={
<>
<Icon type="setting" />
{item.name}
<Icon
type={showMenu ? "up" : "down"}
style={{ marginLeft: "10px" }}
/>
</>
}
>
{showMenu && (
<Menu>
{item.subMenuRoutes.map(childItem => {
return (
<Menu.Item key={childItem.url}>{childItem.name}</Menu.Item>
);
})}
</Menu>
)}
</TabPane>
);
})}
</Tabs>
);
There are a few issues that need to be handled:
Assign unique key for every array item in order to render components correctly.
menuList.map(item => <TabPane key={item.name}></TabPane>);
You need to manage every menu's state in order to show menus correctly with the corresponding icon showMenuManager[item.name]:
<Tabs
onTabClick={e =>
setShowMenuManager(prev => {
const newState = { ...initMenuState, [e]: !prev[e] };
console.log(newState);
return newState;
})
}
/>;
const initMenuState = {
"Navigation two - Submenu": false,
"Navigation Three - Submenu": false
};
function TopMenuManager() {
const [showMenuManager, setShowMenuManager] = useState(initMenuState);
return (
<Tabs ... >
{menuList.map(item => (
<TabPane
key={item.name}
tab={
<>
...
<Icon
type={showMenuManager[item.name] ? "up" : "down"}
/>
</>
}
>
{showMenuManager[item.name] && ...}
</TabPane>
))}
</Tabs>
);
}
Check the final example and forked sandbox:
I am trying to develop a generic filter component which can have many fields to filter on like color,
size, price range etc and each field might have different types of elements like color may have
checkboxes, radio button and price range might have input element, dropdown etc. To support such varied
cases, I tried to go with this pattern but here I have to iterate the same things multiple times.
I am not sure of this data structure. If anyone has suggestion please help me to improve this code but
the main problem here is "multiple iteration". How can i improve this code?
const filterParams = {
field: {
id : 1, label : 'Field', content: <FieldFilter />
},
employee: {
id : 1, label : 'Employee', content: <Employee />
}
}
<Filter filterParams={filterParams} activeFilterParam="field" />
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
return (
<>
<Button secondary icon={filter} onClick={() => setShow(!show)}>Filter</Button>
{show && (
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List
render={() => {
return (
Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
)
}))
}} />
<Tabs.Panels>
{Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
)
})}
</Tabs.Panels>
</Tabs>
</Card.Content>
<Card.Footer>
<Button>
<Button.Content style={{ marginRight: 10 }}>Save</Button.Content>
<Button.Content secondary onClick={()=>setShow(!show)}>Cancel</Button.Content>
</Button>
</Card.Footer>
</Card>
)}
</>
)
}
If you're not liking the multiple calls to Object.keys(filterParams).map, you could move the loop to the top of the component function. Something like the below might work:
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
const {tabs, panels} = Object.keys(filterParams)
.reduce((acc, filterParam) => {
acc.tabs.push(
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
);
acc.panels.push(
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
);
return acc;
}, { tabs: [], panels: [] });
return (
...
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List render={() => tabs} />
<Tabs.Panels>
{panels}
</Tabs.Panels>
</Tabs>
...
</Card>
...
)
}
Note that I haven't run this - it likely won't be quite right, but should give the general idea...
I'm need make Collapse menu from array data,
now i'm click any menu but expand all sub menu
I think my problem is i'm can,t set unique state "open" for Main menu
I don't want to be assigned "state"
To support additional data in the future that may have 3 or 4
I'm use React.js, material-ui
please help me
thank you very much
const myData = [
{
id: '1',
nameHeader: 'Header1',
subMenu: [{ id: '1', name: 'subMenu1' }, { id: '2', name: 'subMenu2' }]
},
{
id: '2',
nameHeader: 'Header2',
subMenu: [{ id: '1', name: 'subMenu1' }, { id: '2', name: 'subMenu2' }]
}
]
class Myclass extends Component {
state = { open: false }
handleClick = () => {
this.setState(state => ({ open: !state.open }))
}
render() {
const { open } = this.state
return (
<div style={{ marginRight: '15px' }}>
<List component="nav">
{myData.map(each => (
<React.Fragment key={each.id}>
<ListItem button onClick={this.handleClick}>
<ListItemText inset primary={each.nameHeader} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Divider />
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{each.subMenu.map(subData => (
<ListItem key={subData.id} button>
<ListItemText inset primary={subData.name} />
</ListItem>
))}
</List>
</Collapse>
</React.Fragment>
))}
</List>
</div>
)
}
}
export default Myclass
I think my problem is i'm can,t set unique state "open" for Main menu
Somehow, yes. You have two different (sub) menus you like to collapse / expand without having an effect on the other menu. This in mind, you need to create a possibility to save the current 'open' state for each of the menus separately.
You already have a unique id for your different menus, you can use them in order to achieve your goal. One way would be to extend your state with the related settings for your menus:
state = { settings: [{ id: "1", open: false }, { id: "2", open: false }] };
This allows you to have the information about the collapsed status of each of your menus.
According to that you need to extend your handleClick function a bit in order to only change the state of the menu item you clicked:
handleClick = id => {
this.setState(state => ({
...state,
settings: state.settings.map(item =>
item.id === id ? { ...item, open: !item.open } : item
)
}));
};
And in your render function you need to make sure that you pass the right id of the menu item you clicked to your handleClick function and that you select the right open state.
<React.Fragment key={each.id}>
<ListItem button onClick={() => this.handleClick(each.id)}>
<ListItemText inset primary={each.nameHeader} />
settings.find(item => item.id === each.id).open
? "expanded"
: "collapsed"}
</ListItem>
<Divider />
<Collapse
in={settings.find(item => item.id === each.id).open}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{each.subMenu.map(subData => (
<ListItem key={subData.id} button>
<ListItemText inset primary={subData.name} />
</ListItem>
))}
</List>
</Collapse>
</React.Fragment>
See it working here: https://codesandbox.io/s/6xjz837j9z
I have 2 files, like in the example:
player.js - for now, I use only one item in the object
const players = {
playerId: '5555',
playerName: 'JHON',
playerTeams: [real, barcelona, liverpol],
};
Team.js
const Teams = [
{ name: 'real', teamImageSrc: '' },
{ name: 'barcelona', teamImageSrc: '' },
{ name: 'liverpol', teamImageSrc: '' },
];
My purpose at this stage of the project is to check that my UI code display the data well, and later I will correct the logic behind the UI, and right now what I'm trying to do is to display the list of teams the player is member, This task that I have managed to do using map () but I need to locate the image of each value I got from the map () like in the example below:
class GridPlayerTeamsMembership extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<List>
{players.playerTeams.map(value => (
<ListItem dense button className={classes.listItem}>
<Avatar ? />
<ListItemText primary={` ${value}`} />
</ListItem>
))}
</List>
</div>
);
}
}
GridPlayerTeamsMembership.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(GridPlayerTeamsMembership);
You can make a function like this:
teamImage = (teamName) => Teams.find(t=>t.name===teamName).teamImageSrc
And use the teamImage like this:
<Avatar src={this.teamImage(value)} />
ok, I succeeded but without the this.
class GridPlayerTeamsMembership extends React.Component {
render() {
const { classes } = this.props;
teamImage = (teamName) => Teams.find(t=>t.name===teamName).teamImageSrc
return (
<div className={classes.root}>
<List>
{players.playerTeams.map(value => (
<ListItem key={value} dense button className={classes.listItem}>
<img src={teamImage(value)} className={classes.img} />
<ListItemText primary={` ${value}`} />
</ListItem>
))}
</List>
</div>
);
}
}
GridPlayerTeamsMembership.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(GridPlayerTeamsMembership);