I am using React-admin and following the demo that they give out. So far everything is working except for the Tab name/title translation. I have done the translation correctly because other components that have label attribute works fine with the translation.
Translations are getting from en.js file and added to app.js according to the react-admin documentation.
Here is my code :
class TabbedDatagrid extends React.Component {
tabs = [
{ id: 'countries', name: 'root.countries.title' },
{ id: 'languages', name: 'root.languages.title' },
];
state = { countries: [], languages: [] };
static getDerivedStateFromProps(props, state) {
if (props.ids !== state[props.filterValues.status]) {
return { ...state, [props.filterValues.status]: props.ids };
}
return null;
}
handleChange = (event, value) => {
const { filterValues, setFilters } = this.props;
setFilters({ ...filterValues, status: value });
};
render() {
const { classes, filterValues, ...props } = this.props;
return (
<Fragment>
<Tabs
fullWidth
centered
value={filterValues.status}
indicatorColor="primary"
onChange={this.handleChange}
>
{this.tabs.map(choice => (
<Tab
key={choice.id}
label={choice.name}
value={choice.id}
/>
))}
</Tabs>
<Divider />
<Responsive
small={<SimpleList primaryText={record => record.title} />}
medium={
<div>
{filterValues.status === 'countries' && (
<Datagrid hover={false}
{...props}
ids={this.state['countries']}
>
<TextField source="id" />
<TextField source="name" label="root.countries.fields.name"/>
</Datagrid>
)}
{filterValues.status === 'languages' && (
<Datagrid hover={false}
{...props}
ids={this.state['languages']}
>
<TextField source="id" />
<TextField source="name" label="root.languages.fields.name"/>
</Datagrid>
)}
</div>
}
/>
</Fragment>
);
}
}
The translations seems to work everywhere else but the Tab label, What I get instead of the Title is uppercase string of this root.countries.title.
Is there a workaround or how to fix this issue?
You probably used <Tab/> 'directly' from material-ui.
You need to use (create) 'enhanced version' (using translate prop) of this component.
Take inspiration from menu or other translatable components.
You need to pass your translations to your App.js as follows :
import React from 'react';
import { Admin, Resource } from 'react-admin';
import frenchMessages from 'ra-language-french';
import englishMessages from 'ra-language-english';
const messages = {
fr: { component:{label:'test'},...frenchMessages },
en: { component:{label:'test'},...englishMessages },,
}
const i18nProvider = locale => messages[locale];
const App = () => (
<Admin locale="en" i18nProvider={i18nProvider}>
...
</Admin>
);
export default App;
than when you want to use translations inside a component, you need to connect it to the react-admin's translate function as follows :
import { TextInput, translate } from 'react-admin';
const translatedComponent = ({translate, ...props}) => {
return <TextInput label={translate('component.label')} />
}
export default translate(translatedComponent);
it is important to connect the component with translate and to get the translate function from props to get the translation work.
Related
I'm trying to use Material UI to create a reusable navigation tab, however, I am having trouble passing the object over to my functional component and mapping it out. Nothing displays when mapping.
I am fairly new to react hooks. Thanks in advance.
Class Component (passing state over to Navigation)
class MyWorkspace extends Component {
state = {
menuItem: [
{
name: "menu 01",
urlPath: "/home/menu01"
},
{
name: "menu 02",
urlPath: "/home/menu02"
},
{
name: "Reports",
urlPath: "/home/menu03"
},
],
}
}
render () {
return (
<div>
<Navigation menuItem />
</div>
)
}
Functional Component
export default function Navigation({ menuItem }) {
const [value, setValue] = React.useState(2);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const MenuList = () => {
return (
<>
{menuItem.map(item => {
return <Tab label={item.name} className="Nav-Tab" />;
})}
</>
)
}
return (
<div className="Nav-Title row">
<Tabs
className="Nav-Tab-List"
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
>
<MenuList />
</Tabs>
</div>
);
}
In the class component, you should assign a value to the prop being passed:
render () {
return (
<div>
<Navigation menuItem={this.state.menuItem} />
</div>
)
}
In function component, you should call MenuList() inside the render :
export default function Navigation({ menuItem }) {
const [value, setValue] = React.useState(2);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const MenuList = () => {
return (
<>
{menuItem.map(item => {
return <Tab label={item.name} className="Nav-Tab" />;
})}
</>
)
}
return (
<div className="Nav-Title row">
<Tabs
className="Nav-Tab-List"
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
>
{MenuList()} // call this or put the map here
</Tabs>
</div>
);
}
First you need to define state in constructor
Second destruct menuitem from state
const {menuItem} = this.state
Third pass props like this
<Navigation menuItem={menuItem} />
If you pass like this Navigation menuItem /> you will get boolean value true inside child component.
In MyWorkspace/render function, you don't actually pass the menuItem state.
<Navigation menuItem /> will pass menuItem as true value. Replace it with: <Navigation menuItem={this.state.menuItem} />
Navigation component code looks correct
As I am trying to enhance the empty state for the hooks view in my UI form where there are no hooks to display in the view but I am trying to expect to see something say other than "No items for search term" in the behavior. I am a bit confused about what logic can I add to enhance the empty state.
Here is my code:
export default class ListHooks extends Component {
handleCreateHook = () => {
this.props.history.push('/hooks/create');
};
handleHookSearchSubmit = hookSearch => {
const query = parse(window.location.search.slice(1));
this.props.history.push({
search: stringify({
...query,
search: hookSearch,
}),
});
};
render() {
const {
classes,
description,
data: { loading, error, hookGroups },
} = this.props;
const query = qs.parse(this.props.location.search.slice(1));
const hookSearch = query.search;
const tree = hookGroups
? hookGroups.map(group => ({
value: group.hookGroupId,
nodes: group.hooks.map(hook => ({
value: hook.hookId,
href: `/hooks/${group.hookGroupId}/${encodeURIComponent(
hook.hookId
)}`,
})),
}))
: [];
return (
<Dashboard
title="Hooks"
helpView={<HelpView description={description} />}
search={
<Search
placeholder="Hook contains"
defaultValue={hookSearch}
onSubmit={this.handleHookSearchSubmit}
/>
}>
{!hookGroups && loading && <Spinner loading />}
<ErrorPanel fixed error={error} />
{hookGroups && (
<MuiTreeView
// key is necessary to expand the list of hook when searching
key={hookSearch}
defaultExpanded={Boolean(hookSearch)}
listItemProps={{ color: classes.listItemProps }}
searchTerm={hookSearch || null}
softSearch
tree={tree}
onEmptySearch={
<Typography variant="subtitle1">
No items for search term {hookSearch}
</Typography>
}
Link={Link}
/>
)}
<Button
spanProps={{ className: classes.actionButton }}
tooltipProps={{ title: 'Create Hook' }}
color="secondary"
variant="round"
onClick={this.handleCreateHook}>
<PlusIcon />
</Button>
</Dashboard>
);
}
}
I have a simple Tabs setup with React Material UI (https://material-ui.com/components/tabs/) where the path value is set dynamically
export const Subnav: React.FC<Props> = ({ routes = [] }) => {
const { pathname } = useLocation();
const { push } = useHistory();
const handleChange = (e: ChangeEvent<{}>, path: string) => push(path);
return (
<Tabs
indicatorColor="primary"
onChange={handleChange}
scrollButtons="auto"
textColor="primary"
value={pathname}
variant="scrollable"
>
{routes.map(r => (
<Tab label={r.name} value={r.path} />
))}
</Tabs>
);
};
When I first load a page / navigate to one of the tab routes, the correct tab is selected, but the indicator is not shown. In order for the indicator to be shown I have to click the same tab again or select another.
For this, there is one more way to handle this.
In React material UI there is component <Grow/>
you can warp <Tabs/> within <Grow/> and <Grow/> will correct the indicator position.
Following is the example:
<Grow in={true}>
<Tabs
action={ref => ref.updateIndicator()}
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
<Tabs>
</Grow>
This was resolved via https://github.com/mui-org/material-ui/issues/20527
You need to manually trigger the updateIndicator method. Easiest way to do this, is to call a resize event (which triggers the method)
useEffect(() => {
window.dispatchEvent(new CustomEvent("resize"));
}, []);
Alternatively add a ref to the actions prop and call the method directly. Still seems like a non-ideal solution, but it is what the maintainer provided.
My solution based on above ones but a bit different. The difference is that I update the indicator via a deferred call (setTimeout) with 400ms delay. Don't know real reason.
Below my definition of Mui-Tabs wrapper
import * as React from 'react';
import MaterialTabs, { TabsActions } from '#material-ui/core/Tabs';
import { withStyles } from '#material-ui/core';
import { IProps } from './types';
import styles from './styles';
class Tabs extends React.PureComponent<IProps> {
tabsActions: React.RefObject<TabsActions>;
constructor(props) {
super(props);
this.tabsActions = React.createRef();
}
/**
* Read more about this here
* - https://github.com/mui-org/material-ui/issues/9337#issuecomment-413789329
*/
componentDidMount(): void {
setTimeout(() => {
if (this.tabsActions.current) {
this.tabsActions.current.updateIndicator();
}
}, 400); // started working only with this timing
}
render(): JSX.Element {
return (
<MaterialTabs
action={this.tabsActions}
indicatorColor="primary"
variant="scrollable"
scrollButtons="auto"
{...this.props}
/>
);
}
}
export default withStyles(styles)(Tabs);
export const Subnav: React.FC<Props> = ({ routes = [], render }) => {
const { pathname } = useLocation();
const { push } = useHistory();
const handleChange = (e: ChangeEvent<{}>, path: string) => push(path);
const ref = useRef();
useEffect(() => {ref.current.updateIndicator()}, [pathname, render])
return (
<Tabs
action={ref}
indicatorColor="primary"
onChange={handleChange}
scrollButtons="auto"
textColor="primary"
value={pathname}
variant="scrollable"
>
{routes.map(r => (
<Tab label={r.name} value={r.path} />
))}
</Tabs>
);
};
Put into props.render your anything dynamically value
In my project, I'm using react-admin (version ^2.9.3) for backoffice and ra-data-firestore-client (version ^0.1.11) for react-admin firestore provider.
According to the react-admin documentation adding-search-and-filters-to-the-list, i did exactly the same thing with like the following :
EventList.js
// ... Some imports ...
const EventFilter = (props) => (
<Filter {...props}>
<SearchInput label="Rechercher" source="q" alwaysOn />
</Filter>
);
export const EventList = props => (
<List title="Tous les événements" filters={<EventFilter />} {...props}>
<Datagrid rowClick="show">
<TextField source="title" label="Titre" />
<TextField source="place" label="Lieu" />
<EditButton />
</Datagrid>
</List>
);
App.js
// ... Some imports ...
import Dashboard from './views/Dashboard';
import { EventList } from './views/event/Events';
const firebaseConfig = {};
const trackedResources = [{ name: 'events', isPublic: true }];
const authConfig = {userProfilePath: '/admins/', userAdminProp: 'isAdmin'};
const messages = {fr: frenchMessages, en: englishMessages};
const i18nProvider = locale => messages[locale];
const dataProvider = base64Uploader(RestProvider(firebaseConfig, { trackedResources }));
const LoginPage = () => <Login backgroundImage={require('./assets/login_image.jpg')} />;
function App() {
return (
<Admin
locale="fr" i18nProvider={i18nProvider}
title="Backoffice"
dashboard={Dashboard}
dataProvider={dataProvider}
authProvider={AuthProvider(authConfig)}
loginPage={LoginPage}
>
<Resource name="events"
list={EventList}
options={{ label: 'Evénements' }}
icon={EventIcon}
/>
</Admin>
);
}
export default App;
Any search word that I enter, the result is always "No result". I don't understand why ! May be i'm missing something. Please help me.
Firestore currently does not support full text search out of the box. Thus, ra-data-firestore-client library does not support a feature that does not exist.
I have a PlaceInput component which support google place autocomplete.
class PlaceInput extends Component {
state = {
scriptLoaded: false
};
handleScriptLoaded = () => this.setState({ scriptLoaded: true });
render() {
const {
input,
width,
onSelect,
placeholder,
options,
meta: { touched, error }
} = this.props;
return (
<Form.Field error={touched && !!error} width={width}>
<Script
url="https://maps.googleapis.com/maps/api/js?key={my google api key}&libraries=places"
onLoad={this.handleScriptLoaded}
/>
{this.state.scriptLoaded &&
<PlacesAutocomplete
inputProps={{...input, placeholder}}
options={options}
onSelect={onSelect}
styles={styles}
/>}
{touched && error && <Label basic color='red'>{error}</Label>}
</Form.Field>
);
}
}
export default PlaceInput;
I also have a menu item which is an<Input> from semantic-ui-react. The frontend is like below:
The menu code is like below:
<Menu.Item>
<Input
icon='marker'
iconPosition='left'
action={{
icon: "search",
onClick: () => this.handleClick()}}
placeholder='My City'
/>
</Menu.Item>
How can I leverage the PlaceInput component to make menu <Input> box to achieve the place autocomplete? Thanks!
If you could share a working sample of your app (in e.g. codesandbox) I should be able to help you make your PlaceInput class work with the Menu.Input from semantic-ui-react.
Otherwise, you can test a fully working example of such integration with the code below, which is based off of the Getting Started code from react-places-autocomplete.
import React from "react";
import ReactDOM from "react-dom";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
import { Input, Menu } from "semantic-ui-react";
const apiScript = document.createElement("script");
apiScript.src =
"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places";
document.head.appendChild(apiScript);
const styleLink = document.createElement("link");
styleLink.rel = "stylesheet";
styleLink.href =
"https://cdn.jsdelivr.net/npm/semantic-ui/dist/semantic.min.css";
document.head.appendChild(styleLink);
class LocationSearchInput extends React.Component {
constructor(props) {
super(props);
this.state = { address: "" };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => console.log("Success", latLng))
.catch(error => console.error("Error", error));
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<Menu>
<Menu.Item>
<Input
icon="marker"
iconPosition="left"
placeholder="My City"
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input"
})}
/>
</Menu.Item>
</Menu>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
ReactDOM.render(<LocationSearchInput />, document.getElementById("root"));
Hope this helps!