I want to DRY the way my <Resource /> components are organized in my project. This way, my App.jsx file won't be bloated by a lot of code.
For example, let's say I have a <EmployeeResource /> component like this:
// ./resources/Employee.jsx
import React from "react";
import { Resource } from "react-admin";
const EmployeeResource = (props) => <Resource {...props} name="employees" />;
export default EmployeeResource;
Now I want to use it in my <Admin /> component like this:
// ./App.jsx
// other imports
import EmployeeResource from "./resources/Employee";
const App = () => (
<Admin
{...otherNecessaryProps}
>
<EmployeeResource />
</Admin>
);
Even if it's registered by react-admin and that I can see its link in the sidebar, its route doesn't work. If I click on the menu item I see a Not Found page, but if I navigate to /undefined in my browser, now I see its content.
Is there a way to DRY many <Resource /> components in one sub folder?
Related
I am struggling with the implementation of the "react-intersection-observer" and i can't for the life of me find a solution.
Details:
I have a simple presentation site which i wanna do with React so i can also learn. Right now the website has only the homepage and the homepage has so far these sections: header, about, portfolio and contact form.
What i wanna do is to simply add a class on each section (about, portfolio and contact form) once the section is in viewport. The kind of stuff that with jquery would be over in 2 minutes.
I have installed "react-intersection-observer" and so far the code in my homepage.component.jsx looks like this:
import React from 'react';
import Header from "../../components/header/header.component";
import About from "../../components/about/about.component";
import PortfolioList from "../../components/portfolio-list/portfolio-list.component";
import ContactForm from "../../components/contact-form/contact-form.component";
import { useInView } from 'react-intersection-observer';
const HomePage = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
triggerOnce: true,
threshold: 1,
onChange: (inView, entry) => {
console.log("salam");
console.log(inView);
console.log(entry);
if (inView) {
entry.target.classList.add("in-view");
}
}
});
return (
<div>
<Header/>
<main className="main">
<About />
<PortfolioList />
<ContactForm />
</main>
</div>
);
};
export default HomePage;
When i have added ref={ref} on each component like this:
<About ref={ref} />
<PortfolioList ref={ref} />
<ContactForm ref={ref} />
i have received an error: Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
The thing is that i don't want to add the useInView module in each of the 3 jsx components because it seems bad practice to have repeat code.
Passing ref as props is bad practice.
Use React.forwardRef instead:
https://beta.reactjs.org/apis/react/forwardRef
Check this example:
https://beta.reactjs.org/apis/react/forwardRef#forwarding-a-ref-through-multiple-components
In my React app, I have a layout file, and I want to be able to pass 2 different components into it. One component is to be shown in Area 1, and another is to be shown in Area 2. Both components need to share information with each other.
So, my layout is:
const SplitLayout = (Area1Content, Area2Content) => {
return (
<div className="area1">
<Area1Content />
</div>
<div className="area2">
<Area2Content />
</div>
);
}
export default SplitLayout;
In my App.js I have:
const App = () => (
<Router>
<Switch>
<Route exact path={ROUTES.HOME}
render={(props) =>
<SplitLayout {...props}
Area1Content={HomeContent}
Area2Content={SidebarContent} />}/>
</Switch>
</Router>
);
export default App;
This works fine; I can put HomeContent and SidebarContent into a file and export both of them, and they are shown correctly.
However, I want to be able to pass information from one to the other, so, for instance, SidebarContent has a list of names; when I click on a name in the list, I want that person's details to be shown in HomeContent (so in the HomeContent component I can have a state variable called currentPerson, and when a name is clicked in SidebarContent, the value of currentPerson should be changed).
Is there a way to achieve this?
I have several pages with similar layouts, so what I'm hoping is that I can have, eg, a HomeComponent.js file which has HomeContent and SidebarContent, and then another component called, say, SecondComponent.js which has SecondContent and SecondSidebar, so I can just add a new Route to App, something like:
<Route exact path={ROUTES.SECOND}
render={(props) =>
<SplitLayout {...props}
Area1Content={SecondContent}
Area2Content={SecondSidebar} />}/>
so it will render the same layout but with different components. I know I could lift the state up to the top level, but there could potentially be several different component pairs, each needing to pass info, and I think it would get messy to manage all of them at the App level. Is there a better way?
EDIT: I think what I want to do is something like this:
In App.js my route would be something like:
<Route exact path={ROUTES.HOME}
render={(props) =>
<SplitLayout {...props}
PageContent={WrapperComponent} />}/>
Then in the SplitLayout file I'd have something like:
const SplitLayout = (WrapperComponent) => {
return (
<div className="area1">
<WrapperComponent.Area1Content/>
</div>
<div className="area2">
<WrapperComponent.Area2Content/>
</div>
);
}
And WrapperComponent would be something like:
const WrapperComponent = () => {
const [myStateVariable, setMyState] = useState("xyz")
const Area1Content = () => {
return (<div>{myStateVariable}</div>);
}
const Area2Content = () => {
return (<div onClick={setMyState("abc")}>Something else</div>);
}
}
export default WrapperComponent;
Is there a way to do something like that?
You can put the shared state in the parent component, and pass it down as props.
This is how components are instantiated in react-admin, but now I need to share the notifications instance between the PostList and the UserList. I would like to avoid a singleton. Is it possible to pass the 'notifications' object somehow to the lists?
import React from 'react';
import PostIcon from '#material-ui/icons/Book';
import UserIcon from '#material-ui/icons/People';
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { PostList } from './posts';
import { UserList } from './users';
import { Notifications } from './notifications';
var notifications = new Notifications();
const App = () => (
<Admin dataProvider={jsonServerProvider('https://jsonplaceholder.typicode.com')}>
<Resource name="posts" list={PostList} icon={PostIcon} />
<Resource name="users" list={UserList} icon={UserIcon} />
</Admin>
);
Thanks in advance.
It seemed to me that everything is well implemented:
https://marmelab.com/react-admin/Theming.html#notifications
import { Layout } from 'react-admin';
import MyNotification from './MyNotification';
const MyLayout = (props) => <Layout {...props} notification={MyNotification} />;
const App = () => (
<Admin layout={MyLayout} dataProvider={simpleRestProvider('http://path.to.my.api')}>
// ...
</Admin>
);
Passing custom props to the <Resource> component was not quite working for me, so on trying out several different approaches, this was the one that suited my requirements.
So, there's a default prop which you can pass to the <Resource> component called options which accepts an object. Normally, you'd see this prop being used when you want to pull a label into your resource. The idea is to customise this object -
// In App.js
<Admin dataProvider={jsonServerProvider('https://jsonplaceholder.typicode.com')}>
<Resource name="posts" list={PostList} icon={PostIcon} options={{ "label": "Posts", "notifications": notifications }} />
<Resource name="users" list={UserList} icon={UserIcon} options={{ "label": "Users", "notifications": notifications }} />
</Admin>
and then access it in your resource something like -
// In Posts.js
export const PostList = (props) => {
const { notifications, label } = props.options;
// You have your notifications object here now
return (
<List
{...props}
title={label}
bulkActionButtons={false}
exporter={false}>
<Datagrid>
<DateField showTime={true} sortable={true} source="created_on" label="Timestamp" />
</Datagrid>
</List>
);
};
Let me know if this worked for you!
Seems like a simple thing. How do I put a title on the navbar? Here's my App.js:
import React from 'react';
import { Admin, Resource } from 'react-admin';
import { UserList } from './Users';
import { ItemList, ItemEdit, ItemCreate } from './Items';
import { ProductTypeList, ProductTypeEdit, ProductTypeCreate } from './ProductType'
import Dashboard from './Dashboard'
import jsonServerProvider from 'ra-data-json-server';
import dataProvider from './dataProvider';
import ItemIcon from '#material-ui/icons/ViewListRounded';
import ProductTypeIcon from '#material-ui/icons/LocalOffer';
import UserIcon from '#material-ui/icons/Group';
import authProvider from './authProvider'
const App = () => (
<Admin
dashboard={Dashboard}
authProvider={authProvider}
dataProvider={dataProvider} >
<Resource
name="items"
list={ItemList}
edit={ItemEdit}
create={ItemCreate}
icon={ItemIcon} />
{<Resource
name="productTypes"
options={{ label: 'Product Types' }}
list={ProductTypeList}
edit={ProductTypeEdit}
create={ProductTypeCreate}
icon={ProductTypeIcon} />}
<Resource
name="users"
list={UserList}
icon={UserIcon} />
</Admin >
);
export default App;
Running react-admin version 3.4.2. In the tutorial it shows "React Admin" as the title after adding the Dashboard. But after recreating a fresh app using npm create-react-app and going through the tutorial, I don't see a title in the navbar. Is this something that should be possible out of the box or do I need to create a custom navbar?
Train (above) led me to think of the component. I imported it and used it within and set title="My Title" and it did show in the navbar. Maybe I misread the docs, but I don't remember seeing anything about , just the title= attribute of , like so:
<Admin
// layout={CustomLayout}
dashboard={Dashboard}
authProvider={authProvider}
dataProvider={dataProvider} >
<Title title="g3tools Admin" />
<Resource
name="items"
list={ItemList}
edit={ItemEdit}
create={ItemCreate}
icon={ItemIcon} />
But, thankfully, it gets me to the next step: Now the title shows up in the navbar but when I choose a resource from the left menu, the resource name appends to the title.
Any suggestions on how to avoid the resource title cramming into the admin title? I'm sure I'm missing something obvious. Options would be: a) Dynamically ad a space or dash after title when resource title is displayed, or b) don't display the resource title (how would I do that?)
I think ultimately, I'd rather have a breadcrumb or show the resource title in the center of the navbar, but maybe I'll need a custom navbar for that?? Any guidance is welcome.
UPDATE:
I see in the docs for Customizing the AppBar how to not show the individual resource page title: Just remove the id="react-admin-title" from the component and then add text to the element:
<AppBar {...props}>
<Typography
variant="h6"
color="inherit"
className={classes.title}
>g3tools Admin</Typography>
</AppBar>
take a look in the docs, try this
<Admin
// add the title prop below
title="my page title"
dashboard={Dashboard}
authProvider={authProvider}
dataProvider={dataProvider} >
How does Youtube, Instagram, Github change the content only after they receive data?
<Switch>
...Other Route
<Route path = "/" exact component={HomeComp} />
<Route path = "/articles" component={ArticleComp} />
</Switch>
In my knowledge when I click a Nav Link to replace url from / to /articles the component replace from HomeComp to ArticleComp as well. But what I saw from other SPA application(those I mention above) even though the url is replace but the components aren't replace instead there is an progress bar, components are replace only until receiving response from fetch request. If you can't understand my word I try to include a picture for better understanding
If I want to do something like that where should I perform fetch request? From the doc It say it should perform in componentsDidMount(). But it seem not right since the component wasn't initial until the data is loaded.
Very simple question how can achieve the goal? Replace components only after receiving fetch response rather than replace url > replace components > start fetch request. The solution I seek for is like how github,youtube do(photo below).
Can I still stick with react-router if I want to achieve this?
Sorry for keep repeating the same question, I was worry what I ask is not clear. English is not my primary language so it is really hard for me research, I don't know include what keyword to find the correct solution. If this question is asked before kindly include the link for me. Thank you!
So, the assumption here is that you want certain parts of your UI common across different pages. i.e... HomeComp and ArticleComp.
Imagine that you have a Layout component (container):
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Header from './Header';
import Footer from './Footer';
class Layout extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
children: PropTypes.node,
};
render() {
return (
<div className={_className}>
<Header />
<div className={_rhsContentClassName}>
{ this.props.children }
</div>
<Footer />
</div>
);
}
}
export default Layout;
Now, in your routes file, you describe the routes as,
<Switch>
...Other Route
<Route path = "/" exact component={HomeComp} />
<Route path = "/articles" component={ArticleComp} />
</Switch>
and for each of the Route Component, HomeComp or ArticleComp, your react component should look something like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Layout from './Layout';
import Preloader from './Preloader';
class ArticleComp extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
};
componentWillMount() {
this.setState({ isLoading: true };
actions
.fetchData()
.then(() => {
// do something
// store data for the content
this.setState({ isLoading: false };
});
}
render() {
if (isLoading)
return <Preloader />;
return (
<Layout>
<div> {/* content based on the fetch request */}</div>
</Layout>
);
}
}
export default ArticleComp;
This way, you get to segregate your Header, Footer or other static ( or even dynamic concerns ) with the real request-based content.