react-admin - Stop List's Filters from submitting "onChange" - react-admin

Is there a way to have a List's filters not submit automatically on every field's change? I'm trying to implement a reporting resource and it would be ideal to have the user set the filters they want and then submit the form, to have the report generated, as they tend to be heavy on the DB.
Thanks!

React-admin's <Filter> component does not submit after every field change, because it uses a debouncing function to submit only once the user has stopped typing.
That being said, if you don't want the filter form to auto-submit, but prefer a filter form with an explicit submit button, you'll have to build a custom <Filter> component yourself - react-admin doesn't provide such a component.
Here is an example custom filter form:
import * as React from 'react';
import { Form } from 'react-final-form';
import { Box, Button, InputAdornment } from '#material-ui/core';
import SearchIcon from '#material-ui/icons/Search';
import { TextInput, NullableBooleanInput, useListContext } from 'react-admin';
const PostFilterForm = () => {
const {
displayedFilters,
filterValues,
setFilters,
hideFilter
} = useListContext();
if (!displayedFilters.main) return null;
const onSubmit = (values) => {
if (Object.keys(values).length > 0) {
setFilters(values);
} else {
hideFilter("main");
}
};
const resetFilter = () => {
setFilters({}, []);
};
return (
<div>
<Form onSubmit={onSubmit} initialValues={filterValues}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Box display="flex" alignItems="flex-end" mb={1}>
<Box component="span" mr={2}>
{/* Full-text search filter. We don't use <SearchFilter> to force a large form input */}
<TextInput
resettable
helperText={false}
source="q"
label="Search"
InputProps={{
endAdornment: (
<InputAdornment>
<SearchIcon color="disabled" />
</InputAdornment>
)
}}
/>
</Box>
<Box component="span" mr={2}>
{/* Commentable filter */}
<NullableBooleanInput helperText={false} source="commentable" />
</Box>
<Box component="span" mr={2} mb={1.5}>
<Button variant="outlined" color="primary" type="submit">
Filter
</Button>
</Box>
<Box component="span" mb={1.5}>
<Button variant="outlined" onClick={resetFilter}>
Close
</Button>
</Box>
</Box>
</form>
)}
</Form>
</div>
);
};
This is documented at: https://marmelab.com/react-admin/List.html#building-a-custom-filter
Edit 2021-11-10: Added example and link to documentation

Related

Material UI TextField select not showing initial value on form edit

I have an edit form in a modal that lets you edit user info. Here is the relevant portion:
export const EditUserModal = (Props) => {
const [formValues, setFormValues] = useState(Props.userData);
const [reporterData, setReporterData] = React.useState<any[]>([]);
React.useEffect(() => {
let apiClient = new APIClient();
apiClient.getOwners().then((response) => {
setReporterData(response);
});
}, []);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormValues({
...formValues,
[name]: value,
});
};
return (
<>
<Modal
open={Props.show}
onClose={() => { Props.toggleModal(false)}}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
component="form"
autoComplete="off"
>
<Typography id="modal-modal-title" variant="h4" component="h4">
Edit User {Props.userData.name}
</Typography>
<div>
<TextField
required
id="outlined-select-basic"
select
name="reports_to"
label="Reports To"
value={formValues.reports_to}
onChange={handleInputChange}
>
{reporterData.map((option) => (
<MenuItem key={option.owner} value={option.owner}>
{option.owner}
</MenuItem>
))}
</TextField>
</div>
</Box>
</Modal>
</>
)
};
The TextField is supposed to show the initial value of {formValues.reports_to}, but it remains blank (the value is still correct) until you make a new selection. reporterData just returns a list of names(string).
I have tried adding it as a defaultValue, which did not work.

Resetting a formik form in react-native on navigation

I am trying to create a React Native app that can create and edit entities.
For example, users.
I have a formik form that takes first and last name, as well as an email address.
If a user params is passed, then the form should take on those values.
However, it only works the first time. After that, the form keeps the first values.
How can I force the form to be reevaluated?
function FormScreen({ navigation, route }) {
const formikRef = React.createRef();
const initialValues = { firstName: "", lastName: "", email: "" };
if (route.params && route.params.user) {
if (route.params.user.firstName) {
initialValues.firstName = route.params.user.firstName;
}
if (route.params.user.lastName) {
initialValues.lastName = route.params.user.lastName;
}
if (route.params.user.email) {
initialValues.email = route.params.user.email;
}
}
const unsubscribeBlur = navigation.addListener("blur", (e) => {
console.log("form blur");
if (formikRef.current) {
console.log("form reset");
formikRef.current?.resetForm();
}
});
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Formik
innerRef={formikRef}
initialValues={initialValues}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, "Must be 15 characters or less")
.required("Required"),
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
email: Yup.string()
.email("Invalid email address")
.required("Required"),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
</View>
);
}
export default FormScreen;
I have code in there to reset the form on blur but the reset doesn't seem to do anything.
You can find the complete code in a snack.
https://snack.expo.io/#hackzilla/create-and-edit-with-formik
Because you are using a tab navigator. The home and form screen will not reMount when you toggle between them.So the formik will not load the new initialValues.In your way,you can reset the form using route params everytime the form screen is focused.But a better way is using a stack navigator,every time create a form using a new instance.
useFocusEffect(
React.useCallback(() => {
if (formikRef.current) {
console.log("form reset");
formikRef.current?.setValues(initialValues);
}
})
);
We can reset the form after submit, so that it will clear all the fields. Please try by replacing your submit method with below mentioned code.
onSubmit={(values, { setSubmitting, resetForm }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
// reset the fields after submit
resetForm();
}, 400);
}}

Cannot pass the fetch method information to the instance data

When I run the below code, the values inside the currencie variable are not displayed.
I cannot pass the fetch method information to the instance data. I would like to fetch the data and display it on the screen. If I use console.log I can see the data as expected.
<template>
<Page class="page">
<ActionBar title="Tela N°1" class="action-bar" />
<ScrollView>
<StackLayout class="home-panel">
<!--Add your page content here-->
<Label textWrap="true" text="Primeira tela criada usando NativeScript"
class="h2 description-label" />
<Button text="Segunda Tela" #tap="onButtonTap" />
<Button text="Terceira Tela" #tap="onButton" />
<ListView class="list-group" for="currencie in currenciess"
style="height:1250px">
<v-template>
<FlexboxLayout flexDirection="row" class="list-group-item">
<Label :text="currencie.name" class="list-group-item-heading" style="width: 60%" />
<Label :text="currencie.buy" class="list-group-item-heading"
style="width: 60%" />
</FlexboxLayout>
</v-template>
</ListView>
</StackLayout>
</ScrollView>
</Page>
</template>
<script>
import Second from "./Second";
import Third from "./Third";
const http = require("tns-core-modules/http");
const url = "https://api.hgbrasil.com/finance?key=c5239f9c";
export default {
data() {
return {
currenciess: []
};
},
mounted() {
fetch(url)
.then(response => response.json())
.then(results => {
this.currenciess = results.results.currencies.USD;
});
},
};
</script>
Change your mounted method. currenciess is an array while your results.results.currencies["USD"] is an object coming from API. I have created a playground for you here.
mounted() {
console.log("mounted");
fetch(url)
.then(response => response.json())
.then(results => {
this.currenciess.push(results.results.currencies["USD"]);
});
}

How do I make a responsive master-detail layout with react-router v4 as described in 'philosophy'

I am trying to make a responsive Master/Detail layout using react-router v4 as described here. The code it suggests is
const App = () => (
<AppLayout>
<Route path="/invoices" component={Invoices}/>
</AppLayout>
)
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
However I am unable to get something working based on this. There are a few things that I am not sure about:
1) What is this AppLayout component?
2) Which Layout component is it referring to and is this important?
3) I am assuming the Media tag refers to react-media?
4) Media query={PRETTY_SMALL} means something along the lines of Media query={{ maxWidth: 599 }}
5) There is no Router component anywhere, which I though was needed
6) There are no Link objects anywhere
The best I have come up with so far (on a project started with create-react-app) is
import Media from "react-media";
import React, { Component } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
const data = [
{
id: 1,
to: 'Mr. Smith',
amount: 10
},
{
id: 2,
to: 'Mrs. Jones',
amount: 100
}
]
const InvoicesNav = () => (
<div>
Nav Bar
</div>
)
const Dashboard = () => (
<div>
Dashboard
</div>
)
const Invoice = () => (
<div>
Invoice
</div>
)
const App = () => (
// <AppLayout>
<Route path="/invoices" component={Invoices} />
// </AppLayout>
)
const Invoices = () => (
// <Layout>
<div>
{/* always show the nav */}
<InvoicesNav />
<Media query={{ maxWidth: 599 }}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard} />
<Route path="/invoices/:id" component={Invoice} />
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard} />
<Route path="/invoices/:id" component={Invoice} />
<Redirect from="/invoices" to="/invoices/dashboard" />
</Switch>
}
</Media>
{/* </Layout> */}
</div>
)
export default App;
But this still doesn't really do anything that resembles a responsive Master/Detail layout and neither does anything in the samples for react-router as far as I can see. :(
That layout is just an idea of your app structure. They don't give much details about it hence you are free to use any content you want for unknown tags, vars, etc.
I couldn't exactly reproduce it because IMO <InvoicesNav /> shouldn't be always displayed because in the mobile view there is no place for it when you view Dashboard or an exact invoice. I assumed that <InvoicesNav /> is the left sidebar of invoices with their IDs.
I hope that you can find answers to your questions in this basic implementation of that philosophy github.com/vogdb/react-router-mediaquery-example. Also there is another very detailed example https://github.com/AWebOfBrown/React-MQL-Manager.
import Media from "react-media";
import React, {Component} from 'react';
import {Route, Switch, Link} from 'react-router-dom';
const InvoiceList = (props) => {
return <ul>
<li key="dashboard">
<Link to="/invoices/dashboard"><span>Dashboard</span></Link>
</li>
{props.invoices.map((invoice) =>
<li key={invoice.id}>
<p><Link to={`/invoices/${invoice.id}`}><span>To:</span>{invoice.to}</Link></p>
<p><span>Amount:</span>{invoice.amount}</p>
<p><span>Paid:</span>{invoice.paid ? 'Yes' : 'No'}</p>
<p><span>Due:</span>{invoice.due.toDateString()}</p>
</li>
)}
</ul>
};
const Dashboard = (props) => {
const balance = props.invoices.reduce((sum, invoice) => sum + invoice.amount, 0);
const unpaidNum = props.invoices.reduce((num, invoice) => num + !invoice.paid, 0);
return <div>
Dashboard:
<div>Unpaid: {unpaidNum}</div>
<div>Balance: {balance}</div>
</div>
};
const Invoice = (props) => {
const id = parseInt(props.match.params.id);
const invoice = props.invoices.find(invoice => invoice.id === id);
return <div>
Invoice #{id}, to: {invoice.to}
</div>
};
const Layout = (props) => (
<div className="invoicesLayout">
{props.children}
</div>
);
const InvoicesSmallScreen = (props) => {
const {invoices} = props;
return (<div>
<Link to="/invoices">Show Invoices</Link>
<Switch>
<Route exact path="/invoices" render={props => <InvoiceList invoices={invoices} {...props}/>}/>
<Route exact path="/invoices/dashboard" render={props => <Dashboard invoices={invoices} {...props}/>}/>
<Route path="/invoices/:id" render={props => <Invoice invoices={invoices} {...props}/>}/>
</Switch>
</div>)
};
const InvoicesBigScreen = (props) => {
const {invoices} = props;
return (<div>
<InvoiceList invoices={invoices}/>
<Switch>
<Route exact path="/invoices/dashboard" render={props => <Dashboard invoices={invoices} {...props}/>}/>
<Route path="/invoices/:id" render={props => <Invoice invoices={invoices} {...props}/>}/>
</Switch>
</div>)
};
class Invoices extends Component {
constructor(props) {
super(props);
this.state = {
invoices: []
}
}
componentDidMount() {
// generate some dummy data here
const data = [];
this.setState({invoices: data})
}
render() {
const {invoices} = this.state;
return (
<Layout>
<Media query={{maxWidth: 599}}>
{screenIsSmall => screenIsSmall ?
<InvoicesSmallScreen invoices={invoices}/>
: <InvoicesBigScreen invoices={invoices}/>
}
</Media>
</Layout>
)
}
}
export default Invoices;
I recently tried a similar thing, and ended up with the following higher order component, where you pass in a master and a detail component:
import React from 'react';
import { Route, Switch, useRouteMatch } from 'react-router-dom';
import Media from 'react-media';
import { mediaQueries } from 'model';
import './MasterDetail.scss';
export const masterDetailHOC = <X,Y>(
MasterComponent: any,
DetailComponent: any,
masterProps?: X, detailProps?: Y) => {
return function(props: any) {
let { path } = useRouteMatch() as any;
return (
<Media query={mediaQueries.md}>
{matches =>
matches ? (
<Switch>
<Route exact path={`${path}`}>
<MasterComponent {...props} {...masterProps}
data-test="Master" />
</Route>
<Route path={`${path}/detail/:id`}>
<DetailComponent {...props} {...detailProps}
data-test="Detail" />
</Route>
</Switch>
) : (
<section className="master-detail">
<section className="master-detail__master">
<Route path={`${path}`}>
<MasterComponent {...props} {...masterProps}
data-test="Master" />
</Route>
</section>
<section className="master-detail__detail">
<Switch>
<Route exact path={`${path}`}>
<DetailComponent {...detailProps}
data-test="Detail" />
</Route>
<Route path={`${path}/detail/:id`}>
<DetailComponent {...props} {...detailProps}
data-test="Detail" />
</Route>
</Switch>
</section>
</section>
)
}
</Media>
);
}
};
If you like, I wrote an accompanying blog post with more detail here + the code can be found on github.

Editing a Material UI row within React + Redux

I have a material UI table in a react project, and I want a user to click on the pencil/Edit Icon to edit the table row.
This is a loaded question, but would this require an additional piece of Material UI? what would the logic look like to make this happen?? I have most of the code set up on the backend, but don't know how to write this code in the component???
Thanks for looking!
//STYLE VARIABLE BOR MATERIAL BUTTON
const style = {
margin: 12
//
};
const mapStateToProps = (state) => ({
user: state.user,
reduxState: state.getExpense
});
class ExpenseTable extends Component {
constructor(props) {
super(props);
this.state = {
getExpense: []
};
}
isSelected = (index) => {
return this.state.selected.indexOf(index) !== -1;
};
handleRowSelection = (selectedRows) => {
this.setState({
selected: selectedRows,
});
};
//on page load, DISPATCH GET_EXPENSE is
//SENT TO expenseSaga which then
//goes to getExpenseReducer and appends EXPENSE_DATA to the
//DOM
componentDidMount() {
const { id } = this.props.match.params;
this.props.dispatch({ type: USER_ACTIONS.FETCH_USER });
this.props.dispatch({ type: 'GET_EXPENSE' });
}
componentDidUpdate() {
if (!this.props.user.isLoading && this.props.user.userName ===
null) {
this.props.history.push('home');
}
}
logout = () => {
this.props.dispatch(triggerLogout());
// this.props.history.push('home');
};
//SETS STATE FOR ALL INPUTS
handleChange = (name) => {
return (event) => {
this.setState({
[name]: event.target.value
});
};
};
//SUBMIT BUTTON- TRIGGERS DISPATCH TO EXPENSE SAGA TO ADD DATA
handleClick = () => {
console.log('add expense', this.state);
this.props.dispatch({
type: 'ADD_EXPENSE',
payload: this.state
});
};
//TRASH ICON-TRIGGERS DISPATCH TO EXPENSE SAGA DELETE
handleClickRemove = (id) => {
console.log('delete expense', this.state);
this.props.dispatch({
type: 'DELETE_EXPENSE',
payload: id
});
};
handleClickEdit = (row) => {
console.log('edit expense', this.state)
this.props.dispatch({
type: 'EDIT_EXPENSE',
payload: row
})
}
render() {
console.log('HEY-oooo expense render', this.state);
let content = null;
if (this.props.user.userName) {
//MAP OVER REDUX STATE.
const tableRows = this.props.reduxState.map((row) => {
//.MAP SEPARATES DATA INTO INDIVIDUAL ITEMS.
const { id, item_description, purchase_date,
item_price, item_link } = row;
return (
<TableRow selectable={false}>
{/* TABLE ROWS */}
<TableRowColumn>{item_description}
</TableRowColumn>
<TableRowColumn>{purchase_date}
</TableRowColumn>
<TableRowColumn>${item_price}</TableRowColumn>
<TableRowColumn><a href={item_link}>{item_link}
</a></TableRowColumn>
<TableRowColumn><EditIcon class="grow:hover"
onClick={() => {this.handleClickEdit(row)}} />
</TableRowColumn>
<TableRowColumn><TrashIcon onClick={() =>
{this.handleClickRemove(id);
}}/>
</TableRowColumn>
</TableRow>
// END TABLE ROWS
);
});
<div>
{/* FORM FOR ADDING EXPENSES(DATA) */}
<form id="expenseForm">
<h3>
Add a new <br />
expense
</h3>
<input
type="text"
id="fname"
name="fname"
placeholder="Item description"
onChange=
{this.handleChange('item_description')}
/>
<br />
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item price"
onChange={this.handleChange('item_price')}
/>
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item link"
onChange={this.handleChange('item_link')}
/>
<br />
{/* END FORM */}
<RaisedButton
id="expSubmit"
label="Submit Expense"
primary={true}
style={style}
onClick={this.handleClick}
/>
{/* TABLE TOTAL KEEPS CURRENT TOTAL OF PRICE COLOUMN */}
<h1>Total:</h1>
<br />
<h3>$748.93</h3>
</form>
{/* TABLE HEADERS */}
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>Item
description</TableHeaderColumn>
<TableHeaderColumn>Purchase
Date</TableHeaderColumn>
<TableHeaderColumn>Item
Price</TableHeaderColumn>
<TableHeaderColumn>Item
Link</TableHeaderColumn>
<TableHeaderColumn>Edit
entry</TableHeaderColumn>
<TableHeaderColumn>Delete
entry</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody>
{tableRows}
</TableBody>
</Table>
</div>
);
}
return (
<div>
<Nav />
{content}
</div>
);
}
}
// this allows us to use <App /> in index.js
export default connect(mapStateToProps)(ExpenseTable);
The default material-ui table doesn't have the feature of in-line editing. You will have to write your own wrapper to achieve this which can be time consuming. To overcome the time issue we can use some good existing libraries.
Here's one powerful library from DevExtreme for the same which i used, but go through their licensing before you take the final decision to use.
https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/controlled-mode/