Is it possible to disable expand on certain rows? - react-admin

I would like to create a DataGrid where only component that have a certain property can be expanded. For example:
comments: [
{ id: 0, author: 'a', text: 'no', responses:[2]},
{ id: 1, author: 'b', text: 'yes' },
{ id: 2, author: 'b', text: 'perhaps' }
]
I would like to display this array, but only first option would be expandable, since it's the only one that has responses. Is there a way of achieving that without rewriting the DataGrid?

Unfortunately no. I would suggest to display an empty state component instead.
Edit
Here is an extract from the documentation:
By default, <Datagrid> renders its body using <DatagridBody>, an internal react-admin component. You can pass a custom component as the body prop to override that default. Besides, <DatagridBody> has a row prop set to <DatagridRow> by default for the same purpose. <DatagridRow> receives the row record, the resource, and a copy of the <Datagrid> children. That means you can create custom datagrid logic without copying several components from the react-admin source.
My suggestion would be to copy the original <DatagridRow> component and add an isExpandable prop accepting a function which will be called with the row record to conditionnaly display the expand button.
You could then use this custom DatagridRow like this:
import MyDatagridRow from './MyDatagridRow`;
const MyDatagridBody = props => <DatagridBody {...props} row={<MyDatagridRow />} />;
const MyDatagrid = props => <Datagrid {...props} body={<MyDatagridBody />} />;
However, as we already have an isSelectable prop, I also suggest to open a new feature request issue on react-admin repository to add an isExpandable prop.

Related

Set form values with mobx-react-form using object/props

I'm using mobx-react-form and I need to fill a form with default values pulled from an object in my store. Unfortunately, if I try to use FormModel.$("email").set(object.email); inside my component mobx complains that I can't modify observed objects outside of an action and I exceed maxdepth.
Specifically my code looks like this (some details removed for clarity)
import React from 'react';
import ReactDOM from "react-dom"
import { observer } from "mobx-react-lite"
import validatorjs from 'validatorjs';
import MobxReactForm from 'mobx-react-form';
const fields = [{
name: 'email',
label: 'Email',
placeholder: 'Email',
rules: 'required|email|string|between:5,25',
// value: user.email,
}, …
]
const FormModel = new MobxReactForm({ fields }, { plugins, hooks }); //nothing exception here standard plugins/hooks
const UserForm = observer(({open, onClose, object}) => { //My component…object has fields with names email…
FormModel.$("email").set(object.email); //This works fine if I replace object.email with "foo"
return (<MobxInput field={FormModel.$("email")} fullWidth />);
});
export default UserForm;
Yes, I've checked the object has the appropriate fields (it's just a bare object passed in from parent …not even an observable object in this case).
My first approach was to simply put everything inside UserForm and simply fill the values in fields from object but when I do this typing doesn't work in the resulting form (I suspect that mobx is trying to observe an object created inside that observer and that doesn't work).
The problem is I need to use the same form sometimes with data suppled by a user object from my user store and sometimes with blank values to create a new user and I'm kinda stuck about how to do this now.
First of all, you can't do that:
const UserForm = observer(({open, onClose, object}) => {
// This won't really work very well
FormModel.$("email").set(object.email);
return (<MobxInput field={FormModel.$("email")} fullWidth />);
});
Because every time you change value in your input your whole UserForm component also rerenders (because it observes FormModel.$("email") value which just changed) and when it rerenders you instantly change new value to your old value from object. I am not sure why exactly you getting maxdepth error, but there might even be endless loop here as you can see in some cases. Modifying anything like that inside render is usually a bad practice. You need to use useEffect at least, or something like that.
I can't modify observed objects outside of an action
This happens because you need to do all mutations inside actions by default. You can configure it though, if you don't like it:
import { configure } from "mobx"
configure({
enforceActions: "never",
})
But it is better to stick with it, it might catch some unwanted behaviour.
I've made quick Codesandbox example with some of your code, it shows how you can make several forms and pass default values to them:
const UserForm = observer(({ object }) => {
const [FormModel] = useState(() => {
const fields = [
{
name: 'email',
label: 'Email',
placeholder: 'Email',
rules: 'required|email|string|between:5,25',
value: object?.email || ''
}
];
return new MobxReactForm({ fields }, { plugins });
});
return (
<form onSubmit={FormModel.onSubmit}>
<input {...FormModel.$('email').bind()} />
<p style={{ color: 'red' }}>{FormModel.$('email').error}</p>
<button type="submit">submit</button>
</form>
);
});
That is just one of many ways, it all depends what you need in the end.

What is renderRow ()

I went through React-Native docs to figure out what is renderRow() but for some reason I am unable to comprehend what does it say from Facebook React-Native docs
This what the official docs says
renderRow
(rowData, sectionID, rowID, highlightRow) => renderable
Takes a data entry from the data source and its ids and should return a renderable component to be rendered as the row. By default the data is exactly what was put into the data source, but it's also possible to provide custom extractors. ListView can be notified when a row is being highlighted by calling highlightRow(sectionID, rowID). This sets a boolean value of adjacentRowHighlighted in renderSeparator, allowing you to control the separators above and below the highlighted row. The highlighted state of a row can be reset by calling highlightRow(null).
[Question:] Can someone please explain me this with example?
ListView is deprecated, use FlatList instead with the equivalent renderItem method. This is responsible of the actual rendering of each row based on the data records:
const data = [
{ key: '1', label: 'foo' },
{ key: '2', label: 'bar' }
]
renderTheItem = ({item}) => {
return <Text>{item.label}</Text>
}
<FlatList
data={data}
renderItem={this.renderTheItem}
/>
And the rendered result will be something like this:
<View> --> coming from FlatList wrapper
<Text key="1">foo</Text> --> coming from the custom renderTheItem function
<Text key="2">bar</Text>
</View>
It is mandatory to either add a unique key prop for each data record, or define a keyExtractor function. Also important to destruct the item in the renderer function with ({item}) as it has other meta parameters as written in documentation of FlatList.
renderItem({ item, index, separators}) => {}

ArrayInput with SimpleFormIterator and conditional inputs

Let's say I have the following array in my create form
const CreateDefaults = {Inputs: [{id: "ID1", param: "a"},{id: "ID2", param: "b"}]};
and then I want to show extra TextInput only when id==="ID2"
export const MyCreate = withStyles(myStyles)(({ classes, ...props }) => (
<Create {...props}>
<SimpleForm defaultValue={CreateDefaults}>
<ArrayInput source="Inputs" {...props}>
<SimpleFormIterator {...props}>
<DisabledInput source="id" />
{/*this does not work*/}
{this.id === "ID2" ? (<TextInput source="ParamValue"/>) :null}
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Create>
));
How can I do something like that? I know that for the whole form, one can use FormDataConsumer. However, what can one do inside ArrayInput/SimpleFormIterator for each iteration?
How to access current object in iteration? I tried something like the answer given to the 'Custom Input do not receive record when inside ArrayInput' issue in the react-admin github page, but it still does not receive the record in custom input.
From the latest documentation here, you can see that if you use FormDataConsumer that is within an ArrayInput, you have a parameter called scopedFormData that you can use to get the current record and dereference that to get whatever fields you need. It usually also goes hand in hand with the getSource function you can use when setting the source within your FormDataConsumer.

React Native FlatList Not Re-Rendering after Asyncronous operation

I have an async function like this:
getDeals() {
if(this.props.user) {
this.setState({loading: true});
this.setState({deals: []});
var parameters = {
zip: this.props.user.zip,
sort: 'All',
category: this.props.selectedCategory,
company: this.props.user.company,
page: null,
user: this.props.user,
search: null
}
axios.post(`${constants.api}/grab-deals/`, parameters)
.then((response) => {
this.setState({totalDeals: response.data.length});
this.setState({deals: response.data, loading: false, refreshing: false});
this.forceUpdate();
})
}
}
And a FlatList component Like this:
<FlatList data={this.state.deals} style={{flex: 1, padding: 10}} extraData={this.state} keyExtractor={this.keyExtractor} renderItem={this.renderDeal.bind(this)} />
Here is the keyextractor:
keyExtractor = (item, index) => item.id;
When I call this.getDeals() the first time it works great. However when I call it a second time the axios call get's all of the correct data, but the flat list still keeps old data (it doesn't remove items that aren't in the new call).
How do I get the FlatList to always reflect the returned data?
Call this.getDeals() in componentWillUpdate() and update props?
I believe you confussing what props and state is for. Basically state is used for things that could change during the lifecycle of the component and props are kept immutable. I use them for behavior.
Unless you are changing the parameters for the getDeals function on the second call, see that all of the properties are based on the props, which are not always updated.
RN has a method called componentWillUpdate that is triggered with the new props which you can then be used to update the component itself. If you want to keep using props in your getDeals method, you will need to check if the props have changed (this happens when the parent updates the child with new props) and then trigger again the data fetch.
If this does not help, please post more code.
According to the docs you need to set the state.selected value
By passing extraData={this.state} to FlatList we make sure FlatList
itself will re-render when the state.selected changes. Without setting
this prop, FlatList would not know it needs to re-render any items
because it is also a PureComponent and the prop comparison will not
show any changes.

React native Accordion

I'm new to react native and I have been working on a small project where I have use for accordion component after searching I found this
which I tried to implement the thing is how can I make this component reusable cause I have different data for different components. I want to do this without using a JSON file as data source.
for example
getInitialState() {
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
return {
dataSource: ds.cloneWithRows(_.range(25)),
here in datasource i want to pass data for different components
can anyone help
The project that you are using was last updated 2 years ago. You may want to consider using this one instead: https://github.com/oblador/react-native-collapsible
There's also another project named react-native-accordion-wrapper, and you can customize it in any way you'd like, and it's so easy to use:
<Accordion
dataSource={[
{ title: 'header one', child: <Component1 /> },
{ title: 'header two', child: <Component2 /> },
]}
/>
you can also use another child component from this library named AccordionItem if you need more customization.