How do I access the dataProvider directly in react-admin? - javascript

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.

Related

How to create a reusable component for large number of Buttons that open a Form

library used: mui 5.4.1
To create a TableCell containing an IconButton that opens a Form, the code is written as follows.
const data = [
{
id: "001",
name: "A",
price: 2000
},
{ id: "002", name: "B", price: 100 },
...
];
const SandboxTable = () => {
return (
<ThemeProvider theme={muiTheme}>
<TableContainer>
<Table>
...
{data.map((datum) => (
<TableRow key={datum.id}>
<TableCell>{datum.id}</TableCell>
<TableCell>{datum.name}</TableCell>
<TableCell>{datum.price}</TableCell>
<ApproveFormButtonCell
toolTip={"approve"}
id = {datum.id}
IconComponent={<CheckCircleOutlineIcon color={"success"} />}
/>
<RejectFormButtonCell
toolTip={"Reject"}
name = {datum.name}
IconComponent={<CancelOutlinedIcon color="error" />}
/>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</ThemeProvider>
);
};
const ApproveFormButtonCell = ({ toolTip, IconComponent }) => {
const usePopoverProps = usePopover();
return (
<TableCell>
<IconButtonWithTooltip
toolTip={toolTip}
onClick={usePopoverProps.handleOpen}
IconComponent={IconComponent}
/>
<BasePopover usePopverProps={usePopoverProps}>
<ApproveForm id={id} handleClose={usePopoverProps.handleClose} />
</BasePopover>
</TableCell>
);
};
Then I have to create a new ButtonCell component every time a new button is added.
To avoid this, when ApproveForm is used in SandboxTable, handleClose could not be received.
<ApproveFormButtonCell
toolTip={"approve"}
id = {datum.id}
IconComponent={<CheckCircleOutlineIcon color={"success"} />}
/>
<ApproveForm handleClose={??}/>
</ApproveFormButtonCell>
I need a lot of buttons open form.
Any good answers to solve this situation?
codesandbox
To keep from making a one-off of every sort and type of form button cell component you can abstract the common behavior/UI, i.e. the IconButtonWithTooltip and BasePopover components, into a generic component and pass in as a prop the content that is different. In this case it seems the form component is what is different.
Create a general purpose component that utilizes the power of a render prop, i.e. a prop that is a function called during the render. This render prop will pass the close handler as an argument.
Example:
const FormButton = ({ toolTip, IconComponent, renderForm }) => {
const popoverProps = usePopover();
return (
<>
<IconButtonWithTooltip
toolTip={toolTip}
onClick={popoverProps.handleOpen}
IconComponent={IconComponent}
/>
<BasePopover usePopverProps={popoverProps}>
{renderForm({ handleClose: popoverProps.handleClose })}
</BasePopover>
</>
);
};
Example usage:
import CheckCircleOutlineIcon from "#mui/icons-material/CheckCircleOutline";
import CancelOutlinedIcon from "#mui/icons-material/CancelOutlined";
import FormButton from "./FormButton";
import RejectForm from "./RejectForm";
import ApproveForm from "./ApproveForm";
...
<TableBody>
{data.map((datum) => (
<TableRow key={datum.id}>
<TableCell>{datum.id}</TableCell>
<TableCell>{datum.name}</TableCell>
<TableCell>{datum.price}</TableCell>
<TableCell>
<FormButton
toolTip={"approve"}
IconComponent={<CheckCircleOutlineIcon color={"success"} />}
renderForm={({ handleClose }) => (
<ApproveForm id={datum.id} handleClose={handleClose} />
)}
/>
</TableCell>
<TableCell>
<FormButton
toolTip={"Reject"}
IconComponent={<CancelOutlinedIcon color="error" />}
renderForm={({ handleClose }) => (
<RejectForm handleClose={handleClose} name={datum.name} />
)}
/>
</TableCell>
</TableRow>
))}
</TableBody>

Conditional rendering does not display my data properly

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

pass multiple refs to child components

Before diving to the main problem, my use case is I am trying to handle the scroll to a desired section. I will have navigations on the left and list of form sections relative to those navigation on the right. The Navigation and Form Section are the child component. Here is how I have structured my code
Parent.js
const scrollToRef = ref => window.scrollTo(0, ref.current.offsetTop);
const Profile = () => {
const socialRef = React.useRef(null);
const smsRef = React.useRef(null);
const handleScroll = ref => {
console.log("scrollRef", ref);
scrollToRef(ref);
};
return (
<>
<Wrapper>
<Grid>
<Row>
<Col xs={12} md={3} sm={12}>
<Navigation
socialRef={socialRef}
smsRef={smsRef}
handleScroll={handleScroll}
/>
</Col>
<Col xs={12} md={9} sm={12}>
<Form
socialRef={socialRef}
smsRef={smsRef}
/>
</Col>
</Row>
</Grid>
</Wrapper>
</>
);
};
Navigation.js(child component)
I tried using forwardRef but seems like it only accepts one argument as ref though I have multiple refs.
const Navigation = React.forwardRef(({ handleScroll }, ref) => {
// it only accepts on ref argument
const items = [
{ id: 1, name: "Social connections", pointer: "social-connections", to: ref }, // socialRef
{ id: 2, name: "SMS preference", pointer: "sms", to: ref }, // smsRef
];
return (
<>
<Box>
<UL>
{items.map(item => {
return (
<LI
key={item.id}
active={item.active}
onClick={() => handleScroll(item.to)}
>
{item.name}
</LI>
);
})}
</UL>
</Box>
</>
);
});
export default Navigation;
Form.js
I do not have idea on passing multiple refs when using forwardRef so for form section I have passed the refs as simple props passing.
const Form = ({ socialRef, smsRef }) => {
return (
<>
<Formik initialValues={initialValues()}>
{({ handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<Social socialRef={socialRef} />
<SMS smsRef={smsRef} />
</form>
);
}}
</Formik>
</>
);
};
Social.js
const Social = ({ socialRef }) => {
return (
<>
<Row ref={socialRef}>
<Col xs={12} md={3}>
<Label>Social connections</Label>
</Col>
<Col xs={12} md={6}></Col>
</Row>
</>
);
};
Can anyone help me at passing multiple refs so when clicked on the particular navigation item, it should scroll me to its respective component(section).
I have added an example below. I have not tested this. This is just the idea.
import React, { createContext, useState, useContext, useRef, useEffect } from 'react'
export const RefContext = createContext({});
export const RefContextProvider = ({ children }) => {
const [refs, setRefs] = useState({});
return <RefContext.Provider value={{ refs, setRefs }}>
{children}
</RefContext.Provider>;
};
const Profile = ({ children }) => {
// ---------------- Here you can access refs set in the Navigation
const { refs } = useContext(RefContext);
console.log(refs.socialRef, refs.smsRef);
return <>
{children}
</>;
};
const Navigation = () => {
const socialRef = useRef(null);
const smsRef = useRef(null);
const { setRefs } = useContext(RefContext);
// --------------- Here you add the refs to context
useEffect(() => {
if (socialRef && smsRef) {
setRefs({ socialRef, smsRef });
}
}, [socialRef, smsRef, setRefs]);
return <>
<div ref={socialRef}></div>
<div ref={smsRef}></div>
</>
};
export const Example = () => {
return (
<RefContextProvider>
<Profile>
<Navigation />
</Profile>
</RefContextProvider>
);
};

How can i achieve 100% reusability of that react-bootstrap component?

I am trying to create a reusable carousel using react-bootstrap, i could create that one"
const ReusableCarousel = (props) => {
return (
<Carousel className="mt-4 border">
{props.items.map((item, index) => {
return (
<Carousel.Item key={index}>
<Row>
{props.children}
</Row>
</Carousel.Item>
);
})}
</Carousel>
);
}
Now as you see it is reusable till the point of the carousel item, props.children may represent multiple elements per one slide or single element per slide, but i can not achieve that according to my logic
in the parent:
<ReusableCarousel items={categories}> //categories is an array of arrays of objects
{
//item prop should be passed here to carouselItemCategories component
//but i couldn't find a way to achieve that
<CarouselItemCategories key={i} />
}
</ReusableCarousel>
carouselItemCategories Component:
const CarouselItemCategories = (props) => {
//still in my dreams
const { item } = props;
return (
<>
{
item.map((c, index) => {
return (
<Col key={index}>
//a category card here
</Col>
);
})
}
</>
);
}
Now i know what makes it work, it is about passing item prop(which represent an array of objects represents fraction of my categories) but i could not find any way to achieve that
you can imagine categories like that:
const categories = [
[
{
title: 'Laptops',
background: 'red'
},
{
title: 'Tablets',
background: 'blue';
}
],
[
{
title: 'Mouses',
background: 'yellow'
},
{
title: 'Printers',
background: 'orange';
}
]
]
If I understand you correctly, you want to use each of the items from your ReusableCarousel to generate a new CarouselItemCategories with the individual item passed in as a prop?
If so, you may want to take a look at the cloneElement function. Effectively, inside your mapping of the items prop, you would create a clone of your child element, and attach the individual item as a prop to that clone. Something like this:
const ReusableCarousel = (props) => {
return (
<Carousel className="mt-4 border">
{props.items.map((item, index) => {
return (
<Carousel.Item key={index}>
<Row>
{React.cloneElement(props.children, { item })}
</Row>
</Carousel.Item>
);
})}
</Carousel>
);
}
I just found another solution by using react context, i created a CarouselContext module :
import React from 'react';
const CarouselContext = React.createContext([]);
export const CarouselProvider = CarouselContext.Provider;
export default CarouselContext
and then in the ReusableCarousel component:
import { CarouselProvider } from './carouselContext'
const ReusableCarousel = (props) => {
return (
<Carousel >
{props.items.map((item, index) => {
return (
<Carousel.Item key={index} >
<Row >
{
<CarouselProvider value={item}>
{props.children}
</CarouselProvider>
}
</Row>
</Carousel.Item>
);
})}
</Carousel>
);
}
and then using the context to get item global variable
const CarouselItemCategories = () => {
const item = useContext(CarouselContext);
return (
<>
{
item.map((c, index) => {
return (
<Col>
//category card here
</Col>
);
})
}
</>
);
}

How can i use map() and find() in the same codeline

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);

Categories

Resources