react-admin label prop does not work when creating custom fields - react-admin

I have created a simple wrapper component for ReferenceField in react-admin app:
import {FC} from "react";
import {ReferenceField, ReferenceFieldProps} from "react-admin";
export const UserReference: FC<Omit<ReferenceFieldProps, "reference" | "label">> = (props) => {
return <ReferenceField {...props} label="User" reference="users" />;
};
UserReference.displayName = "UserReference";
But when using the field the label is still inferred from source property (which is the default).
When I directly use ReferenceField with label it works:
// UserReference renders label "User id" and ReferenceField renders correct "User"
<Datagrid>
<UserReference source="user.id" />
<ReferenceField reference="users" label="User" source="user.id" />
</Datagrid>
Why does this happen and how can I fix it?
Do I have to forward refs?

<Datagrid> inspects its children label prop to build the header row. So to make it work in your case, you have to define the label in the defaultProps:
import {FC} from "react";
import {ReferenceField, ReferenceFieldProps} from "react-admin";
export const UserReference: FC<Omit<ReferenceFieldProps, "reference" | "label">> = (props) => {
return <ReferenceField {...props} reference="users" />;
};
UserReference.displayName = "UserReference";
UserReference.defaultProps = { label: 'User' };
this is documented in the react-admin documentation: https://marmelab.com/react-admin/Fields.html#writing-your-own-field-component

Related

How to press a next button and focus another text field input?

Version react hook form
^7.27.0
What I tried to follow and without successful
react hook form - Discussions 7818
react hook form - Issues 230
About what I have
I have 4 text field components at my screen, the name of each text field is name, documentation, email, password and I would like to know how can I setup some configuration that it will be pressed the NEXT button at keyboard and will focus the following text fields?
An example that I have inside at my component file, again I would like to press the next button and the next component that I will config, will be focus.
<TextField
name="name"
label={I18n.t('registerPersonal.fullNameLabel')}
placeholder={I18n.t('registerPersonal.fullNameInput')}
icon={<TypographyIcon fill={!!errors.name && theme.colors.attention} />}
error={errors.name?.message}
errors={errors}
control={control}
returnKeyType="next"
/>
<TextField
name="documentation"
label={I18n.t('registerPersonal.documentIdentificationLabel')}
placeholder={I18n.t('registerPersonal.documentIdentificationInput')}
icon={
<DocumentIcon
fill={!!errors.documentation && theme.colors.attention}
/>
}
error={errors.documentation?.message}
control={control}
returnKeyType="next"
/>
Some properties that I get at my personal hook
const {
control,
handleSubmit,
formState: { errors, isValid }
} = useForm({ resolver: yupResolver(schema) })
My component TextField
import { TextInputProps, Text } from 'react-native'
import { Control, useController } from 'react-hook-form'
import { Container, Wrapper, TextInput, Label } from './styles'
import theme from '../../global/styles/theme'
type TextFieldProps = {
placeholder?: string
label?: string
icon?: React.ReactNode
error?: string
errors?: {
[x: string]: any
}
name: string
control: Control
} & TextInputProps
export function TextField(props: TextFieldProps) {
const { placeholder, label, icon, error, errors, name, control, ...rest } =
props
const { field } = useController({
control,
defaultValue: '',
name
})
return (
<>
<Container>
{!!label && <Label>{label}</Label>}
<Wrapper hasLabel={!!label} hasError={!!error}>
{!!icon && icon}
<TextInput
value={field.value}
onChangeText={field.onChange}
placeholder={error ? error : placeholder}
placeholderTextColor={
error ? theme.colors.attention : theme.colors.grayColor
}
{...rest}
/>
</Wrapper>
{errors && errors.name && errors.name.type === 'matches' && (
<Text>{errors.name.message}</Text>
)}
</Container>
</>
)
}
your component must include ref prop like
<TextInput
ref={inputRef} // this one
value={field.value}
onChangeText={field.onChange}
placeholder={error ? error : placeholder}
placeholderTextColor={
error ? theme.colors.attention : theme.colors.grayColor
}
{...rest}
/>
then, you need to create ref from parent for each field
const emailRef = useRef(null);
const passwordRef = useRef(null);
after this, you need to add the props that is
onSubmitEditing={() => passwordRef.current.focus()} // to auto focus password field
finally,
<TextField
onSubmitEditing={() => passwordRef.current.focus()} // here
name="name"
label={I18n.t('registerPersonal.fullNameLabel')}
placeholder={I18n.t('registerPersonal.fullNameInput')}
icon={<TypographyIcon fill={!!errors.name && theme.colors.attention} />}
error={errors.name?.message}
errors={errors}
control={control}
returnKeyType="next"
/>

How to create a custom record action button inside a List component with React-Admin?

I'm a totally newbie with React and React-Admin. IMHO, I'm trying to achieve something simple that many people must have already done but I cannot find any kind of tutorial anywhere.
I'd like to add another button to the list of action buttons (show/edit) within each row in a <List> component. This button would archive the record.
My last try looks like the code below.
import React from 'react';
import {
Datagrid,
EmailField,
List,
TextField,
ShowButton,
EditButton,
DeleteButton,
CloneButton,
} from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import ArchiveIcon from '#material-ui/icons/Archive';
const useRowActionToolbarStyles = makeStyles({
toolbar: {
alignItems: 'center',
float: 'right',
width: '160px',
marginTop: -1,
marginBottom: -1,
},
icon_action_button: {
minWidth: '40px;'
},
});
const ArchiveButton = props => {
const transform = data => ({
...data,
archived: true
});
return <CloneButton {...props} transform={transform} />;
}
const RowActionToolbar = (props) => {
const classes = useRowActionToolbarStyles();
return (
<div className={classes.toolbar}>
<ShowButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<EditButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<ArchiveButton {...props} basePath={props.basePath} label="" icon={<ArchiveIcon/>} record={props.record} className={classes.icon_action_button} />
<DeleteButton basePath={props.basePath} label="" record={props.record} className={classes.icon_action_button}/>
</div>
);
};
export const UserList = props => {
return (
<List
{...props}
sort={{ field: 'first_name', order: 'ASC' }}
>
<Datagrid>
<TextField source="first_name"/>
<TextField source="last_name"/>
<EmailField source="email"/>
<RowActionToolbar/>
</Datagrid>
</List>
)
};
Obviously, this code does not work because the <CloneButton> component get rid of the id the record. Moreover, except if I did something wrong - which is totally possible -, it makes a GET request to a create endpoint.
I'm using different routes in my dataProvider (The back end is using Django and Django rest framework). I want to send a PATCH to the detail endpoint, like the <Edit> component does.
I also tried with a <SaveButton>, but it fails too.
Uncaught TypeError: Cannot read property 'save' of undefined
at useSaveContext (SaveContext.js:23)
I guess the <SaveButton> must be within a <SimpleForm>?
I'd like the save behaviour of the <DeleteButton>, i.e. update the record from the list, display the notification that the record has been archived (with the Undo link), send the request to the back end, refresh the list.
Any guidance, directions would be very appreciated.
I don't know that this is a full answer, but felt like more than a comment...
You are trying to archive the existing record, not create a whole new record, right? CloneButton is supposed to be used to create a new record with a new ID (which is why your ID is going away), so you don't want to us it here. note that I've never used CloneButton. it is not fully documented so I could be wrong about its use.
I am thinking that you should use the useRecordContext hook within your Archive button to pull in all of the record's data, including the id; read this little section: https://marmelab.com/react-admin/Architecture.html#context-pull-dont-push
And I don't think transform is what you're looking for here. You will need to use one of the dataProvider hooks, i'm assuming useUpdate: https://marmelab.com/react-admin/Actions.html#useupdate
//first create component
const MyButton = (props: any) => {
const [sendEmailLoading, setSendEmailLoading] =
React.useState<boolean>(false);
const record = useRecordContext(props);
const sendEmail = (id: Identifier) => {
setSendEmailLoading(true)
dataProvider.sendEmail(
"notifications", { id: id })
.then(({ data }: any) => {
if (data && data.status == "success")
notify('Email send success', { type: 'success' });
setSendEmailLoading(false);
refresh();
});
};
return (
<ButtonMUI color='primary' size="small" onClick={() => {
sendEmail(record.id) }}>
{
!record.publish &&(
!sendEmailLoading ? (
translate('resources.notifications.buttons.send')
) : (
<CircularProgress size={25} thickness={2} />
)
)
}
</ButtonMUI>
)
}
//and second add to datagrid list
<Datagrid>
<NumberField source="id" />
<TextFieldRA source="subject" />
<DateField source="date" />
<BooleanField source="publish" />
{/* <EditButton /> */}
<ShowButton />
<MyButton />
</Datagrid>

react-admin TypeError: Cannot read property 'save' of undefined

I'm trying to show a list of users with react-admin.
CustomerList.tsx
import React from 'react';
import {
TextField,
Datagrid,
DateInput,
DateField,
Filter,
List,
EmailField,
SearchInput,
SimpleForm,
} from 'react-admin';
import { useMediaQuery, Theme } from '#material-ui/core';
import MobileGrid from './MobileGrid';
const CustomerFilter = (props: any) => (
<Filter {...props}>
<SearchInput source='q' alwaysOn />
<DateInput source='created_at' />
</Filter>
);
const CustomerList = (props: any) => {
const isXsmall = useMediaQuery<Theme>((theme) =>
theme.breakpoints.down('xs')
);
return (
<List
{...props}
filters={<CustomerFilter />}
sort={{ field: 'created_at', order: 'desc' }}
perPage={100}
>
{isXsmall ? (
<MobileGrid />
) : (
<SimpleForm>
<Datagrid optimized rowClick='edit'>
<TextField source='id' />
<DateField source='created_at' showTime />
<DateField source='updated_at' showTime />
<EmailField source='email' />
<TextField source='status' />
</Datagrid>
</SimpleForm>
)}
</List>
);
};
export default CustomerList;
But i have an error TypeError: Cannot read property 'save' of undefined.
Something Went Wrong
A client error occurred and your request couldn't be completed.
Détails
TypeError: Cannot read property 'save' of undefined
at SaveButton (http://localhost:3000/static/js/0.chunk.js:134106:25) at div at div at Toolbar (http://localhost:3000/static/js/0.chunk.js:31314:23) at WithStyles(ForwardRef(Toolbar)) (http://localhost:3000/static/js/0.chunk.js:41921:31) at Toolbar (http://localhost:3000/static/js/0.chunk.js:140673:38) at WithWidth(Toolbar) (http://localhost:3000/static/js/0.chunk.js:37871:90) at form at SimpleFormView (http://localhost:3000/static/js/0.chunk.js:139725:21) at FormView (http://localhost:3000/static/js/0.chunk.js:108800:19) at ReactFinalForm (http://localhost:3000/static/js/0.chunk.js:189371:20) at FormWithRedirect (http://localhost:3000/static/js/0.chunk.js:108697:18) at SimpleForm at div at Paper (http://localhost:3000/static/js/0.chunk.js:20241:23) at WithStyles(ForwardRef(Paper)) (http://localhost:3000/static/js/0.chunk.js:41921:31) at Card (http://localhost:3000/static/js/0.chunk.js:5097:23) at WithStyles(ForwardRef(Card)) (http://localhost:3000/static/js/0.chunk.js:41921:31) at div at div at ListView (http://localhost:3000/static/js/0.chunk.js:150222:23) at ListContextProvider (http://localhost:3000/static/js/0.chunk.js:96869:18) at List (http://localhost:3000/static/js/0.chunk.js:149806:79) at CustomerList (http://localhost:3000/static/js/main.chunk.js:4177:91) at WithPermissions (http://localhost:3000/static/js/0.chunk.js:95243:23) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at ResourceContextProvider (http://localhost:3000/static/js/0.chunk.js:103522:21) at ResourceRoutes (http://localhost:3000/static/js/0.chunk.js:103390:17) at Resource (http://localhost:3000/static/js/0.chunk.js:103460:15) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at RoutesWithLayout (http://localhost:3000/static/js/0.chunk.js:103566:21) at div at main at div at div at LayoutWithoutTheme (http://localhost:3000/static/js/0.chunk.js:147666:24) at WithStyles(LayoutWithoutTheme) (http://localhost:3000/static/js/0.chunk.js:41921:31) at withRouter(WithStyles(LayoutWithoutTheme)) (http://localhost:3000/static/js/0.chunk.js:193873:37) at Connect(withRouter(WithStyles(LayoutWithoutTheme))) (http://localhost:3000/static/js/0.chunk.js:190558:75) at ThemeProvider (http://localhost:3000/static/js/0.chunk.js:40623:24) at Layout (http://localhost:3000/static/js/0.chunk.js:147783:26) at MyLayout (http://localhost:3000/static/js/main.chunk.js:8952:80) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at div at CoreAdminRouter (http://localhost:3000/static/js/0.chunk.js:103033:87) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at CoreAdminUI (http://localhost:3000/static/js/0.chunk.js:103242:15) at AdminUI at Router (http://localhost:3000/static/js/0.chunk.js:193251:30) at ConnectedRouter (http://localhost:3000/static/js/0.chunk.js:49787:7) at ConnectedRouterWithContext (http://localhost:3000/static/js/0.chunk.js:49892:25) at Connect(ConnectedRouterWithContext) (http://localhost:3000/static/js/0.chunk.js:190558:75) at TranslationProvider (http://localhost:3000/static/js/0.chunk.js:110397:28) at Provider (http://localhost:3000/static/js/0.chunk.js:190271:20) at CoreAdminContext (http://localhost:3000/static/js/0.chunk.js:102802:25) at AdminContext at Admin (http://localhost:3000/static/js/0.chunk.js:154193:22) at App
This is the lastest version of react-admin and all dependencies were updated.
What's wrong with the save button?
Thanks & Regards

access `headerRight` inside a screen that is hosted by stack navigator

I have created a stack navigator:
import {createStackNavigator} from '#react-navigation/stack';
const TheStack = createStackNavigator();
Then, This is my navigator, it claimed component={LandingScreen}:
<TheStack.Navigator ...>
<TheStack.Screen
name="LandingScreen"
component={LandingScreen}
options={{
title: '',
headerLeft: null,
headerRight: () => (
<MyHeaderRightComponent />
),
}}
/>
<TheStack.Navigator>
As you can see above in options of the screen, there is headerRight, I have declared using MyHeaderRightComponent as headerRight so that it is shown on the right side of the header on screen.
Here is my LandingScreen.js :
import React, {useState} from 'react';
import {View, StyleSheet} from 'react-native';
const LandingScreen = ({navigation}) => {
// How can I access the `headerRight` component I have set above from here?
...
}
My question is how can I access the headerRight inside my LandingScreen.js? I know I can update or reset the headerRight by:
navigation.setOptions({headerRight:() => <NewHeaderRightComponent/>})
But now what I need is to access the previous already set component, not setting a new one. How to do that?
Edits to the answer as per the request received in comments. The answer is the same. This is just further demonstration on how to use it.
// The screen component where you want to pass the state.
const Screen = (props) => {
const [color, setColor] = useState("#CCCCCC");
const { navigation } = props //This is important or else UseEffect will be called each time any of the props change
useEffect(() => {
navigation.setParams({ color: color }); // Where its being passed.
}, [color, navigation]);
return (
<>
<Button onPress={() => setColor("#800000")} /> // Change the color state to Maroon
<Button onPress={() => setColor("#FED700")} /> // Change the color state to Gold
</>
)
}
Your Header Component:
const MyHeaderComponent = (props) {
return(
<View style={{ backgroundColor: props.bgColor }} />
)
}
Then you can retrieve this bit in headerRight. Like this:
headerRight:() => <MyHeaderComponent bgColor={route.params.color} />
Note: This method is valid for React Navigation v5. Version 4 has a getParams() function to retrieve the params, but it was dropped in Version 5.
Original Answer
You can create a useState hook in the screen and pass its value into your header component. So, when the header component updates the state, it can be accessed from within the screen where you have defined the state.
you can use setParams() function to set the params you want to use in the header component. Then, use route.params.nameofyourprop to get them in the headerComponent, where you can consume it.
This is to pass params from outside the header to inside of it.
headerRight:() => <MyHeaderRightComponent propname={route.params.propvalue} />
This to to set the Params from outside your header which you can access inside the headerRight component.
const [values, setValue] = useState()
navigation.setParams({propname: value})
This way you can pass state between the header and the screen.
You can also pass the setValue function of the useState in this manner, but it will throw a warning because functions are objects in Javascript and thus its not possible to index them... or something on those lines.

How to get props variable in detailed props React-Native

I have a custom component like this
const MyCustomComponent = ({ value, style }) => {
// I can access value & style with value and style
return <View style={style}>
<Text>{value}</Text>
</View>
}
I can called it with
<MyCustomComponent value="123" style={{ color: "blue" }} />
My question is how to get arguments or alyelse to get all props passed to my component?
In native function, i can use arguments to get allProps as an Array and set it in a new variable like const allProps = arguments[0]
What about in arrow function?
What you have here is a functional component, it is built as such that it only receives one object - that is props. When you did this: const MyComponent({value, style}) you destructured the prop object, extracting only the two variables.
You should instead do this:
const MyCustomComponent = (props) => {
//you can access the values like this
console.log(props.style, props.value)
//or you can access them like this which is the same thing you did
//earlier
const {style, value} = props;
console.log(style, value)
return (
...
)
}
Keep in mind that you need to have React in scope, so be sure to import it at the top:
import React from 'react';
Functional components are very well explained in React documentation: https://reactjs.org/docs/components-and-props.html
Do you want to get 'value' and 'style' props using a single variable?
You can use:
const MyCustomComponent = (props) => {
// I can access value & style with value and style
return <View style={props.style}>
<Text>{props.value}</Text>
</View>
}