How to access record's internals in Show/Edit/Create - admin-on-rest

Similar to admin-on-rest: Access row's column data within a Datagrid component although I think it doesn't apply to my cases:
export const PlantShow = (props) => {
return (<Show {...props}>
<TabbedShowLayout>
<Tab label="Analytics">
{ record.oneId && <MetricsCharts {...props} manufacturer="one" /> }
{ record.otherId && <MetricsCharts {...props} manufacturer="other" /> }
{ record.anotherId && <MetricsCharts {...props} manufacturer="another" /> }
</Tab>
</TabbedShowLayout>
</Show>)
}
There should be a way to access current record's internals so I can introduce conditional logic .. I didn't find anything useful in props.. I even tried to inject {record} in PlantShow function but that didn't work either. Also DependentInput definitely doesn't help here
Thanks!

You'll have to use an intermediate component as a child of Show. It will receive the record prop.
See https://codesandbox.io/s/wyln51r907 (in posts.js, around the PostShow component.

Related

React routing - react-router-dom pushState() call to history happens too many times

I am using react-router-dom (v6) and noticed that I cannot use the browsers back button as expected. It seems like my application (using react/redux) pushes the location multiple times to the browser history when I am routing to a different location in my app.
This results in an application crash in Safari too:
SecurityError: Attempt to use history.pushState() more than 100 times per 30 seconds
The code looks something like this:
App.tsx
...
<BrowserRouter>
<Routes>
<Route path={ROUTES.auth.login} element={<PublicRoute component={Login} />} />
<Route path={ROUTES.auth.register} element={<PublicRoute component={Register} />} />
<Route path={ROUTES.dashboard} element={<PrivateRoute component={Dashboard} />} />
</Routes>
</BrowserRouter>
...
PublicRoute.tsx
...
export default function PublicRoute({ propsForComponent = {}, ...props }: PublicRouteProps): ReactElement {
const { showLoadingScreen } = useSelector((state: RootState) => ({
showLoadingScreen: state.loading.showLoadingScreen,
}));
const { component: Component } = props;
if (showLoadingScreen) return <LoadingOverlay />;
return <Component {...propsForComponent} />;
}
...
I found out that the PublicRoute component gets called multiple times due to the connection to the redux store with the useSelector hook.
showLoadingScreen gets updated when the application is trying to fetch the user, during this time this variable changes.
So my question is now: how do I prevent multiple re-renders, which causes the history.pushState() calls?
I know I could move the LoadingOverlay component to a different entry point. But In PrivateRoute I am also connected to the redux state because I need to access the user object. (I used the PublicRoute component to show a minimal example of whats going on...)
Any help would be highly appreciated,
cheers!

How to add a filter input based on another filter input on react-admin?

I'm using on react-admin and try to add a filter options to a list.
My problem is how to add a filter input based on another filter input?
I want to filter the list by organization and by project bu project is associated to an organization so I want to enable select a project only after organization is selected and and only of projects associated tothe selected organization.
That is waht I try to do:
const ProjectInputs = () => {
const { values } = useFormState();
const translate = useTranslate();
const classes = useStyles();
return (
<ReferenceInput
label={translate("resources.projects.name", { smart_count: 1 })}
source="projectId"
reference="projects"
filter={{ organizationId: values.organizationId }}
disabled={!values.organizationId}
alwaysOn
variant="standard"
>
<SelectInput
optionText="name"
resettable
alwaysOn
/>
</ReferenceInput>
);
}
export const Paymentilter: FC<Omit<FilterProps, 'children'>> = props => {
const classes = useFilterStyles();
return (
<Filter {...props}>
<ReferenceInput
source="organizationId"
reference="organizations"
variant="standard"
>
<AutocompleteInput
optionText={(choice?: Organization) =>
choice?.id // the empty choice is { id: '' }
? `${choice.name}`
: ''
}
/>
</ReferenceInput>
<ProjectInputs />
</Filter>
);
};
It works but there are some problems:
I cant define the project input to be always on.
In the add filter button there is no name for this input only blank shrink space.
The position of the project input is unordered input is popping up.
after choosing this filter I cant close it.
Do you have an idea for me?
How to fix my problems?
Sholud I solve my issue in another way?
Thank you!
EDIT:
I did like #doctoragon way,
and that is ok. in this way the project input cant be choosen from the add filter button only when we choose an organization it apears and when we cancel it disaper.
That ok for me but the second input still looks different as you can see on the picture it is upper then al the others inputs, and the cancel button is overirded by the chosen option.
You might be able to solve this using a FormDataConsumer inside your Filter.
<Filter {...props}>
<ReferenceInput source="organizationId" ... />
<FormDataConsumer source="projects" alwaysOn>
{
({ formData, ...restOfTheProps }) => organizationId && <ProjectInputs />
}
</FormDataConsumer>
</Filter>

AlpineJS - autoselection doesn't work when there is more than one todo

I'm making a little todo app in AlpineJS and I'm running into a problem of autoselecting a field when a user clicks on a todo. This problem only happens when there are multiple todos.
When the user enters the first todo onto the app, they can then click on the todo to edit it. The field will autoselect as expected. However, when the user adds more than one todo onto the app and wants to edit the n+1 todo, the field will no longer autoselects, but the first todo still autoselects just fine.
Here's my CodePen for your reference.
Here's what my edit functionality looks like:
JS
[...]
edit(todo) {
todo.editing = !todo.editing
this.$nextTick(() => {
document.getElementById(`edit-${todo.id}`).select()
})
}
[...]
HTML
[...]
<div
x-text="todo.item"
class="todo-item"
#click="edit(todo)" // <-- point of interest
x-show="!todo.editing"
:class="{ 'completed' : todo.completed }"
>
</div>
<input
type="text"
x-show="todo.editing"
:value="todo.item"
x-model="todo.item"
#keydown.enter="edit(todo)"
#keydown.escape="todo.editing = false"
#click.away="todo.editing = false"
class="edit-input"
:id="`edit-${todo.id}`" // <-- point of interest
/>
[...]
Would anyone have an idea of what might be wrong with my logic? Thank you for the help!
It seems using setTimeout(() => { ... }, 1) (timeout of 1ms) works, see the following Codepen modified from yours.
There are known issues in Alpine.js around $nextTick.
I would also recommend using x-ref instead of ids, so your input would look as follows:
<input
type="text"
x-show="todo.editing"
:value="todo.item"
x-model="todo.item"
#keydown.enter="edit(todo)"
#keydown.escape="todo.editing = false"
#click.away="todo.editing = false"
class="edit-input"
:x-ref="`edit-${todo.id}`"
/>
You can then set edit(todo) to be:
edit(todo) {
todo.editing = !todo.editing
setTimeout(() => {
this.$refs[`edit-${todo.id}`].select()
}, 1)
}

Admin on Rest: How can we preserve a query param between

We have a need where 3 different menu items perform CRUD operations to the same resource ("assets"), but with just the category_id being different on all CRUD operations (until you press again a different menu item).
In order to do this, we introduced a query param, called kind, which is the only practical difference among these 3 links:
<MenuItemLink
to={{
pathname: '/assets',
search: stringify({page: 1, perPage: 25}),
}}
primaryText="Assets"
onClick={onMenuTap}
leftIcon={<AssetsIcon />}
/>
<MenuItemLink
to={{
pathname: '/assets',
search: stringify({
page: 1,
perPage: 25,
kind: 'printers'
}),
}}
primaryText="Printers"
onClick={onMenuTap}
leftIcon={<AssetsIcon />}
/>
<MenuItemLink
to={{
pathname: '/assets',
search: stringify({
page: 1,
perPage: 25,
kind: 'vehicles'
}),
}}
primaryText="Vehicles"
onClick={onMenuTap}
leftIcon={<AssetsIcon />}
/>
Through the following code, the List fetches records only for these assets:
// NOTE: 'parsedKind' is parsed grom query params with previous code, it gets one of these values: '' OR 'printers' OR 'vehicles'
<List
title={<TitleList location={props.location} />}
{...props}
filters={<AssetFilter location={props.location} key={parsedKind} />}
perPage={15}
filter={{cat_id: dbIds[parsedKind]}}
sort={{field: 'name', order: 'ASC'}}
actions={<OurCustomListActions parsedKind={parsedKind} {...props} />}
key={parsedKind}
>
In order for this to work we had to customize the "actions" with custom Buttons, so that the parsedKindis following through. For example, the CreateButton now needs a prop ourQuery:
<FlatButton
primary
label={label && translate(label)}
icon={<ContentAdd />}
containerElement={<Link to={`${basePath}/create?${ourQuery}`} />}
style={styles.flat}
/>
I have 3 questions here:
This was a tedious work (all buttons have been customized) and I wonder if there was another solution to cover this need.
Still, the "SaveButton" causes headaches, because it uses a handleSubmitWithRedirect function that comes from props and the parameter is just the name of the view ('list' for example). So how can I add the query param on the final URL there? I used an ugly setTimeout to do this after 2 seconds (window.location.hash = window.location.hash + '?' + this.props.ourQuery;) but obviously this is very wrong. The DeleteButtonis also problematic because it redirects to a subview.
Using the above approach I get NaN-NaN from 19 in the List pagination. Why? Can this also be solved somehow?
Thank you!
So I think your best bet would have been to simply create three resources: Assets, Vehicles and Printers.
Than in your restClient/dataProvider, create logic to route these three resources to the assets endpoint with the correct parameter. I simple middleware would suffice, something in the line off:
// MyAssetResourceMiddleware.js
export default restClient => (type, resource, params) => {
switch(resource){
case 'Assets':
case 'Vehicles':
case 'Printers':
return restClient(type,'assets',{...params,kind:resource});
default:
return restClient(type,resource,params);
}
}
And wrap your provider with it
<Admin dataProvider={MyAssetResourceMiddleware(dataProvider)} .../>

Use jsx to write vue component,How to bind the event with sync modifier?

Here is MyCompnent
props: ['visible']
methods: {
update () {
this.$emit('update:visible')
}
}
I use this component in jsx:
<MyComponent visible={this.visible} {...{['on-update:visible']: console.log}} />
but can not bind the event.
so how to bind this event in jsx.
I have find the answer from the example https://github.com/vuejs/babel-plugin-transform-vue-jsx/blob/master/example/example.js
It cant write like this:
<MyComponent visible={this.visible} {...{on:{'update:visible': console.log}} />

Resources