How can I set a default value on ReferenceManyField component? - react-admin

I'm rendering a ... which resource is "Users", so always is calling this ReferenceManyField on but sometimes "User" don't have "Name", and I want to display some default if "User" don't have name, I did't find any solution for this problem, sorry. Thank you!

You can replace the contents of the 'record' field before passing it to your components:
<ReferenceManyField reference="Users" target="id" >
<SingleFieldList>
<FormDataConsumer>
{ ({ formData, dispatch, record, ...rest }) => {
const defValues = { Name: 'MyName', Param1: 'Value1', Param2: 'Value2' }
const newRecord = { ...defValues, ...record }
const params = { record: newRecord, ...rest }
return (<TextField source="Name" {...params} />)
}
}
</FormDataConsumer>
</SingleFieldList>
</ReferenceManyField>

Related

React-admin SelectInput occurs out of range warning

I have SelectInput inside of ReferenceInput
As you can see below,
This is workaround version of code. the selection shows right data and it parses as I expected, however, a out-of-range value **[object Object]** is show. (please see the reference image below)
This is a code snippet of SelectInput component
const AddressSelectInput: FC<AddressSelectInputProps> = (
{
customerId,
source,
...props
}
) => {
const classes = useStyles()
const [choices, setChoices] = useState<InputSourceProps[] | undefined>()
// sorting data from props
useEffect(() => {
if (props.choices) {
const result: InputSourceProps[] | undefined = props.choices?.find(
item => item._id === customerId
)?.addresses?.map((item: UserAddress) => {
let res: InputSourceProps;
res = {
name: item.label,
description: item.place.description,
postalCode: item.place.postalCode,
location: {
lat: item.place.location.lat,
lng: item.place.location.lng
}
}
return res;
})
setChoices(result)
}
}, [props.choices, customerId])
if (!choices) {
return null
}
const optionRenderer = (choice: Place) => `${choice.name} - ${choice.description}`;
return (
<SelectInput
className={classes.selectInputStyle}
label="Addresses suggestion"
choices={choices}
source={source}
optionText={optionRenderer}
optionValue={'name'}
defaultValue=''
parse={(name: string) => choices.find(c => c.name === name)}
/>
)
}
this is parent component of SelectInput:
const RideReferenceAddressInput: FC<RideReferenceInputProps> = ({
source,
label,
customerId,
}) => {
const filterToQuery = (customerId: string) => (filter: string) => ({
$search: filter,
_id: customerId
})
return (
<ReferenceInput
reference="users"
source={source}
filterToQuery={filterToQuery(customerId)}
>
<AddressSelectInput source={source} label={label} customerId={customerId} />
</ReferenceInput>
)
}
Could anyone can help to remove the warning? and why this is warning shows?
Thank you all in advance!

Adjust a field in a SimpleForm's value

I have a React Admin Edit form with a field which by default doesn't exist in the record. I could manually type it in, save it and it would work. However I wish to retrieve the value of this field programmatically when a user clicks a button then adjust it.
I use a useMutation hook to access a custom API which performs an expensive operation and returns a result for the field. I only want to perform this operation when the user clicks a button.
So inside a Edit form I have this field called key I want to apply the data from this useMutation hook to it.
export const PostEdit = (props) => (
<Edit title={<PostTitle />} {...props}>
<SimpleForm>
<TextInput disabled source="id" />
<RetrieveKey />
<TextInput source="key" />
</SimpleForm>
</Edit>
);
The RetrieveKey button is like this
const RetrieveKey = ({ record }) => {
const [retrieve, { loading }] = useMutation(
{
type: "retrieveKey",
resource: "posts",
payload: { id: record.id }
},
{
onSuccess: ({ data }) => {
if (data) {
// Set the key field here.
} else {
console.log("No Key");
}
},
onFailure: (error) => console.log("Error");
}
);
return (
<Button
onClick={retrieve}
disabled={loading}
label={"Retrieve Key"}
></Button>
);
};
I have looked through the various Form Hooks but can't find anything documented which would let me accomplish this.
Note, I don't want to instantly call the UpdateMethod or this would be trivial and I could use the DataProvider useUpdate to call it. Instead I want to prefill in the form for the user from the responsed key.
A codesandbox is provided here: https://codesandbox.io/s/nifty-firefly-k13g7?file=/src/App.js
I needed to access the underlying React-Final-Form API in order to edit the form.
The React-Admin Docs briefly touch on it here: https://marmelab.com/react-admin/Inputs.html#linking-two-inputs
So in this case I needed to use the useForm hook which then allowed me to update the form value.
const RetrieveKey = ({ record }) => {
const form = useForm();
const [retrieve, { loading }] = useMutation(
{
type: "retrieveKey",
resource: "posts",
payload: { id: record.id }
},
{
onSuccess: ({ data }) => {
console.log(form);
if (data) {
form.change("key", data);
} else {
console.log("No Key found");
}
},
onFailure: (error) => console.log("Error");
}
);
return (
<Button
onClick={retrieve}
disabled={loading}
label={"Retrieve Key"}
></Button>
);
};

React Admin Change field based on related record field

Let's say I have a record called 'assets' which has a column called deducitble. An asset can have one Insurer. The insurer has a boolean field 'allowOtherDeductible'.
When editing the asset, I want the ability to first check if the associated insurer has allowOtherDeductible set to true. If so I'll allow a TextInput for deductible, if false, a SelectInput.
How can I achieve this? I cannot see a way to fetch related record's fields when conditionally rendering fields.
I ended up pulling in all the insurers and loading the asset when the component loaded. Seems a bit inefficient, but I couldn't come up with a better way:
export const AssetEdit = props => {
const dataProvider = useDataProvider();
const [insurers, setInsurers] = useState([]);
const [asset, setAsset] = useState(null);
const [otherDeductible, setOtherDeductible] = useState(false);
useEffect(() => {
dataProvider.getOne('assets', { id: props.id })
.then(({ data }) => {
setAsset(data);
return dataProvider.getList('insurers', { pagination: { page: 1, perPage: 100 } });
})
.then(({ data }) => setInsurers(data))
.catch(error => {
console.log(error)
})
}, [])
useEffect(() => {
if (asset && insurers) {
const selectedInsurer = insurers.find(insurer => insurer.id === asset?.insurer_id);
setOtherDeductible(selectedInsurer?.other_deductible);
}
}, [insurers])
return (
<Edit {...props}>
<SimpleForm>
{otherDeductible &&
<NumberInput
source={'deductible'}
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
/>
}
<FormDataConsumer>
{({ formData, ...rest }) => {
if(!otherDeductible){
return <SelectInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
choices={getDeductibleChoices(formData.insured_value)}
/>
}
}}
</FormDataConsumer>
</SimpleForm>
</Edit>
)
}
I'd write a custom Input taking advantage of the fact that SimpleForm injects the record to all its children:
const DeductibleInput = ({ record }) => {
if (!record) return null;
const { data, loaded } = useGetOne('insurers', record.insured_id);
if (!loaded) return null; // or a loader component
return data.otherDeductible
? <NumberInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
/>
: <SelectInput
source="deductible"
parse={val => dollarsToCents(val)}
format={val => centsToDollars(val)}
choices={getDeductibleChoices(record.insured_value)}
/>
}

react-native InputText onChange returns undefined or doesn't allow to change the value

I have the following code:
const { state, updateParentName, updateBabyName } = useContext(AuthContext);
const [isParentInputVisible, setParentInputVisible] = useState(false);
const toggleParentInput = () => {
setParentInputVisible(!isParentInputVisible);
};
<View style={styles.headerContainer}>
<Text style={styles.header}>Hello, </Text>
{isParentInputVisible
? (<TouchableOpacity onPress={toggleParentInput}>
<Text style={styles.headerLink}>{state.parentName}</Text>
</TouchableOpacity>)
: (<TextInput
value={state.parentName}
style={styles.parentInput}
onChangeText={(value) => {updateParentName(value);}}
onSubmitEditing={toggleParentInput}
/>)}
</View>;
And, as you can see, I use a Context to store my parentName value. I also set this var to parent as my default value.
Now, the problem that I am having and, that is driving me crazy, is I can't change the value in this Input field to anything. When I try to enter a new value there is returns undefined. When I remove onChangeText prop from that input for the sake of testing, it doesn't allow me to change the default value.
Can anyone point me to what should I do to fix it? I couldn't find anything useful that would help me.
UPDATE:
AuthContext file:
import createDataContext from './createDataContext';
const authReducer = (state, action) => {
switch (action.type) {
case 'update_parent_name':
return { ...state, parentName: action.payload };
default:
return state;
}
};
const updateParentName = (dispatch) => ({ parentName }) => {
dispatch({ type: 'update_parent_name', payload: parentName });
};
export const { Provider, Context } = createDataContext(
authReducer,
{
updateParentName,
},
{
parentName: 'parent',
}
);
and, just in case createDataContext code:
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext(defaultValue);
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
The error is you've taken object as an argument in this function updateParentName but while calling the function through prop onChangeText you've passed the value as is without enclosing it in an object!
const updateParentName = (dispatch) => ({ parentName }) => {
dispatch({ type: 'update_parent_name', payload: parentName });
};
onChangeText={(value) => {updateParentName(value);}} //passed only value not in an object!
Just change the onChangeText prop as such
onChangeText={(value) => {updateParentName({parentName:value});}}

Required props for List and Edit components in route when using CustomApp

I'm trying to upgrade a custom app from admin-on-rest to react-admin (v2.15). After I figured out that the declareResources action was "replaced" by registerResource most seemed ok, but I still struggle with the List and Edit components route definitions that complains about missing required props (compared to what props are defined in the custom app documentation).
If I define a List component like this it works fine:
<Route exact path="/mystuffs" render={(routeProps) => <MystuffList hasCreate hasEdit hasShow={false} hasList resource="mystuffs" basePath="/mystuffs" {...routeProps} />} />
Similar the only way I can get an Edit-component to work is to pass the required props like so:
<Route exact path="/mystuffs/:id" render={(routeProps) => <MystuffEdit resource="mystuffs" id={routeProps.match.params.id} basePath="/mystuffs" {...routeProps} />} />
But to me it seems a bit tedious to define all of these props (i.e was not required with admin-on-rest). Is this the correct way of doing it or am I missing something obvious here since the custom app documentation doesn't specify all of the required props?
I ended up adding a custom resource component that is pretty similar to the Resource in react-admin. Something like this:
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
const RACustomResource = ({ resource, list, edit, create, show, path, children, ...rest }) => {
const { computedMatch, ...options } = rest;
const listResource = list ?
(<Route
exact
path={path}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(list, {
basePath: path || routeProps.match.url,
resource,
hasCreate: !!create,
hasList: !!list,
hasEdit: !!edit,
hasShow: !!show,
...routeOpts,
...options,
});
}}
/>)
: null;
const createResource = create ?
(<Route
path={`${path}/create`}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(create, {
basePath: path || routeProps.match.url,
resource,
hasList: !!list,
hasShow: !!show,
record: {},
...routeOpts,
...options,
});
}}
/>)
: null;
const editResource = edit ?
(<Route
exact
path={`${path}/:id`}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(edit, {
basePath: path || routeProps.match.url,
resource,
hasCreate: !!create,
hasList: !!list,
hasEdit: !!edit,
hasShow: !!show,
id: routeProps.match.params.id,
...routeOpts,
...options,
});
}}
/>)
: null;
const showResource = show ?
(<Route
exact
path={`${path}/:id/show`}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(show, {
basePath: path || routeProps.match.url,
resource,
hasCreate: !!create,
hasList: !!list,
hasEdit: !!edit,
hasShow: !!show,
id: routeProps.match.params.id,
...routeOpts,
...options,
});
}}
/>)
: null;
return (
<Switch>
{createResource}
{showResource}
{editResource}
{listResource}
{children}
</Switch>
);
};
RACustomResource.propTypes = {
resource: PropTypes.string.isRequired,
path: PropTypes.string,
basePath: PropTypes.string,
children: PropTypes.any,
list: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
create: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
edit: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
show: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
};
export default RACustomResource;
// used like this
// <RACustomResource path="/myresource" resource="myresource" list={MyResourceList} create={MyResourceCreate} edit={MyResourceEdit} />
It is indeed required. We have still a lot of work to do on the custom app side, including documentation.
You can help us! Can you explain why you needed to use react-admin this way? What wasn't possible using the default Admin? etc.
Thanks!