Add a custom button to react-admin app bar - react-admin

In my react-admin app I'd like to add a custom button next to the "Refresh" button. Unfortunately I cannot find any information on how to do that.

Using the following method, you can add your own buttons before the "Refresh" button":
https://marmelab.com/react-admin/Theming.html#customizing-the-appbar-content
// MyAppBar.js
import React from 'react'
import { AppBar } from 'react-admin'
import Typography from '#material-ui/core/Typography'
import { withStyles } from '#material-ui/core/styles'
import IconButton from '#material-ui/core/IconButton'
import BackIcon from '#material-ui/icons/ArrowBack'
const styles = {
title: {
flex: 1,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
},
spacer: {
flex: 1,
},
}
const MyAppBar = withStyles(styles)(({ classes, ...props }) => (
<AppBar {...props}>
<Typography
variant="title"
color="inherit"
className={classes.title}
id="react-admin-title"
/>
<span className={classes.spacer} />
<IconButton color="inherit" >
<BackIcon/>
</IconButton>
</AppBar>
))
export default MyAppBar

Related

Is it possible to Get 'style' property values from React Native Element AFTER rendering using useRef?

I'm trying to dynamically apply margin & padding to a View, based on a ref'd TextInput's borderRadius. I am new to React coming from Xamarin where this type of thing is common.
I'm not sure if I have the correct approach, but I have seen some examples of people deriving style values from useRef.
Here is my custom LabelInput component:
import React, {useState} from 'react';
import {
View,
Animated,
StyleSheet,
ViewProps,
} from 'react-native';
import {Colors} from '../../../resources/colors';
import Text from './Text';
import TextInput from './TextInput';
import {TextInputProps} from 'react-native/Libraries/Components/TextInput/TextInput';
import {isNullOrWhiteSpace} from '../../../utils/stringMethods';
import {TextProps} from 'react-native/Libraries/Text/Text';
interface LabeledInputProps {
label: string;
error: string;
onChangeText: (text: string) => void;
placeholder?: string;
inputValue: string;
mask?: (text: string) => string;
validator?: (text: string) => string;
onValidate?: (value: string) => void;
viewProps?: ViewProps;
textProps?: TextProps;
errorTextProps?: TextProps;
inputProps?: TextInputProps;
}
export default function LabeledInput(props: LabeledInputProps) {
const inputRef = React.useRef<any>(null);
const [dynamicStyle, setDynamicStyle] = useState(StyleSheet.create({
dynamicContainer:{
marginHorizonal: 0,
paddingHorizonal: 0,
}
}));
const changeTextHandler = (inputText: string) => {
const displayText = props?.mask ? props.mask(inputText) : inputText;
props.onChangeText(displayText);
// ultimately not the exact behavior I'm after, but this is a simple example.
var test = inputRef.current.props.style;
// props.style always returns undefined,
// there doesn't appear to be a 'props' property on the 'current' object when debugging.
setDynamicStyle(StyleSheet.create({
dynamicContainer:{
marginHorizonal: test.borderRadius, // I want the padding/margin of this element to be
paddingHorizonal: test.borderRadius,// dynamically set based on the inputRef's borderRadius
}
}))
};
return (
<View
{...props.viewProps}
style={[
props.viewProps?.style,
localStyles.container,
]}>
<TextInput
ref={inputRef}
{...props.inputProps}
placeholder={props.placeholder}
style={localStyles.input}
onChangeText={changeTextHandler}
value={props.inputValue}
/>
<Animated.View
pointerEvents={'none'}>
<Text
{...props.textProps}
style={[props.textProps?.style, animatedStyles.label]}>
{props.label}
</Text>
</Animated.View>
{/* {stuff} */}
</View>
);
}
const localStyles = StyleSheet.create({
container: {
backgroundColor: 'blue',
justifyContent: 'flex-start',
flex: 1,
},
label: {
fontWeight: 'bold',
marginBottom: 8,
},
input: {
padding: 8,
},
error: {
backgroundColor: 'pink',
fontSize: 12,
paddingHorizontal: 8,
color: Colors.danger,
marginTop: 4,
},
});
const animatedStyles = StyleSheet.create({
label: {
fontSize: 16,
fontWeight: 'normal',
},
});
Here is my custom LabelInput component with forwardRef() implemented:
import React, {ForwardedRef, forwardRef} from 'react';
import {TextInput as NativeTextInput, TextInputProps} from 'react-native';
import {useGlobalStyles} from '../../../resources/styles';
const TextInput = (
props: TextInputProps,
ref: ForwardedRef<NativeTextInput>,
) => {
const styles = useGlobalStyles();
return (
<NativeTextInput
{...props}
ref={ref}
style={[styles.textInput, props.style]}
placeholderTextColor={styles.textInput.borderColor}
onChangeText={(text: string) => {
if (props.onChangeText) {
props.onChangeText(text);
}
}}
/>
);
};
export default forwardRef(TextInput);
I've tried referencing inputRef from different hooks, like useCallback & useEffect.
var test = inputRef.current.props.style; always returns undefined. And there doesn't appear to be a 'props' property on the 'current' object when debugging.
The link you mentioned contains two files with inputRef. Since inputRef is in parent component and use ref prop to pass inputRef, this will not work. ref is not available as prop. If you still want to use ref as prop, then use forward ref in child component as access the ref as second argument or you can use any other prop name to pass ref i.e. innerRef. You can read more in react documentation. Forward Refs
According to the code you attach in code sandbox, i think you are trying to access input styles in two components: App and LabeledInput. You should use one ref in main component and use it in LabelInput component. If you still want to have separate refs then you can ref callback function and attach the node with both refs.
const attachRef = (node: NativeTextInput) => {
inputRef.current = node;
ref.current = node;
};
return <TextInput ref={attachRef} />;
The correct type for inputRef.current is TextInputProps.
const inputRef = useRef() as MutableRefObject<TextInputProps>;
I have updated the code sandbox. I was able to access input field styles in both components. Hope this solves your problem.

NativeBase: Button does not work, but ReactNative's Button does

Experiencing a strange issue in my React Native project for Android.
Using React-Navigation, I have a component with a button inside. This button should navigate to a new screen.
Thing is, the built-in button of React Native works like a charm, while the button of Native Base does not. I am completely confused, even more because I use this Native Base Button in another location, too. And there it works fine.
What is going on here?
Here, you see the application works with the built-in React Native button:
On the opposite, using the button of Native Base, it not only does not work, even styles are not applied.
Here is the code with the React Native button:
import React from "react";
import { Button, View, Text, StyleSheet } from "react-native";
import { withNavigation } from "react-navigation";
type Props = { navigation: any };
const ButtonTestScreen: React.FC<Props> = ({ navigation }) => {
return (
<View>
<Button
title="Hi i am a button"
onPress={() => navigation.navigate("Details")}
></Button>
</View>
);
};
export default withNavigation(ButtonTestScreen);
And the code with Native Base button:
import React from "react";
import { Button, View, Text, StyleSheet } from "react-native";
import { withNavigation } from "react-navigation";
import ButtonNavigate from "../../components/atoms/ButtonNavigate/ButtonNavigate";
type Props = { navigation: any };
const ButtonTestScreen: React.FC<Props> = ({ navigation }) => {
return (
<View>
<ButtonNavigate
title="Hi i am a button"
navigateTo="Details"
></ButtonNavigate>
</View>
);
};
const styles = StyleSheet.create({
button_style: {
backgroundColor: "red"
},
text_style: {
color: "#000",
fontSize: 30
}
});
export default withNavigation(ButtonTestScreen);
And the respective ButtonNavigate component itself:
import React from "react";
import { StyleSheet } from "react-native";
import { withNavigation } from "react-navigation";
import { Button, Text } from "native-base";
type Props = {
title: string,
navigateTo: string,
navigation: any
};
const ButtonNavigate: React.FC<Props> = ({ title, navigateTo, navigation }) => {
return (
<Button
rounded
transparent
style={styles.button_style}
onPress={() => navigation.navigate(navigateTo)}
>
<Text style={styles.text_style}>{title}</Text>
</Button>
);
};
const styles = StyleSheet.create({
button_style: {
backgroundColor: "red"
},
text_style: {
color: "#151414"
}
});
export default withNavigation(ButtonNavigate);
I have just tested you code in expo.snack but without navigation and its ok,
see it here
You can test in your app to remove navigation and go step by step until you find the bug.
Folks, reason for this strange behavior is the "rounded" property of Native Base's button. In my application, somehow it causes the button to become non-clickable.
Maybe contributors of Native Base know what to do with this problem, so if you read this, maybe you have an idea.
Solution for my now was simply removing "rounded".
Native Base: 2.13.8
React-Navigation: 4.0.10
In my case it was the "top" in the container property of the button causing this issue. Removed it and adding "marginBottom" to the container above it solved the issue

Unable to set header title dynamically in react native

I am trying to change the Header title of new component screen dynamically but getting the following error:
TypeError: TypeError: undefined is not an object (evaluating 'navigationData.navigation.getParam')
* screens\CategoryMealsScreen.js:26:42 in navigationOptions
* screens\CategoryMealsScreen.js:10:40 in CategoryMealsScreen
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:9473:27 in renderWithHooks
- node_modules\react-native\Libraries\Renderer\oss\ReactNativeRenderer-dev.js:11994:6 in mountIndeterminateComponent
My Code:
import React from "react";
import { Text, View, StyleSheet, Button } from "react-native";
import { CATEGORIES } from "../data/dummydata";
import Colors from "../constans/Colors";
let titleHeader = "";
const CategoryMealsScreen = props => {
const categoryId = props.navigation.getParam("categoryId");
const selectedCategory = CATEGORIES.find(cat => cat.id === categoryId);
CategoryMealsScreen.navigationOptions(selectedCategory.title);
// console.log(Object.keys(props.navigation));
titleHeader = selectedCategory.title;
return (
<View style={styles.screen}>
<Text>{selectedCategory.title}</Text>
<Button
title="Meals Details"
onPress={() => props.navigation.navigate("MealsDetail")}
/>
</View>
);
};
CategoryMealsScreen.navigationOptions = navigationData => {
const catId = navigationData.navigation.getParam("categoryId");
const selectedCategory = CATEGORIES.find(cat => cat.id === catId);
// console.log(catId);
// console.log(navigationData.navigation.getParam("categoryId"));
return {
headerTitle: selectedCategory.title,
headerStyle: {
backgroundColor: Colors.primaryColor
},
headerTintColor: "white"
};
};
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: "center",
alignContent: "center"
}
});
export default CategoryMealsScreen;
I tried to console log catId and it does show the output in the console but the error remains.
I am able to get data with getParam inside the component but not in CategoryMealsScreen.navigationOptions
Some sited its problem with bable configuration but it is not working or I am doing something wrong.
Right not I am using global variable titleHeader to change header title and it works but it's still a hack.
GitHub
Problem occurs because of async task like find CATEGORIES.find(cat.. This will take a time to complete
Solution : use async/await with your fuction which wait for your task completion.
We can set title dynamically using navigationOptions directly in stack configuration.
CategoryMeals : {
screen : CategoryMealsScreen,
navigationOptions: ({ navigation }) => ({
title : navigation.getParam('categoryId', 'CategoryMeals')
}),
},

how to pass form data from screen1 to screen2 in react native?

how to pass form data from screen1 to screen2 in react native ? I have following code in scrren1 I want posted amount data in screen2. Please let me know how can I pass data on screen2 and receive it in react native?
import React, { Component } from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import t from 'tcomb-form-native'; // 0.6.9
const Form = t.form.Form;
const User = t.struct({
amount: t.String,
});
const formStyles = {
...Form.stylesheet,
formGroup: {
normal: {
marginBottom: 10
},
},
controlLabel: {
normal: {
color: 'blue',
fontSize: 18,
marginBottom: 7,
fontWeight: '600'
},
// the style applied when a validation error occours
error: {
color: 'red',
fontSize: 18,
marginBottom: 7,
fontWeight: '600'
}
}
}
const options = {
fields: {
amount: {
label: "Enter Amount You want to Top up",
error: 'Please add amount to proceed ahead!'
},
},
stylesheet: formStyles,
};
class HomeScreen extends Component {
static navigationOptions = {
title: 'Home',
};
handleSubmit = () => {
const value = this._form.getValue();
console.log('value: ', value);
}
render() {
return (
<View style={styles.container}>
<Form
ref={c => this._form = c}
type={User}
options={options}
/>
<Button
title="Pay Now"
onPress={this.handleSubmit}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
marginTop: 50,
padding: 20,
backgroundColor: '#ffffff',
},
});
export default HomeScreen;
It depends if you want to pass data between Parent to Child, Child to Parent or Between Siblingsā€Š
I suggest you to read Passing Data Between React Components, old but this article did help me to understand the logic behind passing data as it's not as easy to implement as in other programming languages.
Excerpt using props:
class App extends React.Component {
render() {
[... somewhere in here I define a variable listName
which I think will be useful as data in my ToDoList component...]
return (
<div>
<InputBar/>
<ToDoList listNameFromParent={listName}/>
</div>
);
}
}
Now in the ToDoList component, use this.props.listNameFromParent to access that data.
You have many ways to send informations from one screen to another in React Native.
eg.
Use React Navigation to navigate between your scenes. You will be able to pass params to your components, which will be accessible in the navigation props when received.
this.props.navigation.navigate({routeName:'sceneOne', params:{name} });
You can also send directly props to a component, and treat them in it. In your render section of your first component, you could have something like this :
<myComponent oneProps={name}/>
In that example, you will receive the props "oneProps" in your second component and you will be able to access it that way :
type Props = {
oneProps: string,
}
class myComponent extends React.Component<Props> {
render() {
console.log('received sent props', oneProps);
return (
<View> // display it
<Text>{this.props.oneProps}</Text>
</View>
);
};
}
These are only two effective solutions, but there are a lot more.
Hope it helped you :)
Have a good day

React Native Pass properties on navigator pop

I'm using NavigatorIOS on my react native app. I want to pass some properties when navigating back to previous route.
An example case:
I'm in a form page. After submitting data, I want to go back to the previous route and do something based on the submitted data
How should I do that ?
Could you pass a callback func on the navigator props when you push the new route and call that with the form data before you pop to the previous route?
Code sample showing how to use a callback before pop. This is specifically for Navigator and not NavigatorIOS but similar code can be applied for that as well.
You have Page1 and Page2. You are pushing from Page1 to Page2 and then popping back to Page1. You need to pass a callback function from Page2 which triggers some code in Page1 and only after that you will pop back to Page1.
In Page1 -
_goToPage2: function() {
this.props.navigator.push({
component: Page2,
sceneConfig: Navigator.SceneConfigs.FloatFromBottom,
title: 'hey',
callback: this.callbackFunction,
})
},
callbackFunction: function(args) {
//do something
console.log(args)
},
In Page2 -
_backToPage1: function() {
this.props.route.callback(args);
this.props.navigator.pop();
},
The function "callbackFunction" will be called before "pop". For NavigatorIOS you should do the same callback in "passProps". You can also pass args to this callback. Hope it helps.
You can use AsyncStorage, save some value on child Component and then call navigator.pop():
AsyncStorage.setItem('postsReload','true');
this.props.navigator.pop();
In parent Component you can read it from AsyncStorage:
async componentWillReceiveProps(nextProps) {
const reload = await AsyncStorage.getItem('postsReload');
if (reload && reload=='true')
{
AsyncStorage.setItem('postsReload','false');
//do something
}
}
For NavigatorIOS you can also use replacePreviousAndPop().
Code:
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
TouchableOpacity,
View,
AppRegistry,
NavigatorIOS
} = React;
var MainApp = React.createClass({
render: function() {
return (
<NavigatorIOS
style={styles.mainContainer}
initialRoute={{
component: FirstScreen,
title: 'First Screen',
passProps: { text: ' ...' },
}}
/>
);
},
});
var FirstScreen = React.createClass({
render: function() {
return (
<View style={styles.container}>
<Text style={styles.helloText}>
Hello {this.props.text}
</Text>
<TouchableOpacity
style={styles.changeButton} onPress={this.gotoSecondScreen}>
<Text>Click to change</Text>
</TouchableOpacity>
</View>
);
},
gotoSecondScreen: function() {
console.log("button pressed");
this.props.navigator.push({
title: "Second Screen",
component: SecondScreen
});
},
});
var SecondScreen = React.createClass({
render: function() {
return (
<View style={styles.container}>
<Text style={styles.helloText}>
Select a greeting
</Text>
<TouchableOpacity
style={styles.changeButton} onPress={() => this.sayHello("World!")}>
<Text>...World!</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.changeButton} onPress={() => this.sayHello("my Friend!")}>
<Text>...my Friend!</Text>
</TouchableOpacity>
</View>
);
},
sayHello: function(greeting) {
console.log("world button pressed");
this.props.navigator.replacePreviousAndPop({
title: "First Screen",
component: FirstScreen,
passProps: {text: greeting}
});
}
});
var styles = StyleSheet.create({
mainContainer: {
flex: 1,
backgroundColor: "#eee"
},
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
marginTop: 50,
},
helloText: {
fontSize: 16,
},
changeButton: {
padding: 5,
borderWidth: 1,
borderColor: "blue",
borderRadius: 4,
marginTop: 20
}
});
AppRegistry.registerComponent("TestApp", () => MainApp);
You can find the working example here: https://rnplay.org/apps/JPWaPQ
I hope that helps!
I had the same issue with React Native's navigator which I managed to solve using EventEmitters and Subscribables. This example here was really helpful: https://colinramsay.co.uk/2015/07/04/react-native-eventemitters.html
All I needed to do was update for ES6 and the latest version of React Native.
Top level of the app:
import React, { Component } from 'react';
import {AppRegistry} from 'react-native';
import {MyNavigator} from './components/MyNavigator';
import EventEmitter from 'EventEmitter';
import Subscribable from 'Subscribable';
class MyApp extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.eventEmitter = new EventEmitter();
}
render() {
return (<MyNavigator events={this.eventEmitter}/>);
}
}
AppRegistry.registerComponent('MyApp', () => MyApp);
In the _renderScene function of your navigator, make sure you include the "events" prop:
_renderScene(route, navigator) {
var Component = route.component;
return (
<Component {...route.props} navigator={navigator} route={route} events={this.props.events} />
);
}
And here is the code for the FooScreen Component which renders a listview.
(Note that react-mixin was used here in order to subscribe to the event. In most cases mixins should be eschewed in favor of higher order components but I couldn't find a way around it in this case):
import React, { Component } from 'react';
import {
StyleSheet,
View,
ListView,
Text
} from 'react-native';
import {ListItemForFoo} from './ListItemForFoo';
import reactMixin from 'react-mixin'
import Subscribable from 'Subscribable';
export class FooScreen extends Component {
constructor(props) {
super(props);
this._refreshData = this._refreshData.bind(this);
this._renderRow = this._renderRow.bind(this);
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows([])
}
}
componentDidMount(){
//This is the code that listens for a "FooSaved" event.
this.addListenerOn(this.props.events, 'FooSaved', this._refreshData);
this._refreshData();
}
_refreshData(){
this.setState({
dataSource: this.state.dataSource.cloneWithRows(//YOUR DATASOURCE GOES HERE)
})
}
_renderRow(rowData){
return <ListItemForFoo
foo={rowData}
navigator={this.props.navigator} />;
}
render(){
return(
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
/>
)
}
}
reactMixin(FooScreen.prototype, Subscribable.Mixin);
Finally. We need to actually emit that event after saving a Foo:
In your NewFooForm.js Component you should have a method like this:
_onPressButton(){
//Some code that saves your Foo
this.props.events.emit('FooSaved'); //emit the event
this.props.navigator.pop(); //Pop back to your ListView component
}
This is an old question, but currently React Navigation's documentation for Passing params to a previous screen suggests that we use navigation.navigate() and pass whatever parameters we want the previous screen to have.