When wrapping an antd table column render function with observer, I get an error of render is not a function. I'm trying to avoid the need to every time create a separate observer wrapped functional component that I call from a column render function. Anybody had any luck or knows how to directly wrap a column render function with an observer (or an observer like implementation)?
Here is the typescript code:
import { observer } from "mobx-react-lite";
import { Button } from "antd";
import { ColumnsType } from "antd/lib/table";
import { TableDataSourceRecord } from "../models/TableDataSourceRecord";
const columns: ColumnsType<TableDataSourceRecord> = [
{
title: "View",
dataIndex: "url",
key: "url",
render: observer((url: any, _record: TableDataSourceRecord) => {
return (
<Button type="link" href={url.value}>
View
</Button>
);
}),
},
];
Thanks
Since render expects a function you can't pass React.Component to it, I guess (observer creates component).
Have you tried <Observer> wrapper component?
import { Observer } from "mobx-react-lite";
import { Button } from "antd";
import { ColumnsType } from "antd/lib/table";
import { TableDataSourceRecord } from "../models/TableDataSourceRecord";
const columns: ColumnsType<TableDataSourceRecord> = [
{
title: "View",
dataIndex: "url",
key: "url",
render: (url: string, _record: TableDataSourceRecord) => {
return (
<Observer>
{() => (
<Button type="link" href={url}>
View
</Button>
)}
</Observer>
);
},
},
];
Anyway, there is no point in having Observer in your example since you have no observable values there, url is just string primitive.
Related
In my React-Admin app, I'd like to leverage react-hook-form's useFormContext for various things, such as, for example, setting the default pre-selected choice in this custom input field:
...
import {
Create, SimpleForm, SelectInput
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
const MyInput = () => {
const formContext = useFormContext();
formContext.setValue('category', 'tech');
return (
<SelectInput source="category" choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'people', name: 'People' },
]}
/>
);
};
...
const ItemCreate = () => {
return (
<Create>
<SimpleForm>
<MyInput />
</SimpleForm>
</Create>
);
};
...
This sets the pre-selected value of the field, just as intended. But it throws a warning: Cannot update a component ("Form") while rendering a different component ("MyInput")...
Is there some way to achieve this without getting the warning?
Note: The only reason I'm using a custom input field here is because when I put useFormContext() directly into the component that contains SimpleForm it returns null (similarly described here).
The warning is related to the fact that the entire body of the MyInput() function is executed during each render, you need to call the setValue() function inside the useEffect hook.
Got this working by moving formContext.setValue into a useEffect hook:
...
import {
Create, SimpleForm, SelectInput
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
const MyInput = () => {
const formContext = useFormContext();
// moved the setValue into a useEffect
useEffect(() => {
formContext.setValue('category', 'tech');
});
return (
<SelectInput source="category" choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'people', name: 'People' },
]}
/>
);
};
...
const ItemCreate = () => {
return (
<Create>
<SimpleForm>
<MyInput />
</SimpleForm>
</Create>
);
};
...
I am using antd Select and react hook form via 'Controller'. I am populating the Select options from a fetched data with structure;
{
{
"id": "232342",
"term": "hello"
}
{
"id": "232342",
"term": "hello"
}
}
the Select component properly displays the term for selection. However, i want to retrieve both the 'id'and 'term' of the selected and use it to populate another json object.
getValues(" ") retrieves the 'id' only. How do i retrieve and access both the 'id' and 'term'.
Here is a portion of code:
import React from 'react'
import { useForm, Controller } from 'react-hook-form'
import { Select } from 'antd'
const { Option } = Select
export default function PatientRegistrationForm({ options }) {
const { register, handleSubmit, getValues, control, formState: { errors } } = useForm({
defaultValues: {
customer: "",
}
})
const children = []
for (const {id, pt: {term}} of options){
children.push(<Option key={id}>{term}</Option>)
}
// Define the retrieved values from the form
const retrievedID = getValues("customer")
// Use the retreived values to populate this object
const customer = {
customerId = retrievedID
customerName = "nothing happens here"
},
return (
<div className="">
<form onSubmit={handleSubmit(onSubmit)} className="">
<section>
<Controller
control={control}
name="customer"
render={({ field }) => (
<Select {...field} defaultValue=""
bordered={true}
filterOption={true}
className="form-control"
>
{ children }
</Select>
)}
/>
</section>
</form>
</div>
);
}
Thanks in advance for your help.
You have to retrieve the option manually using something like:
const retrievedID = getValues("customer")
const retrievedOption = options.find(option => option.id === retrievedID)
const customer = {
customerId: retrievedID,
customerName: retrievedOption.term
}
thank you #sgarcia.dev for your answer. I know its been a while but i want to put it out here incase it helps someone else. It turns out it had little to do with react hook form. Ant design select component has a prop 'labelInValue' which returns an object containing both the label and value.
I am using a matTable with a matTableDataSource to generate a data table that allows each row to be selected or deselected by checkbox in the first column of each row.
When a form containing the table is submitted, currently all rows are being submitted regardless if selected or not.
Is it possible to limit the data submitted to only those rows that are selected? Here is an example of the current data on submit:
myTableData: [
{
"selected": true,
"dataCell1": "9279694138",
"dataCell2": "Some value",
"dataCell3": "00010-00/8.1"
},
{
"selected": false,
"dataCell1": "5730371160",
"dataCell2": "Some value",
"dataCell3": "00010-00/8.2",
},
{
"selected": true,
"dataCell1": "1234567890",
"dataCell2": "Some value",
"dataCell3": "00010-00/8.4"
},
etc...
]
In the example above, since the second record was not selected by the end user ("selected": false), I would like it to be removed from the table's submitted data object.
Does the mat-table have a method to accomplish this?
I don't think there is anything built in to the mat-table to achieve this. You would need to filter the data manually before submission. They do document a case for adding selection to a mat-table: https://material.angular.io/components/table/overview#selection
Even with this, you would need to add manual filtering before submitting.
This is easily achieved by something like this:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-component',
templateUrl: './app.component.html'
})
export class AppComponent {
myTableData: any[] = [...];
constructor(
private http: HttpClient
) { }
submit() {
const data = this.myTableData.filter((item) => item.selected);
if (data && data.length) {
this.http.post("/my/api/endpoint", data).subscribe()
}
}
}
I am using redux-observable with rxjs's tap operator to initialize an ipcRenderer event. Before of that I want to dispatch another action. The question is how to proper pass the payload to the tap operator? -
Note: I want to keep the tap operator at the end of sequence
I have tried to pass it in the action's payload but It is not what I want.. check the example
import { ipcRenderer } from 'electron';
import { map, tap, ignoreElements } from 'rxjs/operators';
import { ofType } from 'redux-observable';
import { pipe } from 'rxjs';
import { togglePackageLoader } from 'models/ui/actions'
import { viewPackageStart} from '../actions';
// I want to pass payload to tap
/* togglePackageLoader returns {
type: 'TOGGLE_LOADER',
payload: {
loading: true,
options: {
opt1: value1,
}
}
}
// i want to avoid passing options to toggleLoader payload..
*/
const viewPackageEpic = pipe(
ofType(viewPackageStart.type),
map(({ payload }) => togglePackageLoader({
loading: true,
options: payload
})),
tap(({ payload: { options } }) => {
ipcRenderer.send('npm-view', options)
}),
ignoreElements()
);
export { viewPackageEpic };
I expected to dispatch toggleLoader first then make the ipcRenderer call
I found a solution that suits my needs. I created another epic which catch the same type. Using ignoreElements after the tap operatator.
const viewPackageEpic = pipe(
ofType(viewPackageStart.type),
map(() => updatePackageLoader({
loading: true
})),
);
const viewPackageLoaderEpic = pipe(
ofType(viewPackageStart.type),
tap(({ payload: { options } }) => {
ipcRenderer.send('npm-view', options)
}),
ignoreElements()
);
i'm trying to understand how ControlValueAccessor work precisely.
I have studied the behavior of it with two different control component:
The first is suppose to provide a primitive value: a single number.
The second provide a complex object.
So in short:
class FirstControlComponent implements ControlValueAccessor {
// ...
value:number = 10;
writeValue(value: number) {
this.value = value;
}
// ...
}
class SecondControlComponent implements ControlValueAccessor {
// ...
value:any = {};
writeValue(value: any) {
this.value = value;
}
// ...
}
The ControlValueAccessor interface only specify a 'setter': writeValue, but no 'getter'.
So when i bind a Control to SecondControlComponent, something like:
this.form = this.builder.group({
controlName: this.builder.control(this.theObject) });
and later in the template:
<second-component ngControl='controlName'> <second-component>
Everything works just fine, because writeValue is called on init with a reference to the existing theObject object, so the control modify the same instance of the object (hope i'm clear)
BUT: if i do exactly the same thing with FirstControlComponent, because the value is not passed as a reference (cause it's a primitive), and because ControlValueAccessor do not provide a 'setter' the value in my control and the value in my host component are NOT kept in sync ...
Does this mean that we HAVE to pass Object and not primitive to custom control implementing the ControlValueAccessor? I Guess no, so i guess i must be misunderstanding something .. :)
I'm using it the right way ?
Any hints are welcome !
Thanks !
It's not clear to me what you try to do here but ControlValueAccessor is an entity that you need to register for your element. Something like that:
const CUSTOM_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => LabelsValueAccessor), multi: true});
#Directive({
(...)
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class LabelsValueAccessor implements ControlValueAccessor {
(...)
}
It will then take part of the value updates (from component and from the template). When you set a value within the component on the input (for example), the writeValue method is called in your value accessor. If you want to update the input value from the value accessor, you need to leverage the registered onChange callback by Angular2
See this article (section "NgModel-compatible component") for more details:
http://restlet.com/blog/2016/02/17/implementing-angular2-forms-beyond-basics-part-2/
In Parent Component I want output of form like this {someOtherControlName: any, controlName: number} so output of Child form must be number.also in update form (example) parentform.patchValue({someOtherControlName: '', controlNmae: 3}) value of formControl must set properly
#Component({
selector: 'parent',
template: `
<div [formGroup]="form">
<second-component [formControl]='form.controlName'> <second-component>
<div formControlName="someOtherControlName"></div>
export class ParentComponent {
parentForm = new FormGroup({
controlName: new FormControl(), <<<<<======important
someOtherControlName: new FormControl()
})
}
In ControlValueAccessor:
#Component({
selector: 'counter',
template: `
<div class="number-input" [formGroup]="form">
<input type="text" class="form-control text-center" formControlName="counter" />
</div>
`,
styleUrls: ['counter.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CounterComponent,
multi: true,
},
],
})
export class CounterComponent implements ControlValueAccessor, OnDestroy, OnInit {
form = new FormGroup({
counter: new FormControl(0, Validators.required),
})
private onChange: (value: any) => void = value => {}
private onTouched: () => void = () => {}
myValue: number
private onDestroy$: Subject<void> = new Subject()
ngOnInit() {
this.form.valueChanges
.pipe(
tap(value => {
this.onChange(typeof value === 'number' ? value : value.counter) <<<< important
this.onTouched()
}),
takeUntil(this.onDestroy$)
)
.subscribe()
}
}
writeValue(value: number | { counter: number }) {
this.myValue = typeof value === 'number' ? value : value.counter
this.form.setValue({ counter: value > 0 ? value : 0 })
}
registerOnChange(fn: () => {}) {
this.onChange = fn
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn
}
setDisabledState?(isDisabled: boolean): void {
// when the parent updates the
// state of the form control
if (isDisabled) {
this.form.disable()
} else {
this.form.enable()
}
}
ngOnDestroy(): void {
this.onDestroy$.next()
this.onDestroy$.complete()
}