How to set color of asterisk added by FieldTitle component for validation={required()} fields - react-admin

There seems to be no way of styling the asterisk that FieldTitle adds for components that have validation={required()} fields.
You can use an asterisk that mui provides for required fields by providing the "required" attribute, but you'll get just two asterisks.
React-admin v2

No, react-admin provides no API to style the asterisk in its Input components.
You'll have to build your own Input component, as explained in the react-admin documentation:
import TextField from '#material-ui/core/TextField';
import { useInput, required } from 'react-admin';
const BoundedTextField = props => {
const {
input: { name, onChange, ...rest },
meta: { touched, error },
isRequired
} = useInput(props);
return (
<TextField
name={name}
label={props.label}
onChange={onChange}
error={!!(touched && error)}
helperText={touched && error}
required={isRequired}
{...rest}
/>
);
};

If you’re using FormLabel from Material-UI and you have to use it for selected fields, you can use with required and MuiFormLabel-asterisk.
<FormLabel required sx={{ '& .MuiFormLabel-asterisk': { color: '#FF0000' } }}>Title</FormLabel>

Related

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

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

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-native-material-textfield Doesn't work as a controlled input

I'm using "react-native-material-textfield" for text inputs. I have a View to edit user details it fetch values from api when mounting and set it to state. But after upgrading "react-native-material-textfield" to "0.16.1" that original first name value is not shown in the text input after mounting. What I'm doing wrong here ?
constructor(props) {
super(props);
this.state = {
firstName: '',
};
}
componentDidMount(props) {
APIcall().then(data)=>{
this.setState({
firstName: data.firstName
});
}
}
<TextField
label="First Name"
value={this.state.firstName}
onChangeText={firstName => this.setState({firstName})}
/>
I ran into this after upgrading. In version 0.13.0 of the library, it switched to being a fully uncontrolled component according to the release notes.
Changed
defaultValue prop becomes current value on focus
value prop provides only initial value
Based on the current usage docs, there is now a method exposed for setting & getting the value using a ref to the component:
let { current: field } = this.fieldRef;
console.log(field.value());
(Personally, while I can maybe understand this improving performance because typing can often be fast for state updates, I'm not a fan of uncontrolled components since I want my state to drive the UI. I feel like this makes other live updates for validation very fiddly.)
In react-native-material-textfield, 'value' prop acts as default. To update the value you need to use ref. Get the ref using React.createRef(), then use setValue function
import React, { Component } from 'react';
import { TextField } from 'react-native-material-textfield';
import { View, Button } from 'react-native';
export default class TestComponent extends Component {
textField = React.createRef<TextField>();
constructor(props) {
super(props);
this.state = {
value: 'check',
};
}
onChangeText = () => {
// Send request via API to save the value in DB
};
updateText = () => {
if (this.textField && this.textField.current) {
this.textField.current.setValue('test');
}
};
render() {
return (
<View>
<TextField
label="Test value"
value={this.state.value}
onChangeText={this.onChangeText}
ref={this.textField}
/>
<Button onPress={this.updateText} />
</View>
);
}
}
Touch area in TextView
https://github.com/n4kz/react-native-material-textfield/issues/248
react-native-material-textfield
labelTextStyle={{ position: 'absolute', left: '100%' }}
label: {
fontFamily: fonts.Muli_SemiBold,
fontSize: 14,
letterSpacing: 0.1,
color: colors.gray90,
position: 'absolute', left: '100%'
},
<TextField
style={style.textInputRight}
labelTextStyle={style.label}
labelFontSize={16}}
onChangeText={value => onTextChange(value)}
/>

React-native Formik setFieldValue

Here is a simplified version of my code.
Notice the setFieldValue_ and this.setFieldValue_ = setFieldValue;
This code works fine, I'm able to get the output when submit button is clicked.
I'm actually wondering if this is the right way to do it? If not, can you point me to the right direction? Also what is this method called? (assigning class variable to some function and use it within another function)
class MyComponent extends React.Component {
setFieldValue_;
someFunction() {
this.setFieldValue_("name", value);
}
render() {
return (
<Formik
initialValues={{
something: ""
}}
onSubmit={values => console.log(values)}
>
{({
setFieldValue,
}) => {
this.setFieldValue_ = setFieldValue;
<ThirdPartyCustomComponent onChange={this.someFunction} />
}}
</Formik>
}
}
I would personally have the onChange simply call formik set field value there and then rather than using different functions. Strictly speaking you don't want to set the value like that because every re-render is setting the value again.
I would also recommend looking at custom formik inputs using the useField hook - https://jaredpalmer.com/formik/docs/api/useField. This will allow you to write a small wrapper around your third party component and formik. Noticing you have used a class based component you may want to do some short reading into react hooks before throwing yourself into using useField.
Docs example:
const MyTextField = ({ label, ...props }) => {
const [field, meta, helpers] = useField(props);
return (
<>
<label>
{label}
<input {...field} {...props} />
</label>
{meta.touched && meta.error ? (
<div className='error'>{meta.error}</div>
) : null}
</>
);
};

React Native + Redux Form - Use keyboard next button to go to next TextInput field

I'm using Redux Form (RF) in a React Native application. Everything works fine but I can not figure out how to get the refs from the Field input to go to the next input field with Redux Form.
Without RF this solution would work just fine.
Here is my code:
class RenderInput extends Component {
const { input, nextField, refs,
meta: { touched, error, warning },
input: { onChange } } = this.props
render() {
return (
<Input
returnKeyType = {'next'}
onChangeText={onChange}
onBlur={input.onBlur}
onFocus={input.onFocus}
onSubmitEditing = {(event) => {
// refs is undefined
refs[nextField].focus()
}}/>
)
}
}
class Form extends Component {
render() {
return (
<Field
name="field1"
focus
withRef
ref='field1'
nextField = "field2"
component={RenderInput}/>
<Field
name="vendor"
withRef
ref="field2"
nextAction = "field3"
component={RenderInput}/>
)
}
}
I'm passing on the property nextField to the component to determine the next input field when the Next key on the keyboard is clicked but I can not get the refs property inside the RenderInput component.
Any idea how to get the refs property?
This solution passes props from the Form component to the RenderInput component and passes a function call back.
Here's the code:
class RenderInput extends Component {
const { input, refField, onEnter,
meta: { touched, error, warning },
input: { onChange } } = this.props
render() {
return (
<TextInput
ref = {refField}
returnKeyType = {'next'}
onChangeText={onChange}
onBlur={input.onBlur}
onFocus={input.onFocus}
onSubmitEditing={onEnter}/>
)
}
}
class Form extends Component {
render() {
return (
<Field
name="field1"
focus
withRef
ref={(componentRef) => this.field1 = componentRef}
refField="field1"
component={RenderInput}
onEnter={() => {
this.field2.getRenderedComponent().refs.field2.focus()
}}/>
<Field
name="field2"
withRef
ref={(componentRef) => this.field2 = componentRef}
refField="field2"
component={RenderInput}/>
)
}
}
So what happened here?
I assign the ref to local scope with ref={(componentRef) => this.field1 = componentRef} as #Ksyqo suggested. Thanks for the hint.
I pass refField="field1" to the RenderInput and assign the value to the input ref property ref = {refField}. This will add the input object to the refs property.
I assigned a onEnter function in the Field
I pass the function to the props of RenderInput and assign it to the onSubmitEditing={onEnter} function. Now we have bind the two functions together. That means if onSubmitEditing gets invoked the onEnter function gets invoked as well
Finally, refer to the local field field2, get the rendered Component and use the refs, which we assigned in the Input field, to set the focus. this.field2.getRenderedComponent().refs.field2.focus()
I don't know if this is the most elegant solution but it works.
For people who are using Redux Form + React Native Elements, just follow #Thomas Dittmar answer, and add the following prop to the 'FormInput' component: textInputRef={refField}
The newest version of React Native Element has added the focus() method, so you don't have to worry about that.
withRef is deprecated, use forwardRef instead.
I worked on getting a ref like this that worked for me.
const renderComp = ({
refName,
meta: { touched, error },
input,
...custom
}) => (
<MyComponent
ref={refName}
{...custom}
/>
)
<Field
name={name}
component={renderComp}
ref={node =>
isLeft ? (this.compRef1 = node) : (this.compRef2 = node)}
refName={node =>
(this.myRef= node) }
withRef
/>
now access instance functions like this.
this.myRef.anyFunc()
I had a slightly different use case, but I imagine it works for the above as well.
I used the focus action
import { focus } from 'redux-form';
dispatch(focus('signIn', 'email'));
Then in the (custom) form field that contains the TextInput, I added in the render function
<TextInput
ref="email"
/>
formStates.filter((state) => meta[state]).map((state) => {
if(state === 'active'){
this.refs.email.focus()
}
})
No worries about the component nesting/hierarchy anymore.