How to map a mongodb comparison (e.g. $ne) inside a <Filter /> input - mongodb

We are using some softDelete functionality for some of our mongodb collections (e.g. Users). The deletedAT prop of these collections is either null or a date. I need to use a filter input to show active (deletedAt = null) or inactive (e.g., deletedAt = ISODate("2019-07-18T20:45:45.340Z")) collection items.
This works for a <List /> filter prop
<List title="User List" {...props} filter={{ deletedAt: null }} >
OR
<List title="User List" {...props} filter={{ deletedAt: { $ne: null } }} >
It actually also works for the filterDefaultValues prop
<List title="User List" {...props} filterDefaultValues={{ deletedAt: null }} >
OR
<List title="User List" {...props} filterDefaultValues={{ deletedAt: { $ne: null } }} >
However, I cannot make this work inside a <Filter /> input e.g.,
export const UserFilter = (props) => (
<Filter {...props}>
<RadioButtonGroupInput source="deletedAt" choices={[
{ id: null, name: 'No' },
{ id: '$ne: null', name: 'Yes' },
]} alwaysOn />
</Filter>
)
I get errors like this Cast to date failed for value "$ne: null" at path "deletedAt" for model "User" I've tried a number of different syntaxes, including trying to pass as objects, strings, etc with no luck. I suspect there is a difference in the request between a filter input and the filter/filterDefaultValues props of <List />. I'm hoping for a nudge in the right direction for creating a filter input that will handle mongodb comparison query operators?

This ended up working:
<List title="User List" {...props} bulkActionButtons={<ConfirmBulkDelete />} {...paginationProps} filters={ <UserFilter /> } filterDefaultValues={{ deletedAt: { $type: 'null' } }} >
export const UserFilter = (props) => (
<SelectInput label="Status" source="deletedAt[$type]" choices={[
{ id: 'null', name: 'Active' },
{ id: 'date', name: 'Archived' },
]} alwaysOn />
</Filter>
)

Related

Formik - arrayfields -- validation handling

I am working with formik/material ui -- and yup validation. I am struggling to get validation showing/working on field arrays
my schema and validation looks like this currently for each field.
"fields": [
{
"type": "date",
"label": "Start Date",
"name": "startDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
},
{
"type": "date",
"label": "End Date",
"name": "endDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
}
]
I've seen on formik - they have some validation like this - but how do I apply it my code base for dates?
https://formik.org/docs/api/fieldarray
const schema = Yup.object().shape({
friends: Yup.array()
.of(
Yup.object().shape({
name: Yup.string().min(4, 'too short').required('Required'), // these constraints take precedence
salary: Yup.string().min(3, 'cmon').required('Required'), // these constraints take precedence
})
)
.required('Must have friends') // these constraints are shown if and only if inner constraints are satisfied
.min(3, 'Minimum of 3 friends'),
});
my fieldarray looks like this -- and I believe errors should appear under the field group -- the fields outer border goes red -- but it doesn't seem to work for when I null the date - like is required date working?
<>
<FieldArray
name={item.name}
onChange={event => {
console.log("event field array change", event)
}}
>
{({ insert, remove, push }) => (
<div className="field field-array">
<div className="row" key={0}>
{item.fields.map((ch, inx) => (
<span key={"x"+inx}>
<div className="col-x">
<Field
name={`${item.name}.${ch.name}`}
>
{({
field, // { name, value, onChange, onBlur }
form,
meta,
}) => (
<>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label={ch.label}
disablePast={ch.disablePast}
disableFuture={ch.disableFuture}
minDate={moment(ch.minDate)}
maxDate={moment(ch.maxDate)}
value={field.value? moment(field.value).format('YYYY-MM-DD'): moment().format('YYYY-MM-DD')}
{...field}
onChange={(value) => {
form.setFieldValue(field.name, value);
this.props.onHandle(field.name, value);
}}
renderInput={(params) => {
return (<TextField {...params} name={field.name} />)
}}
/>
</LocalizationProvider>
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</>
)}
</Field>
</div>
{inx === 0 &&
(<span></span>)
}
</span>
))}
</div>
</div>
)}
</FieldArray>
</>
I worked this out
"validation": yup.array().of( yup.object().shape({ firstName: yup.string().min(4, 'too short').required('Required'), lastName: yup.string().min(3, 'cmon').required('Required'), }) ).min(1, 'Minimum of 1 friends')
-- but in the display of errors had to check if it was an array or a string to avoid a render error
under the add more button to display array errors of the main list.
<FormHelperText
error={(form.errors[parent.name] && form.errors[parent.name].length > 0 ? true : false)}
>
{typeof form.errors[parent.name] === "string" &&
<>{form.errors[parent.name]}</>
}
</FormHelperText>
and under the fields - meta errors
{(getHelperVisibility(values, ch)) &&
<FormHelperText
error={meta.touched && (meta.error && meta.error.length > 0 ? true : false)}
>
{meta.error}
</FormHelperText>
}

Fomik FormGroup textfields

I am working on an application and I have made a form framework -- but there is a request to have dual fields.
Min and Max age
rather then having two fields -- "min_age" and "max_age" -- I think they would want an array field "age"
so instead of min_age: 18 and max_age: 33 --- I think they would want an array - age: [18, 33]
I have seen and implemented Radio and Checkbox groups.
-- but when I've tried to swap out the controlled field to a TextField -- the field is malfunctioning and not changing value.
this is what I have got -- as a textfield array group
<>
<FormGroup
row
name={item.name}
disabled={item.disabled}
{...field}
>
{
item.options.map((itm, j) => {
return (
<FormControlLabel key={j}
disabled={item.disabled}
control={
<div className="field field-text">
<TextField
fullWidth={false}
label={itm.label}
value={field.value[j]}
inputProps={{
maxLength: item.charLimit? item.charLimit:null,
autoComplete: item.autoComplete? item.autoComplete:"off"
}}
rows={(item.type === "comment") ? 6 : null}
multiline={(item.type === "comment") ? true : false}
/>
</div>
}
//label={itm.label}
onChange={(e, value) => {
//form.setFieldValue(item.name, value)
//this.props.onHandle(item.name, itm.value);
}}
/>
)
})
}
</FormGroup>
</>
and this is the radio group field that works just fine -- I've not seen any other example where textfields are controlled by the formgroup
<>
<RadioGroup
row
name={item.name}
{...field}
>
{
item.options.map((itm, j) => {
return (
<FormControlLabel key={j}
value={itm.value}
disabled={itm.disabled}
control={<Radio />}
label={itm.label}
onChange={(e, value) => {
//form.setFieldValue(item.name, value)
this.props.onHandle(item.name, itm.value);
}}
/>
)
})
}
</RadioGroup>
</>
I've tried to wrap tags around it - from this example -- but then the field_names are uncontrolled.
https://codesandbox.io/s/formik-multi-step-set-value-context-wrapper-sezzs?file=/src/App.js:2157-2449
:rf: : "111"
:rh: : "222"
age : 18
age_array : [18, 30]
button_field : "3"

Mui Autocomplete with inputLabelProps shrink true in custom theme

When using TextField we can add a custom theme to make the label shrink by default
MuiTextField: {
defaultProps: { InputLabelProps: { shrink: true } },
},
When using it with Autocomplete component.
<Autocomplete
fullWidth
options={options}
clearOnBlur
disablePortal
popupIcon={false}
id="combo-box-demo"
renderInput={params => {
return (
<TextField {...params} label="Movie" variant="standard" />
);
}}
/>
What I have tried is that we can add it in autocomplete renderInput props
renderInput={({ InputLabelProps, params })=> {
return (
<TextField {...params}
label="Movie" variant="standard"
InputLabelProps={{
shrink: true,
...InputLabelProps,
}}
/>
);
}}
but the problem is we need to pass it everytime we want to render the autocomplete and it could be easily forgotten.
It would be convenient if we can also make it default in custom theme.
MuiAutoComplete: {
defaultProps: { InputLabelProps: { shrink: true } },
},
is this possible?
I think you should pass shrink: true in the default props to InputLabel itself like that
MuiInputLabel: {
defaultProps: { shrink: true },
},

React-hook-form + dynamic form: Render element upon dropdown selection

I am working in form using react-hook-form. This form use useFieldArray, it has to be dynamic.
Right now is very simple, it contains a react-select component with a few options and a textfield that get rendered depending on the option that the user select on the select component.
The problem I have is that the textfield component renders when the state updates, which is correct until I add a new group of element to the form. Since the textfield is listening to the same state it doesn't matter which select I use to render the textfield element, it gets rendered in all groups.
I am looking a way to specify which textfield should be rendered when the user change the select.
I the sandbox you can see what I have done. To reproduce the problem click on the "Add"-button and you will see two areas, each one with a select component.
When you choose "Other" in the select component a textfield appears, but not only in the area where the select was changed but in all areas.
How can I avoid that behavior?
https://codesandbox.io/s/vibrant-fast-381q0?file=/src/App.tsx
Extract:
const [isDisabled, setIsDisabled] = useState<boolean>(true);
const { control, handleSubmit, getValues } = useForm<IFormFields>({
defaultValues: {
managerialPositions: [
{
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
}
]
}
});
useFieldArray implementation:
const {
fields: managerialPositionsFields,
append: managerialPositionsAppend,
remove: managerialPositionsRemove
} = useFieldArray({
name: "managerialPositions",
control
});
Here i update the state when the user select "Other title" in the select component:
const watchChange = (value?: number, i?: number) => {
let values: any = getValues();
if (values.managerialPositions[i].title === 3) {
setIsDisabled(false);
}
};
And here is where I render the button to create a new group of elements and the select component and the textfield that should be rendered if "isDisabled" is false.
{managerialPositionsFields.map((field, index) => {
return (
<Stack className="sectionContainer" key={field.id}>
<Stack horizontal horizontalAlign="space-between">
<StackItem>
<CommandBarButton
iconProps={{ iconName: "AddTo" }}
text="Add"
type="button"
onClick={() => {
managerialPositionsAppend({
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
});
}}
/>
</StackItem>
</Stack>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<StackItem>
<Label className="select-label requiredIkon">Title</Label>
<Controller
control={control}
name={`managerialPositions.${index}.title`}
render={({ field: { onChange, value, ref } }) => (
<>
<Select
className="react-select-container authoritySelect"
classNamePrefix="react-select"
placeholder="Select title"
options={titelList}
id={`managerialPositions.${index}.title`}
value={
titelList.find((g) => g.value === value)
? titelList.find((g) => g.value === value)
: null
}
onChange={(val) => {
onChange(val.value);
watchChange(val.value, index);
}}
/>
{
// this input is for select validation
<input
tabIndex={-1}
autoComplete="off"
style={{ opacity: 0, height: 0 }}
value={
titelList.find((g) => g.value === value)
? titelList
.find((g) => g.value === value)
.toString()
: ""
}
required={true}
//Without this console will get an error:
onChange={() => {}}
/>
}
</>
)}
/>
</StackItem>
{!isDisabled && (
<StackItem className="">
<Controller
name={`managerialPositions.${index}.otherTitle`}
control={control}
render={({
field: { onChange, name: fieldName, value }
}) => (
<TextField
label="Other title"
name={fieldName}
onChange={(e) => {
onChange(e);
}}
value={value}
/>
)}
/>
</StackItem>
)}
</Stack>
</Stack>
);
})}

React-Admin: <ReferenceField> is stuck in loading state, even though data fetch succeeds

I have problem with <ReferenceField>.
On my <List> page, I have rows with UserId and ToolId that I need reference. I want to show user name and tool code.
But these two columns get stuck in loading state, even though the dataProvider, GET_MANY request succeeded for users and tools.
When I console.log(data), it the data is all there but somehow it's not rendered into <ReferenceField>. And It no error is logged at all.
Any idea what am I doing wrong? Or what's happening?
ReferenceFields still loading
EDIT:
I have updated react-admin and now its not displaying loading bar, but just display nothing.
Here is code of List:
export const B_toolList = props => (
<List {...props}>
<Datagrid rowClick="edit">
<TextField source="id" />
<NumberField source="active" />
<NumberField source="time" />
<DateField source="createdAt" />
<DateField source="updatedAt" />
<ReferenceField source="UserId" reference="Users">
<TextField source="name" />
</ReferenceField>
<ReferenceField source="ToolId" reference="Tools">
<TextField source="code" />
</ReferenceField>
</Datagrid>
</List>
);
I also find out that if i try to inspect ReferenceField element with react-dev-tool, There are no values but just "Loading..." note.
But I dont know why... It looks to me that API response is OK.
API res:
Btools fetch:
(2) [{…}, {…}]
0:
ToolId: 1
UserId: 1
active: 1
createdAt: "2020-04-08T16:00:03.000Z"
id: 1
time: 1
updatedAt: "2020-04-08T16:00:03.000Z"
__proto__: Object
1:
ToolId: 1
UserId: 2
active: 1
createdAt: "2020-04-08T16:00:03.000Z"
id: 2
time: 1
updatedAt: "2020-04-08T16:00:03.000Z"
__proto__: Object
length: 2
__proto__: Array(0)
Than Users and Tools fetch:
(2) [{…}, {…}]
0: {id: 1, name: "Tomáš Princ", email: "p***#seznam.cz", createdAt: "2020-04-08T15:59:54.000Z", updatedAt: "2020-04-08T15:59:54.000Z"}
1: {id: 2, name: "jhfgjhfg", email: "admin#admin.cz", createdAt: "2020-04-08T18:44:16.000Z", updatedAt: "2020-04-08T18:44:16.000Z"}
length: 2
__proto__: Array(0)
[{…}]
0: {id: 1, code: "guhf", name: "jhfh", state: "ghf", free: 1, …}
length: 1
__proto__: Array(0)
I log this json data in DataProvider and return it like data: json
switch (type) {
case GET_LIST:
case GET_MANY:
case GET_MANY_REFERENCE:
console.log(json)
console.log(type)
if (!headers.has('content-range')) {
throw new Error(
'The Content-Range header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?'
);
}
console.log((headers.get('content-range')))
return {
data: json,
total:
//json.length,
parseInt(
headers
.get('content-range')
.split('/')
.pop(),
10
),
};
So it look like app have all data it needs, but for some reason still loading and waiting for something.
EDIT 2:
I tried little workaround by creating custom element, that load userId and toolId and fetches classic query get user/tool by ID for every row. It works.
const ToolCode = ({ record = {} }) => {
const dataProvider = useDataProvider();
const [tool, setTool] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
dataProvider.getOne('tools', { id: record.ToolId })
.then(({ data }) => {
setTool(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
})
}, []);
if (loading) return <Loading />;
if (error) return <Error />;
if (!tool) return null;
console.log("fetch tools")
return (
tool.code
)
};
const UserName = ({ record = {} }) => {
const dataProvider = useDataProvider();
const [user, setUser] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
dataProvider.getOne('users', { id: record.UserId })
.then(({ data }) => {
setUser(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
})
}, []);
if (loading) return <Loading />;
if (error) return <Error />;
if (!user) return null;
return (
user.name
)
};
const B_toolFilter = (props) => (
<Filter {...props}>
<TextInput label="Search" source="q" alwaysOn />
<TextInput label="Title" source="title" defaultValue="Hello, World!" />
</Filter>
);
export const B_toolList = props => (
<List {...props}>
<Datagrid rowClick="edit">
<TextField source="id" />
<NumberField source="active" />
<NumberField source="time" />
<DateField source="createdAt" />
<DateField source="updatedAt" />
<UserName source="UserId" />
<ToolCode source="ToolId" />
</Datagrid>
</List>
);
Now I can get tool code and user name by IDs form B_Tools, but its much less efficient than GET_MANY method used by ReferenceField..
Nwm. Whole time it was just about capital letters of Tools and Users in
<ReferenceField source="UserId" reference="Users">
and
<ReferenceField source="ToolId" reference="Tools">
I changes it to
<ReferenceField source="UserId" reference="users">
and
<ReferenceField source="ToolId" reference="tools">
Now it works!
I just still dont get why it fetched right resources... It is like it is not capital sensitive when it fetches data, but it is capital sensitive when it renders. I dont know.
Anyway, I would expect some kind of error message with mistake like this... not just right response and empty render.