I want to map over values and return an instance of my <FirstRepeatAttributeLabelAssistant /> for every label (basically to render an label above each input, number of inputs can vary).
I've started off with this:
{object.attributeCollection.questions.map((question) => (
<FirstRepeatAttributeLabelAssistant />
))}
The output of the map above is like so:
[StringAttributeModel, MemoAttributeModel, LabelAttributeModel, MemoAttributeModel, StringAttributeModel, StringAttributeModel] I only care about the "StringAttributeModel" as each of those 3 include 3 different labels (the part I care about). Their structure is like so:
How can I update my logic to ensure all of the potential labels are covered and a <FirstRepeatAttributeLabelAssistant /> component is rendered for each of them?
Suppose all your elements are a component, then you can simply do.
{object.attributeCollection.questions.map(Comp => {
...
return <Comp label="..." />
})}
You can fill whatever your custom logic in the ... area.
Related
So I need a ReferenceField to access data from another table. Since I am doing this often i extracted this in a custom component
CustomField.tsx
const CustomField = (props: any) => {
const record = useRecordContext();
return (
<ReferenceField
record={record}
source="someId"
reference="table"
link="show"
label="some label"
{...props}
>
<TextField source="name" />
</ReferenceField>
);
};
now when i use the component:
<CustomField/>
everithing is working fine, data is displayed fine, except no label is shown.
So I am expecting some form of label at the top but no signs.
If I do it without creating a custom field, everything is working just fine, the label and sorting is there. But when I extract the code into a separate component it doesn't seem to work.
Looks like the attributes lose their default values and behavior when extracted to a separate component.
My current workaround
<OrganisationField label="Some label" sortBy="something" />
that is fine, it works but it's not practical (and it's annoying) to do this everytime I or someone else wants to use the component, since that should already be defined inside it.
When you say "no label is shown", I assume that's when you use your custom Field inside a Datagrid.
Datagrid inspects its children for their label prop to display the column header. To make your label inspect-able, declare it as defaultProp:
CustomField.defaultProps = {
label: "someId"
}
This is explained in the react-admin "writing a custom field" documentation: https://marmelab.com/react-admin/Fields.html#writing-your-own-field-component
I'm trying to create a custom layout component, so I can design my Show page better.
I'm not sure why some things work and some don't. My plan is to use the Material-UI <Grid> component. I know that the Grid component doesn't pass the props, then I'm extending to my own:
export default function OrderShow(props) {
return (
<Show {...props}>
<CustomGrid>
<TextField source="shortId" label="Short ID" />
</CustomGrid>
</Show>
);
}
My CustomGrid component, which is just a draft yet, is cloning its children to pass the props:
function CustomGrid(props) {
return React.Children.map(props.children, (child) => React.cloneElement(child, props));
}
When I use it, the TextField receives the source, and renders it correctly. However the label is gone. I'm not sure why this happens.
Another problem is that when I try to use the CustomGrid with a ReferenceField, it doesn't work at all:
<Show {...props}>
<CustomGrid>
<ReferenceField source="user" reference="users" label="Name" link="show">
<FunctionField render={(record) => `${record.firstName} ${record.lastName}`} />
</ReferenceField>
</CustomGrid>
</Show>
I can see that the props are passed until the ReferenceField, but it's lost before reaching the FunctionField:
Is there a better way to customize the SimpleShowLayout and continue using the TextField and other components from react-admin?
Yes, there is a way. Simply put react-admin's SimpleShowLayout is a little bit more coplex then just passing the props to the children fields. That's why you have to recursively iterate until you reach a field and perform the stuff the react-admin does.
Finally I have decided to share some privately developed components in a new package I am building. I have added layouts which work both with Box and Grid material-ui's components. You can have a look here: ra-compact-ui
In essence I have the following inside a react functional component:
<div className="content">
<div className="filters">
<MyFilter1 data={data}>
{(filter1Data) => {
return <MyFilter2 data={filter1Data}>
{(finalData) => {
return <div className="final-data">{finalData}</div>
}}
</MyFilter2>
}}
</MyFilter1>
</div>
<div className="filtered" />
I am using multiple filters with the render children paradigm to get the final data. But for (admittedly frustrating) styling reasons, I would like to render the final data in a div outside of the filters location (inside the 'filtered' div in this case). How can I do this? Is my approach wrong? Is the 'filter with render children' the wrong approach?
I have tried:
React portal with createRef(), in essence the problem ends up being that the ref is not available yet when the 'finalData' function is called.
Edit:
The filter components themselves are very complicated but I can give a couple examples. The data I'm filtering is a simple object with 12ish properties. One of the filters is implemented as a series of checkboxes determining which properties of the data I want to display (necessary as a separate component because of the number of fields). One of the properties on the data is a price, and another filter is a price range 'dialer', that specifies the price range to display ( aka exclude data points outside the range).
I have 2 components. In one component I render map of table rows =>
this is place, where tDs.map is rendered
this.state.dataOld.map(it =>(
<>
<tr onClick={()=>{it.selected_=!it.selected_;this.forceUpdate()}} value={it} key={it.id}>
{this.tDs.map(fnc => fnc(it,this.hide,this))}
</tr>
</>))
dataOld is array of 2 objects
tDs is array like
function(it,hide,a){return !hide.id ? <td>{it.id}</td> : null},
function(it,hide,a){return !hide.category3 ? <td>{it.category3}</td> : null},
function(it,hide,a){return !hide.edit ? <td>
<Button onClick={() => a.setState({edit:!a.state.edit,element:it})}>
</Button>
</td> : null}
the thing i interested in is a.setState({edit:!a.state.edit,element:it})}
I render another component inside of first
<Edit isOpen={this.state.edit} editBack={this.editBack} th={this} element={this.state.element}/>
I transfer my this.state.element to Edit, inside Edit component I'm doing something like this:
elem = {};
componentDidUpdate(){
this.elem = this.props.element
console.log(this.elem)
}
...
<Input onChange={ e => {this.elem.packagingType = e.target.value; console.log(e.target.value)} } ... />
so, the problem is: I transfer this.state.element to Edit component, then in Edit component I make new variable elem and make it be equal to this.props.elem I transferred
the problem starting here, the first, (original, natural) this.state.element is changing, but I dont change dataOld, I dont change any element that has been rendered, I really dont undestand, how it works here.
My guess was about this place
<Edit isOpen={this.state.edit} editBack={this.editBack} th={this} element={this.state.element}/>
I think that when I'm doing element={this.state.element} I somehow connect this 2 things and when I change element inside of Edit, the natural this.state.element is changing too.
Codesandbox example:
https://codesandbox.io/embed/7wk8689op6?fontsize=14
when you press a button near every row, then change data and press cancel, the original data is changing, I dont understand why
I think I understood the question.
When I put an object to the Edit component, and then change an object inside of component, the original object is changing too because I didn't make a new version of this object, but I gave to Edit a link to this object.
I have a fancy UI in which have a few 'panes' with dividers in between that let you change what each one does. Say I have two different components - a to-do list and a simple text editor. I want you to be able to change the component present in each pane to make a flexible UI. For example, I might want to change the pane on the left from a text editor to a to-do list. Assuming I have a parent element Pane, how could I replace one of its children with another?
<Pane>
<TextEditor /> /* I want to replace that with a <ToDoList /> when I press a button */
<SomeOtherComponentOnTheRight />
</Pane>
I've tried storing React.Children.toArray(this.props.children) in the <Pane />'s state (as this.state.currentChildren), and replacing the element there, but for some reason I can't find a way to get the index of <TextEditor /> in the <Pane />'s this.state.currentChildren because for some reason this.props.children does not preserve children's props, and so I can't transmit data through it.
Sorry if I've overcomplicated this, but I simply want to know how to change a component's children dynamically.
You could check the state in your JSX to change what is displayed such as:
<Pane>
{ this.state.showEditor ? <TextEditor/> : <ToDoList /> }
<SomeOtherComponentOnTheRight />
</Pane>
Elsewhere in your code you would have some button that invokes an onClick event handler that would set the state of 'showEditor' to true/false depending on the previous state.
You can store the selected components in an array or object, then assign the selected component to a variable (just make sure it starts with an uppercase letter) and then use it as a component:
const routes = {
a: TextEditor,
b: ToDoList
};
const ChosenComponent = routes['a']; // select your component and store in variable
return (
<Pane>
<ChosenComponent />
<SomeOtherComponentOnTheRight /> {/* render selected variable as component */}
</Pane>
);