React-native: How can I auto update the mainscreen after adding a new item - mongodb

I'm basically new to React-native and I'm trying to integrate it with MongoDB, apollo-graphql to implement a basic chat app.
I need to update the screen automatically and show the newly created group when I add a new group. Now what happens is, when I create the group, I need to reload the app every time to show the updation made.
GroupScreen.tsx
const MY_GROUPS = gql`
query chatRooms {
chatRooms {
id
name
createdAt
imageUri
}
}
`;
export default function GroupScreen() {
const [groups, setGroups] = useState(null);
const { data, error, loading } = useQuery(MY_GROUPS);
useEffect(() => {
if (error) {
Alert.alert("Something went Wrong! Please reload.");
}
}, [error]);
useEffect(() => {
if (data) {
//console.log(data);
setGroups(data.chatRooms);
}
}, [data]);
return (
<View style={styles.container}>
<FlatList
style={{ width: "100%" }}
data={groups}
renderItem={({ item }) => <GroupListItem chatRoom={item} />}
keyExtractor={(item) => item.id}
/>
<NewGroupButtonItem />
</View>
);
}
NewGroupButtonItem.tsx
const CREATE_CHATROOM = gql`
mutation Mutation(
$createChatRoomName: String!
$createChatRoomImageUri: String
) {
createChatRoom(
name: $createChatRoomName
imageUri: $createChatRoomImageUri
) {
id
name
imageUri
createdAt
users {
id
name
}
}
}
`;
const NewGroupButtonItem = () => {
const [modalVisible, setModalVisible] = useState(false);
const [groupName, setGroupName] = useState("");
const [groupPic, setGroupPic] = useState(null);
const [newGroup, { data, error, loading }] = useMutation(CREATE_CHATROOM);
const onPress = () => {
setGroupName("");
setGroupPic(null);
setModalVisible(!modalVisible);
};
const onPressSave = () => {
newGroup({
variables: {
createChatRoomName: groupName,
createChatRoomImageUri: groupPic,
},
});
setModalVisible(!modalVisible);
};
return (
<View style={styles.container}>
<Modal animationType="fade" transparent={true} visible={modalVisible}>
<TouchableOpacity
style={styles.touchableContainer}
activeOpacity={1}
onPress={() => setModalVisible(!modalVisible)}
>
<View style={styles.mainContainer}>
<View style={styles.innerContainer}>
<Pressable
onPress={() => {
console.warn("Clicked Image!");
}}
>
<Image source={{}} style={styles.avatar} />
</Pressable>
<TextInput
placeholder={"Group Name"}
style={styles.inputBox}
value={groupName}
onChangeText={setGroupName}
/>
<Pressable
onPress={() => {
console.warn("Clicked Emojies!");
}}
>
<Entypo name="emoji-flirt" size={30} color="#37474f" />
</Pressable>
</View>
{!groupName ? (
<Text style={styles.saveButton} onPress={onPress}>
Cancel
</Text>
) : (
<Text style={styles.saveButton} onPress={onPressSave}>
Save
</Text>
)}
</View>
</TouchableOpacity>
</Modal>
<TouchableOpacity onPress={onPress}>
<MaterialIcons name="group-add" size={30} color="white" />
</TouchableOpacity>
</View>
);
};
export default NewGroupButtonItem;

Related

mui-datatable - update content of table by click a button possible?

I painstakingly put together a mui-datatable that displays the content of a CSV file.
Now I want to read another CSV file when a button is clicked.
Unfortunately, I'm a complete beginner - and I don't really know how to implement it.
I tried the following - but unfortunately it doesn't work that way.
Can someone help me with that?
As a 2nd step, I want to show a mui-alert for 3 seconds when I click on a cell. At the same time, a filter with the content of the cell is set. I have already successfully implemented this functionality using the setTimeout function. Strangely enough, only if I remove a filter within the 3 seconds "setTimeout" and then add a new filter, both filters (the one already removed and the new filter) are added again. Somehow the "setTimeout" function seems to be blocking further input in the mui-datatable....
Here is my code:
import {
Typography,
AppBar,
Button,
CssBaseline,
Toolbar,
Container,
Grid,
} from "#mui/material";
import React, { useState} from "react";
import { BackupTable, Refresh } from "#mui/icons-material";
import useStyles from "./styles";
//Tabelle
import MUIDataTable, { ExpandButton } from "mui-datatables";
import { TableRow, TableCell } from "#mui/material";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import total from "./test.csv";
import lost1 from "./test.csv";
import lost2 from "./test.csv";
//const App = () => {
function App() {
const { classes } = useStyles();
const [file, setFile] = useState();
const [array, setArray] = useState([]);
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setArray(array);
setCols(initFilter(Object.keys(Object.assign({}, ...array))));
};
const headerKeys = Object.keys(Object.assign({}, ...array));
let [selectedFilter, setSelectedFilter] = useState([new Array()]);
let [cols, setCols] = useState();
//Tabelle
const components = {
ExpandButton: function (props) {
return <ExpandButton {...props} />;
},
};
var removedFilterList=[];
const options = {
filter: true,
filterType: "multiselect",
onFilterChange: (changedColumn, filterList) => {
cols = modifyFilter(cols, filterList);
},
onFilterChipClose: (index, removedFilter, filterList) => {
removedFilterList = filterList;
selectedFilter = filterList;
},
responsive: "standard",
selectableRowsOnClick: false,
rowHover: true,
expandableRows: false,
expandableRowsHeader: false,
expandableRowsOnClick: true,
selectableRows: "none",
rowsPerPage: 100,
filterList: [],
selectableRowsHideCheckboxes: false,
onCellClick: (rowData, rowMeta) => {
onFilter(rowData, rowMeta);
},
};
const theme = createTheme({
overrides: {
MUIDataTableSelectCell: {
expandDisabled: {
// Soft hide the button.
visibility: "hidden",
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
backgroundColor: "#FF0000",
},
},
},
},
});
const initFilter = (cols) => {
//add options to columns
for (let i = 0; i < cols.length; i++) {
cols[i] = {
name: cols[i],
options: {
filterList: []
}
}
}
return cols;
}
let modifyFilter = (colss, newFilterArray) => {
for (let i = 0; i < colss.length; i++) {
colss[i].options.filterList = newFilterArray[i];
}
return colss;
}
const onFilter = (value, rowMeta) => {
let filteredCols = [...cols];
if (value !== "All") {
if (selectedFilter.length < rowMeta.colIndex + 1) {
for (let i = selectedFilter.length; i < rowMeta.colIndex + 1; i++) {
selectedFilter.push(new Array());
}
}
let schonVorhanden = selectedFilter[rowMeta.colIndex].indexOf(value);
if (schonVorhanden == "-1") {
selectedFilter[rowMeta.colIndex].push(value);
}
else {
console.log(value, "schon vorhanden");
}
}
filteredCols[rowMeta.colIndex].options.filterList = selectedFilter[rowMeta.colIndex];
setCols(filteredCols);
};
//---------------------------
//TEST
const ImportSamples = () => {
let input = total;
if(file == "lost1"){
input=lost1;
}
if (array.length == 0) {
fetch(input)
.then((r) => r.text())
.then((text) => {
csvFileToArray(text);
});
}
};
const doSetFile = (wert) => {
setFile(wert);
ImportSamples();
Refresh();
}
ImportSamples();
return (
<>
<CssBaseline />
<AppBar position="relative">
<Toolbar>
<BackupTable className={classes.icon} />
<Typography variant="h6">SampleProject Tabelle</Typography>
</Toolbar>
</AppBar>
<main>
<div className={classes.container}>
<Container maxWidth="sm">
<Typography
variant="h2"
align="center"
color="textPrimary"
gutterBottom
>
SampleProject
</Typography>
<Typography
variant="h5"
align="center"
color="textSecondary"
paragraph
>
Viewer
</Typography>
<div className={classes.button}>
<Grid container spacing={2} justifyContent="center">
<Grid item>
<Button variant="contained" color="primary" onClick={() => doSetFile('lost1')}>
Erste Aktion
</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="primary">
Zweite Aktion
</Button>
</Grid>
</Grid>
</div>
</Container>
</div>
<div className={classes.container}>
<Container maxWidth="false">
<ThemeProvider theme={theme}>
<MUIDataTable
title={"SampleProject"}
data={array}
columns={cols}
components={components}
options={options}
/>
</ThemeProvider>
</Container>
</div>
</main>
<footer className={classes.footer}>
<Typography variant="h6" align="center" gutterBottom>
Footer
</Typography>
<Typography variant="subtitle1" align="center" color="textSecondary">
Something here to give the footer a purpose!
</Typography>
</footer>
</>
);
}
export default App;

React Leaflet popup open after rerender

I'd like to have my markers popup open even after rerender. I have a marker showing user position so that updates all the time. After that marker rerenders that closes the popup of other marker.
My code:
const StaticReferencePoints = (props) => {
const { selectedCalibrationPoint, setSelectedCalibrationPoint, staticReferencePoints } = props;
const addToast = useToastContext();
const [snackbarOpen, setSnackbarOpen] = useState(true);
const [latitude, longitude] = useGeolocationStoreHelper(['latitude', 'longitude']);
const [map, setMap] = useState(null);
const center = [latitude, longitude];
const markerRef = useRef({});
const [selectedMarker, setSelectedMarker] = useState(null);
useEffect(() => {
// open popup if it exists
if (selectedMarker) {
markerRef.current[selectedMarker]?.openPopup();
}
}, [selectedMarker, latitude, longitude]);
useEffect(() => {
if (map) {
map.addEventListener('click', () => {
setSelectedMarker(null);
});
}
}, [map]);
const handleSnackbarClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSnackbarOpen(false);
};
const handleDeletePoint = (point) => {
if (confirm('Are you sure you want to delete this point?')) {
Meteor.call(...) => {
if (err) {
console.log(err);
addToast('Error deleting point', 'error');
}
if (res) {
console.log(res);
addToast('Point deleted', 'success');
}
});
}
};
const SingleMarker = (point) => {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<Marker
key={'staticPoint-' + point.point?.name}
position={point.point?.coordinates}
ref={(el) => (markerRef.current[point.point.name] = el)}
eventHandlers={{
click: () => {
setSelectedMarker(point.point.name);
},
}}
>
<Popup options={{ autoClose: false }}>
<Grid container item justifyContent={'center'} xs={12}>
<Grid item container direction={'row'} xs={12}>
<Grid item xs={1} container justifyContent={'center'} alignItems={'center'}>
{ACL.check) && (
<div>
<IconButton size="small" onClick={handleClick}>
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={() => handleDeletePoint(point.point)}>
<ListItemIcon>
<DeleteIcon />
</ListItemIcon>
<ListItemText primary="Delete" />
</MenuItem>
</Menu>
</div>
)}
</Grid>
<Grid item xs={10} container justifyContent={'center'} alignItems={'center'}>
<Typography>{point.point?.name}</Typography>
</Grid>
<Grid item xs={1}></Grid>
</Grid>
<Button
onClick={() => {
setSelectedCalibrationPoint(point.point);
setSelectedMarker(null);
}}
>
Select For Calibration
</Button>
</Grid>
</Popup>
</Marker>
);
};
const StaticPointMarkers = () => {
return staticReferencePoints.map((point, i) => {
return <SingleMarker key={'marker-' + i} point={point} />;
});
};
const UserPositionMarker = () => <Marker position={[latitude, longitude]} icon={userPosition}></Marker>;
return (
<Grid item xs={12}>
<Snackbar
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={snackbarOpen}
onClose={handleSnackbarClose}
autoHideDuration={5000}
message="Calibrate first by selecting a point from the map"
action={
<IconButton size="small" aria-label="close" color="inherit" onClick={handleSnackbarClose}>
<Close fontSize="small" />
</IconButton>
}
/>
<MapContainer
center={center}
zoom={18}
maxZoom={28}
scrollWheelZoom={false}
style={{ height: '65vh' }}
whenCreated={setMap}
>
<TileLayer
url="https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
maxZoom={28}
subdomains={['mt0', 'mt1', 'mt2', 'mt3']}
/>
{staticReferencePoints && <StaticPointMarkers />}
{latitude && longitude && <UserPositionMarker />}
</MapContainer>
<Grid id={'info'} item container xs={12} justifyContent={'center'} marginTop={'1rem'}>
<Typography>{`${latitude}, ${longitude}`}</Typography>
</Grid>
<Grid id={'info'} item container xs={12} justifyContent={'center'}>
<InfoIcon onClick={() => setSnackbarOpen(true)} />
</Grid>
</Grid>
);
};
export default StaticReferencePoints;
I have now set current open popup on state and on useEffect open the popup but that creates flickering.
What would be the best way to force popup stay open?
If I understand you question, the problem is that the selected marker popup flickers when the user location changes?
In general the popup should be able to stay open as long as the marker is not created again.
I can see that you have latitude and longitude as dependencies in the useEffect they are not used in the useEffect tough. This will cause the useEffect too trigger on each update on latitude and longitude.
My first suggestion is to remove them from the dependency array.
Change this:
useEffect(() => {
// open popup if it exists
if (selectedMarker) {
markerRef.current[selectedMarker]?.openPopup();
}
}, [selectedMarker, latitude, longitude]);
Secondly a good thing to do is to make sure that the popup is not already open this can be done with
To this:
useEffect(() => {
// open popup if it exists
if (selectedMarker && markerRef.current[selectedMarker]?isPopupOpen()) {
markerRef.current[selectedMarker]?.openPopup();
}
}, [selectedMarker, markerRef]);
As a side note in my experience with react-leaflet, unnecessary rerenders can cause some visual irritation such as flickering. You should strive to reduce the amount of re renders. This can be done using useMemo and useCallback hooks. Genrally passing props that are functions, arrays or object might cause rerenders even if they are the same.
From what I can see your code is deeply nested and it looks like you define component inside other components. Try to break out you code to clear and clean components instead and pass props to them instead, you can still have them all in the same file.
Using a eslint and typescript might also help you to find issues with the code.
Than you #Disco for your answer.
I managed to solve the problem with:
const StaticPointMarkers = (markers) => {
return useMemo(() => markers.map((point, i) => <SingleMarker key={'marker-' + i} point={point} />), [markers])};

Why is timeout not working on this component?

I am using material ui on a react-typescript project and need the bio to be mostly hidden until the show more button is pressed. In the docs for Collapse from material-ui it says you can pass in a number value for timeout and it will determine the amount of time that the transition takes to expand the card. However, I have passed in a large number here and the transition still only takes less than a second. Should I be passing the timeout prop in differently?
export default function ProfileBio({ bio }: Props) {
const [expandBio, setExpandBio] = useState<boolean>(false);
const [animation, setAnimation] = useState({});
const classes = useStyles();
const [bioHeight, setBioHeight] = useState(classes.notExpanded);
// bio
const BIO = [
{
name: 'Bio',
icon: <IconStyle icon={fileTextOutline} />,
content: bio
}
];
const fields = [
{
name: 'bio',
content: bio,
displayName: 'Bio'
}
];
const handleExpandClick = () => {
setExpandBio(!expandBio);
};
const CurrentContent: Function = () => {
return BIO.map((bioField) => (
<DetailsWrapper key={bioField.name}>
<Collapse
sx={{ mt: 0 }}
collapsedSize={72}
in={expandBio}
easing="ease"
timeout={60000}
onEnter={() => console.log('entered')}
>
<Typography
variant="body2"
color="text.primary"
style={{ whiteSpace: 'pre-wrap' }}
// className={bioHeight}
>
{bioField.icon}
{bioField.content}
</Typography>
</Collapse>
</DetailsWrapper>
));
};
const ShowMoreButton: Function = () => {
if (!edit) {
return (
<ExpandMore
stretchedLink
expand={expandBio}
onClick={handleExpandClick}
aria-expanded={expandBio}
aria-label="show more"
>
<Typography variant="button">
show
{expandBio ? 'less' : 'more'}
</Typography>
<ExpandMoreIcon className="chevron" />
</ExpandMore>
);
}
return <Box />;
};
return (
<Card>
<CardHeader
title="Bio"
action={
<IconButton
onClick={() => {
setEdit(!edit);
}}
>
<CreateIcon />
</IconButton>
}
/>
<CardContent>
<CurrentContent />
</CardContent>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
backgroundColor: '#FFFFFF'
}}
>
<ShowMoreButton />
</Box>
</Card>
);
}

DataGrid does not show inside Tabs with filterValues

I am trying to reproduce a component with Tabs and according filters like the OrderList in the react-admin demo (credentials are admin/password). The source can be found here.
What I have done is the following:
import React, { Fragment } from "react";
import {
List,
TextField,
Datagrid
} from "react-admin";
import Divider from "#material-ui/core/Divider";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
class TabbedDatagrid extends React.Component {
tabs = [
{ id: 1, name: "in service" },
{ id: 2, name: "scrapped" },
{ id: 3, name: "locked" }
];
state = { service: [], scrapped: [], locked: [] };
static getDerivedStateFromProps(props, state) {
if (props.ids !== state[props.filterValues.conditionId]) {
return { ...state, [props.filterValues.conditionId]: props.ids };
}
return null;
}
handleChange = (event, value) => {
const { filterValues, setFilters } = this.props;
setFilters({ ...filterValues, conditionId: value });
};
render() {
const { classes, filterValues, ...props } = this.props;
return (
<Fragment>
<Tabs
variant="fullWidth"
centered
value={filterValues.conditionId}
indicatorColor="primary"
onChange={this.handleChange}
>
{this.tabs.map(choice => (
<Tab key={choice.id} label={choice.name} value={choice.id} />
))}
</Tabs>
<Divider />
<div>
{filterValues.conditionId === 1 && (
<Datagrid {...props} ids={this.state.service}>
<TextField source="id" />
</Datagrid>
)}
{filterValues.conditionId === 2 && (
<Datagrid {...props} ids={this.state.scrapped}>
<TextField source="id" />
</Datagrid>
)}
{filterValues.conditionId === 3 && (
<Datagrid {...props} ids={this.state.locked}>
<TextField source="id" />
</Datagrid>
)}
</div>
</Fragment>
);
}
}
const RollList = ({ classes, ...props }) => (
<List
title="Roll Inventory"
{...props}
filterDefaultValues={{ conditionId: 1 }}
sort={{ field: "name", order: "ASC" }}
perPage={25}
>
<TabbedDatagrid />
</List>
);
export default RollList;
When I run this code:
I see this
Data is not shown, although when I look at the props and the number of records in the right bottom corner I can see that data is there.
The console doesn't show any error (besides missing translations).
I am using the following (relevant) dependencies:
"#material-ui/core": "^3.9.2",
"#material-ui/icons": "^3.0.2",
"ra-data-json-server": "^2.8.0",
"react": "^16.8.4",
"react-admin": "^2.8.1",
"react-dom": "^16.8.4",
"react-scripts": "^2.1.8"
Has anyone an idea what I can do about it?
I found a solution:
I saw that the only difference to the react-admin demo OrderList is that the id of the Tabs is a string whereas in my code it is an integer (like in my data provider).
I changed my code and also my data to be a string as an id instead of an integer.
tabs = [
{ id: 'service', name: 'in service' },
{ id: 'scrapped', name: 'scrapped' },
{ id: 'locked', name: 'locked' }
];
and also in the condition part further down
{filterValues.conditionId === 'service' && (
<Datagrid {...props} ids={this.state.service}>
{//...}
</DataGrid>
)}
... not to forget the default value in the RollList wrapper at the end
filterDefaultValues={{ conditionId: 'service' }}
now it is working.
Thanks to all who thought about it.
Cheers.

Currency field on Field ( redux-form ) for mobile app

I am currently developing a react mobile app.
The issue is that I want to add '£' currency symbol to a form-redux field with type=number.
Code
Form
import React from 'react';
import Card from "../../pandle-ui/Card";
import cssmodules from 'react-css-modules'
import styles from './form.cssmodule.scss'
import {Field} from "redux-form";
import CardTitleTextField from "../../form_fields/CardTitleTextField";
import {CardText, Divider, IconButton} from "material-ui";
import CardFieldRow from "../../layout/CardFieldRow";
import {TextFieldBase} from "../../form_fields/TextField";
import CurrencyField from "../../form_fields/CurrencyField";
import {CardActions, FlatButton} from "material-ui";
import DoneIcon from 'material-ui/svg-icons/action/done'
import {normalizePercentage} from "../../../util/currency_helpers";
class Form extends React.Component {
render() {
const {config, cancelButton, submit, wrapperRef} = this.props
return (
<div styleName="wrapper" ref={wrapperRef} >
<Card>
<DescriptionField />
<CardText style={ { paddingTop: 0 } }>
<QuantityField />
<UnitsField show={config.showUnits} />
<PriceField />
<DiscountField show={config.showDiscount} />
<Totals showTax={config.showTax} />
</CardText>
<Divider />
<CardActions style={{ textAlign: 'right', padding: '0 8px' }}>
{cancelButton}
<DoneButton onClick={() => submit()} />
</CardActions>
</Card>
</div>
)
}
}
Form.displayName = 'PagesLineItemsForm';
Form.propTypes = {};
Form.defaultProps = {};
export default cssmodules(Form, styles)
const DescriptionField = (props) =>
<Field
label="Description"
name="description"
component={CardTitleTextField}
{...props}
/>
const QuantityField = (props) =>
<CardFieldRow label="Quantity">
<Field name="quantity" type="number" component={TextFieldBase} {...props} />
</CardFieldRow>
const UnitsField = ({ show, ...props }) => {
if(show){
return <CardFieldRow label="Unit Type">
<Field name="unit" component={TextFieldBase} {...props} />
</CardFieldRow>
} else {
return null
}
}
const PriceField = (props) =>
<CardFieldRow label="Price">
<Field name="price" type="number" component={CurrencyField} {...props} />
</CardFieldRow>
const DiscountField = ({ show, props }) => {
if(show){
return <CardFieldRow label="Discount (%)">
<Field
name="discount_percentage"
component={TextFieldBase}
type="number"
normalize={normalizePercentage}
{...props}
/>
</CardFieldRow>
} else {
return null
}
}
const Totals = ({ showTax }) => {
if(showTax){
return <div>
<NetAmountField />
<TaxAmountField />
<TotalField />
</div>
} else {
return <TotalField name="net_amount" />
}
}
const NetAmountField = (props) =>
<CardFieldRow label="Net Amount">
<Field name="net_amount" component={CurrencyField} {...props} />
</CardFieldRow>
const TaxAmountField = (props) => {
return <CardFieldRow label="Tax Amount">
<Field name="tax_amount" component={CurrencyField} {...props} />
</CardFieldRow>;
}
const TotalField = (props) =>
<CardFieldRow label="Total">
<Field name="total_amount" disabled={true} component={CurrencyField} {...props} />
</CardFieldRow>
const DoneButton = (props) => <IconButton {...props}><DoneIcon /></IconButton>
CurrencyField.js
import React from 'react'
import {TextFieldBase} from "./TextField";
import {formatCurrency} from "../../util/formatters";
import {truncateCurrency} from "../../util/currency_helpers";
import {createComponentLogger} from "../../util/logging";
export default class CurrencyField extends React.Component {
constructor(props){
super(props)
log('Constructor called with props', props)
this.state = { value: props.value, focused : false }
this.baseInputPassProps = this.getBaseInputPassProps()
}
getBaseInputPassProps(){
return {
onChange: (e) => this.onChange(truncateCurrency(e.target.value)),
onBlur: (e) => this.onBlur(truncateCurrency(e.target.value)),
onFocus: (e) => this.onFocus()
}
}
onChange(value){
log('onChange called with', value)
safeParse(value, parsed => {
if(log('should update state', this.shouldUpdateStateValue(value))){
this.setState({ value })
this.props.input.onChange(parsed*100)
}
})
}
onBlur(value){
log('onBlur called with', value)
safeParse(value, parsed => {
this.props.input.onBlur(parsed*100)
})
}
onFocus(){
log('onFocus called')
this.props.input.onFocus()
}
shouldUpdateStateValue(value){
return this.state.value !== value
}
componentWillReceiveProps(nextProps){
const {value} = nextProps.input
log('will receive props', nextProps)
if(log('should update props value', this.shouldUpdatePropsValue(value))){
this.setState({
value: log('updating state.value', (value/100).toString())
})
}
}
shouldUpdatePropsValue(value){
const {input, meta: {active}} = this.props
return !active && input.value !== value
}
render(){
log('Render called')
return <TextFieldBase
{...log('Props for TextField', this.getTextFieldProps())}
/>
}
getTextFieldProps(){
const {value, ...props} = this.props
return Object.assign({}, props, this.getPassProps())
}
getPassProps(){
return { input: log('Input pass props', this.getInputPassProps()) }
}
getInputPassProps(){
return Object.assign({}, this.props.input, this.baseInputPassProps, {
value: log('Value for input pass props', this.getValue())
})
}
getValue(){
if(this.props.meta.active){
return log('active, state', this.state.value)
} else if(this.props.input.value) {
return log('inactive, formatted', this.getFormattedValue())
} else {
return ''
}
}
getFormattedValue() {
return (this.props.input.name === 'price') ?
this.props.input.value / 100
:
formatCurrency(
null,
log('for currency formatter', this.props.input.value / 100)
)
}
}
function safeParse(value, fn){
const parsed = Number(value)
log(`parsed ${value} as `, parsed)
!isNaN(parsed) && fn(parsed)
}
CurrencyField.displayName = 'CurrencyField'
const log = createComponentLogger(CurrencyField.displayName)
formatters.js
import numeral from 'numeral'
import 'numeral/locales/en-gb'
import moment from 'moment'
numeral.locale('en-gb');
export function formatCurrencyPence(number){
return (number > 0) ? formatCurrency(null, number/100) : '-'
}
export function formatCurrency(symbol='£', number){
return numeral(number).format('($0,0.00)')
}
export function formatAmount(number){
return numeral(number).format('-0,0.00')
}
export function formatInvoiceProps(item){
return [
formatInvoiceDates,
formatInvoiceAmounts
].reduce((memo, fn) => fn(memo), item)
}
const formatInvoiceDates = createFormatter(
formatServerDate,
'date',
'date-due'
)
function formatServerDate(date){
return formatMomentDate(serverToMomentDate(date))
}
function serverToMomentDate(date){
return moment(date, 'YYYY-MM-DD')
}
/**
* #param date {moment}
*/
export function formatMomentDate(date){
return (date.isValid() && date.format('DD/MM/YYYY')) || '-'
}
export function formatDate(date){
return formatMomentDate(moment(date))
}
const formatInvoiceAmounts = createFormatter(
formatInvoiceAmount,
'net-amount',
'tax-amount',
'total-amount'
)
function formatInvoiceAmount(amount){
return formatCurrency(null, amount)
}
function createFormatter(formatter, ...keys){
return ob => formatProps(ob, formatter, keys)
}
function formatProps(ob, formatter, keys){
return keys.reduce(formatProp(formatter), ob)
}
function formatProp(formatter){
return (ob, k) => {
const v = ob[k]
return v ? Object.assign({}, ob, { [k] : formatter(v, ob) }) : ob
}
}
This code uses number pad when the user tap in the field which is fine, but how can I format that?
Also, how can I prevent users to insert negative numbers?
Thx in advance.