Prevent react-admin from redirect to first page after each update? - react-admin

using react-admin i created an app with sample list with many pages then i ran it. if i opened another page in the list and chose to edit a row or to delete it, the task done but the list is redirected to the first page and this is not good for user experience. if the user want to review multiple rows and edit them this will oblige him to return to the page each time he made edit. i am not sure if this is a how to question or a bug or feature that should be posted in github. i tested it in multiple react-admin versions 3.6.0, 3.5.5, 3.0.0 and the same behavior appeared.
// in src/App.js
import * as React from "react";
import { Admin, Resource } from "react-admin";
import jsonServerProvider from "ra-data-json-server";
import CommentList from "./comments";
const dataProvider = jsonServerProvider("https://jsonplaceholder.typicode.com");
const App = () => (
<Admin dataProvider={dataProvider}>
<Resource name="comments" list={CommentList} />
</Admin>
);
export default App;
import * as React from "react";
import {
List,
Datagrid,
TextField,
ReferenceField,
EmailField
} from "react-admin";
import { Fragment } from "react";
import { BulkDeleteButton } from "react-admin";
import ResetViewsButton from "./ResetViewsButton";
const PostBulkActionButtons = props => (
<Fragment>
<ResetViewsButton label="Reset Views" {...props} />
{/* default bulk delete action */}
<BulkDeleteButton {...props} />
</Fragment>
);
const CommentList = props => (
<List {...props} bulkActionButtons={<PostBulkActionButtons />}>
<Datagrid rowClick="edit">
<ReferenceField source="postId" reference="posts">
<TextField source="id" />
</ReferenceField>
<TextField source="id" />
<TextField source="name" />
<EmailField source="email" />
<TextField source="body" />
</Datagrid>
</List>
);
export default CommentList;
import * as React from "react";
import { Button, useUpdateMany, useNotify, useUnselectAll } from "react-admin";
import { VisibilityOff } from "#material-ui/icons";
const ResetViewsButton = props => {
const notify = useNotify();
const unselectAll = useUnselectAll();
console.log(props.selectedIds);
console.log(props.basePath);
const [updateMany, { loading }] = useUpdateMany(
"comments",
props.selectedIds,
{ emails: "" },
{
onSuccess: () => {
notify("comments updated");
unselectAll("comments");
},
onFailure: error => notify("Error: comments not updated", "warning")
}
);
return (
<Button
label="simple.action.resetViews"
disabled={loading}
onClick={updateMany}
>
<VisibilityOff />
</Button>
);
};
export default ResetViewsButton;

You can use "useRedirect" If I am not getting you wrong. You want to redirect after edit info.
import { useRedirect } from 'react-admin';
const ResetViewsButton = props => {
const notify = useNotify();
const redirectTo = useRedirect();
const unselectAll = useUnselectAll();
console.log(props.selectedIds);
console.log(props.basePath);
const [updateMany, { loading }] = useUpdateMany(
"comments",
props.selectedIds,
{ emails: "" },
{
onSuccess: () => {
notify("comments updated");
unselectAll("comments");
redirectTo('/url');
},
onFailure: error => notify("Error: comments not updated", "warning")
}
);
return (
<Button
label="simple.action.resetViews"
disabled={loading}
onClick={updateMany}
>
<VisibilityOff />
</Button>
);
};
export default ResetViewsButton;

it was a bug. wait version 3.6.1

Related

React Native useContext results are undefined

I am trying to useContext provider in my midiMonitor function. The midi monitor function is a helper function. The issue is that the context results as undefined because it's not inside the profile context. I am trying to figure out how to give the midiMonitor access to the profileContext. I know it can be done if I import the helper function inside the home component however I don't want to import it in the home component because it has nothing the do with the home component.
Is there another way I can use the midiMonitor helper function and have access to the contents of the profileContext
const App = () => {
midiMonitor()
return(
<ProfileProvider>
<Home />
</ProfileProvider>
)
}
const Home = () => {
// some functions that have access to the Profile Provider
const {profileName} = useContext(ProfileContext)
return(
<View>
<Text>{profileName}</Text>
</View>
)
}
const midiMonitor = () => {
const {profileName} = useContext(ProfileContext)
if (profileName === 'default'){
// results are undefined. I know why but do not want to
//import in into the Home component as it has nothing to do with the home component
console.log('you are using default midi profile')
}
}
It would have been easier if you could "ProfileContext" and "ProfileProvider". Here is an example of the same. Hope this helps.
import { useState, createContext, useContext } from "react";
import ReactDOM from "react-dom/client";
const ProfileContext = createContext();
function Component1() {
const [user, setUser] = useState("Jesse Hall");
return (
<ProfileContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 user={user} />
</ProfileContext.Provider>
);
}
function Component2() {
return (
<>
<h1>Component 2</h1>
<Component3 />
</>
);
}
function Component3() {
return (
<>
<h1>Component 3</h1>
<Component4 />
</>
);
}
function Component4() {
return (
<>
<h1>Component 4</h1>
<Component5 />
</>
);
}
function Component5() {
const user = useContext(ProfileContext);
return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component1 />);

React Native Root Element, deciding on async call

I'm currently writing an App in React-Native, which also includes a login. I use AsyncStorage for saving the credentials. Now I want to show the user different Screens (Navigators) whether he is logged in or not.
To check if he is logged in, I check if there are credentials in the AsyncStorage, and the function to check this returns a promise. So now when I call the function in my component, it wont wait until the promise has resolved and I don't have any idea on how to solve. I tried with but this also failed. Maybe you have any idea. Below my code. Thanks
import 'react-native-gesture-handler'
import { NavigationContainer } from '#react-navigation/native'
import AppNavigation from './navigation/AppNavigation.js'
import { ThemeProvider, Text } from 'react-native-magnus'
import { useState, useEffect, useCallback, Suspense} from 'react'
import {React } from 'react'
import getNutrientsCompare from './utils/getNutrientsCompare.js'
import getLoginSession from './utils/getLoginSession.js'
import Login from './pages/Login.js'
import { ActivityIndicator } from 'react-native'
const wait = (timeout) => {
return new Promise(resolve => setTimeout(resolve, timeout));
}
const RootElement = () => {
const [result, setResult] = useState(null)
getLoginSession().then(data => {
[loginSessionState, setLoginSessionState] = useState("");
if (loginSessionState != null) {
setResult((
<ThemeProvider>
<NavigationContainer >
<AppNavigation />
</NavigationContainer>
</ThemeProvider>))
} else {
setResult((
<ThemeProvider>
<Login>
</Login>
</ThemeProvider>
))
}
})
return result
}
const App = () => {
return (
<Suspense fallback={<ActivityIndicator />}>
<RootElement />
</Suspense>
)
}
export default App
Give this a try
import { ActivityIndicator } from "react-native";
const RootElement = () => {
const [loggedIn, setLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
try {
const data = await getLoginSession();
if (data != null) {
setLoggedIn(true);
}
} catch (error) {
setLoggedIn(false);
}
setLoading(false);
})();
}, []);
return (
<>
{!loading ? (
loggedIn ? (
<ThemeProvider>
<NavigationContainer>
<AppNavigation />
</NavigationContainer>
</ThemeProvider>
) : (
<ThemeProvider>
<Login />
</ThemeProvider>
)
) : (
<ActivityIndicator size="large" color="#00ff00" />
)}
</>
);
};

Use the Datagrid component with custom queries - react-admin

Receive below errors, when using Datagrid component with custom queries. Below code works with react-admin ver 3.3.1, whereas it doesn't work with ver 3.8.1
TypeError: Cannot read property 'includes' of undefined
Browser's console info: List components must be used inside a <ListContext.Provider>. Relying on props rather than context to get List data and callbacks is deprecated and won't be supported in the next major version of react-admin.
Refer: https://marmelab.com/react-admin/List.html
#Tip: You can use the Datagrid component with custom queries:
import keyBy from 'lodash/keyBy'
import { useQuery, Datagrid, TextField, Pagination, Loading } from 'react-admin'
const CustomList = () => {
const [page, setPage] = useState(1);
const perPage = 50;
const { data, total, loading, error } = useQuery({
type: 'GET_LIST',
resource: 'posts',
payload: {
pagination: { page, perPage },
sort: { field: 'id', order: 'ASC' },
filter: {},
}
});
if (loading) {
return <Loading />
}
if (error) {
return <p>ERROR: {error}</p>
}
return (
<>
<Datagrid
data={keyBy(data, 'id')}
ids={data.map(({ id }) => id)}
currentSort={{ field: 'id', order: 'ASC' }}
basePath="/posts" // required only if you set use "rowClick"
rowClick="edit"
>
<TextField source="id" />
<TextField source="name" />
</Datagrid>
<Pagination
page={page}
perPage={perPage}
setPage={setPage}
total={total}
/>
</>
)
} ```
Please help!
Since react-admin 3.7, <Datagrid> and <Pagination> read data from a ListContext, instead of expecting the data to be injected by props. See for instance the updated <Datagrid> docs at https://marmelab.com/react-admin/List.html#the-datagrid-component.
Your code will work if you wrap it in a <ListContextProvider>:
import React, { useState } from 'react';
import keyBy from 'lodash/keyBy'
import { useQuery, Datagrid, TextField, Pagination, Loading, ListContextProvider } from 'react-admin'
export const CustomList = () => {
const [page, setPage] = useState(1);
const perPage = 50;
const { data, total, loading, error } = useQuery({
type: 'GET_LIST',
resource: 'posts',
payload: {
pagination: { page, perPage },
sort: { field: 'id', order: 'ASC' },
filter: {},
}
});
if (loading) {
return <Loading />
}
if (error) {
return <p>ERROR: {error}</p>
}
return (
<ListContextProvider value={{
data: keyBy(data, 'id'),
ids: data.map(({ id }) => id),
total,
page,
perPage,
setPage,
currentSort: { field: 'id', order: 'ASC' },
basePath: "/posts",
resource: 'posts',
selectedIds: []
}}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="name" />
</Datagrid>
<Pagination />
</ListContextProvider >
)
}
<ReferenceManyField>, as well as other relationship-related components, also implement a ListContext. That means you can use a <Datagrid> of a <Pagination> inside this component.
https://marmelab.com/react-admin/List.html#uselistcontext
Your code should look like this:
import React, { useState } from 'react';
import keyBy from 'lodash/keyBy'
import { useQuery, Datagrid, TextField, Pagination, Loading, ListContextProvider } from 'react-admin'
export const CustomList = () => {
return (
<ReferenceManyField reference="Your resource for pull the data" target="linked field">
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="name" />
</Datagrid>
</ReferenceManyField>
)
}

DatePicker input value not pass to Redux Form when submit

I'm using DatePicker with ReduxForm. However, when I click submit button, the input value from Date Picker not pass to Redux Form.
I've search around and come across the answer from this (my code of renderDatePicker comes from there) but it still doesn't work for me.
My demo of the form on my Simulator:
Here's my code:
import React, { Component } from 'react';
import {
View, Text, Button, Icon, Container, Item,
Input, Label, Content, Form, Picker, Footer, DatePicker
} from 'native-base';
import { Field, reduxForm } from 'redux-form';
import { addTransactionItem } from '../redux/ActionCreators';
import moment from 'moment';
import { connect } from 'react-redux';
const mapDispatchToProps = dispatch => ({
addTransactionItem: (transactionItem) => dispatch(addTransactionItem(transactionItem))
})
class AddTransaction extends Component {
constructor(props) {
super(props);
this.renderField = this.renderField.bind(this);
this.submit = this.submit.bind(this);
this.renderDatePicker = this.renderDatePicker.bind(this);
}
renderDatePicker = ({ input, placeholder, defaultValue, meta: { touched, error }, label ,...custom }) => (
<Item>
<Label>{label}</Label>
<DatePicker {...input} {...custom} dateForm="MM/DD/YYYY"
onChange={(value) => input.onChange(value)}
autoOk={true}
selected={input.value ? moment(input.value) : null} />
{touched && error && <span>{error}</span>}
</Item>
);
submit = values => {
alert(`The values are ${JSON.stringify(values)}`)
const transactionItem = JSON.parse(JSON.stringify(values))
this.props.addTransactionItem(transactionItem);
const { navigate } = this.props.navigation;
navigate('Home');
}
render() {
const { handleSubmit } = this.props
return (
<>
<Form>
<Field keyboardType='default' label='Date' component={this.renderDatePicker} name="date" />
</Form>
<Button full light onPress={handleSubmit(this.submit)}>
<Text>Submit</Text>
</Button>
</>
);
}
}
AddTransaction = connect(null, mapDispatchToProps)(AddTransaction);
export default reduxForm({
form: 'addTransaction',
})(AddTransaction);
I think this is because you do not have "change" attribute in the Field component.
Try to add change function as shown below:
renderDatePicker = (
{
input,
placeholder,
defaultValue,
meta: { touched, error },
label ,
...custom,
change
}
) => (
<Item>
<Label>{label}</Label>
<DatePicker {...input} {...custom} dateForm="MM/DD/YYYY"
onDateChange={change}
autoOk={true}
selected={input.value ? moment(input.value) : null} />
{touched && error && <span>{error}</span>}
</Item>
);
render() {
const { handleSubmit, change } = this.props
return (
<>
<Form>
<Field
keyboardType='default'
label='Date'
component={this.renderDatePicker}
name="date"
change={change}
/>
</Form>
<Button full light onPress={handleSubmit(this.submit)}>
<Text>Submit</Text>
</Button>
</>
);
}
Hope it will work for you.
I see that there is no onChange listener for DatePicker. May be you should use onDateChange. http://docs.nativebase.io/Components.html#picker-input-headref

Cannot read property `_root` of undefined on Native Base ActionSheet

I'm wrapping my main App component in a Native Base <Root> as the docs suggest.
It looks like this:
import {AppRegistry} from 'react-native';
import { Root } from 'native-base';
import App from './App';
import {name as appName} from './app.json';
const RootApp = () => (
<Root>
<App />
</Root>
);
AppRegistry.registerComponent(appName, () => RootApp);
Then i'm trying to trigger the ActionSheet like this:
<Button transparent onPress={
() => ActionSheet.show({
options: this.BUTTONS,
cancelButtonIndex: this.CANCEL_INDEX,
destructiveButtonIndex: this.DESTRUCTIVE_INDEX,
title: i18n.t("settings")
},
buttonIndex => {
alert('Logout was clicked ' + buttonIndex);
}
)}>
</Button>
And it throws Cannot read property _root of undefined
I although would like to have the Button to call it like this:
<Button onPress={ () => this.openSettings }></Button
And openSettings function looking like this:
openSettings() {
ActionSheet.show({
options: this.BUTTONS,
cancelButtonIndex: this.CANCEL_INDEX,
destructiveButtonIndex: this.DESTRUCTIVE_INDEX,
title: i18n.t("settings")
},
buttonIndex => {
alert('Logout was clicked ' + buttonIndex);
}
)
}
But again, didn't work.
Any suggestions?
React-Native version: 0.57.8
Native-Base version: ^2.10.0
#msqar
I think it's an installation problem. there might be native-base not installed properly.
It's work properly in my project.
I'm going to share my code maybe it helps you.
go through it.
package.json looks like this.
index.js File
import React from "react";
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import { Root } from "native-base";
const RootApp = () => (
<Root>
<App />
</Root>
);
AppRegistry.registerComponent(appName, () => RootApp);
App.js File
import React, { Component } from "react";
import {
Container,
Header,
Button,
Content,
ActionSheet,
Text
} from "native-base";
var BUTTONS = ["Option 0", "Option 1", "Option 2", "Delete", "Cancel"];
var DESTRUCTIVE_INDEX = 3;
var CANCEL_INDEX = 4;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
openSettings = () => {
ActionSheet.show(
{
options: BUTTONS,
cancelButtonIndex: CANCEL_INDEX,
destructiveButtonIndex: DESTRUCTIVE_INDEX,
title: "Testing ActionSheet"
},
buttonIndex => {
alert("logout was clicked" + buttonIndex);
}
);
};
render() {
return (
<Container>
<Header />
<Content padder>
<Button
transparent
onPress={() =>
ActionSheet.show(
{
options: BUTTONS,
cancelButtonIndex: CANCEL_INDEX,
destructiveButtonIndex: DESTRUCTIVE_INDEX,
title: "settings"
},
buttonIndex => {
alert("Logout was clicked " + buttonIndex);
}
)
}
>
<Text>Actionsheet1</Text>
</Button>
<Button transparent onPress={() => this.openSettings()}>
<Text>Actionsheet2</Text>
</Button>
</Content>
</Container>
);
}
}
I have covered your both approach this code.
Screenshot of outputs: