I have a problem with React Final Form. I tried to follow the example on the official documentation, but I still don't understand why my form doesn't want to call the onSubmit as the example, and I'm still trying to understand what the role of handlesubmit is.
I think that the problem is that my fields are called from another component that uses the useField hook.
import React, { FC } from 'react'
import { Form, Field } from 'react-final-form'
import {
Grid,
Box,
Button,
createStyles,
makeStyles,
Theme,
} from '#material-ui/core'
import { render } from 'react-dom'
import InputField from './InputField'
interface InputFinalFormModalProps {
fieldsValue: { title: string; value: string }[]
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
annullaBtn: {
backgroundColor: '#fff',
border: `1px solid ${theme.palette.secondary.main}`,
color: theme.palette.secondary.main,
fontFamily: 'HouschkaHead',
fontSize: '17px',
fontWeight: 'bold',
paddingLeft: '40px',
paddingRight: '40px',
marginRight: '15px',
},
salvaBtn: {
fontFamily: 'HouschkaHead',
fontSize: '17px',
fontWeight: 'bold',
paddingLeft: '40px',
paddingRight: '40px',
marginLeft: '15px',
},
row: {
width: '100%',
textAlign: 'end',
},
container: {
'& > .MuiGrid-item': {
paddingBottom: '20px',
paddingTop: '0px',
paddingLeft: '11px',
paddingRight: '11px',
},
},
})
)
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const onSubmit = async (values: {}) => {
await sleep(300)
console.log(JSON.stringify(values))
}
const validate = (values: {}) => {
const errors = {message: ''}
if (!Object.values(values).toString()) {
errors.message = "Required";
}
return errors
}
const InputFinalFormModal: FC<InputFinalFormModalProps> = ({ fieldsValue }) => {
const classes = useStyles()
return (
<Form
onSubmit={onSubmit}
validate={validate}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<Grid container className={classes.container}>
{fieldsValue.map((field) => {
return (
<InputField title={field.title} value={field.value} />
)
})}
</Grid>
<Box className={classes.row} mt="20px">
<Button className={classes.annullaBtn}>Annulla</Button>
<Button
type="submit"
onClick={() => onSubmit(values)}
className={classes.salvaBtn}
disabled={submitting || pristine}
>
Salva
</Button>
</Box>
{/* <button
type="submit"
onClick={() => onSubmit(values)}
disabled={submitting || pristine}
>
Submit
</button>
<button
type="button"
onClick={form.reset}
disabled={submitting || pristine}
>
Reset
</button>
<pre>{JSON.stringify(values)}</pre>*/}
</form>
)}
/>
)
}
InputFinalFormModal.displayName = 'InputFinalFormModal'
export default InputFinalFormModal
And here my snippet about the inputField
import React, { FC, useEffect, useState } from 'react'
import { useField } from 'react-final-form'
import {
Box,
Grid,
createStyles,
makeStyles,
Theme,
withStyles,
} from '#material-ui/core'
import InputLabel from '#material-ui/core/InputLabel'
import InputBase from '#material-ui/core/InputBase'
import { parse } from 'path'
interface InputFieldProps {
title: string
value: string
}
const BootstrapInput = withStyles((theme: Theme) =>
createStyles({
input: {
borderRadius: 4,
position: 'relative',
backgroundColor: theme.palette.common.white,
border: '1px solid #bdc7d3',
width: '100%',
padding: '10px 12px',
transition: theme.transitions.create(['border-color', 'box-shadow']),
// Use the system font instead of the default Roboto font.
fontFamily: 'Montserrat',
fontSize: '14px',
lineHeight: '1.21',
fontWeight: 'normal',
color: theme.palette.text.primary,
'&:focus': {
boxShadow: 'inset 0 0 12px 4px rgba(0, 179, 152, 0.05)',
borderColor: theme.palette.secondary.main,
},
},
})
)(InputBase)
const useStyles = makeStyles((theme: Theme) =>
createStyles({
inputTitle: {
color: '#7C92A8',
fontSize: '19px',
lineHeight: 1.76,
transition: 'font-weight 0.5s, color 0.5s',
'&.Mui-focused': {
color: theme.palette.secondary.main,
fontWeight: 'bold',
},
},
requiredText:{
color: 'red'
}
})
)
const InputField: FC<InputFieldProps> = ({ title, value}) => {
let {input, meta} = useField(title)
const classes = useStyles()
let [handleValidate, setHandleVlidate] = useState(false)
let [required, setRequired] = useState('')
useEffect(() => {
if(parseInt(value)){
setRequired('valore non valido')
setHandleVlidate(true)
/* if(isNaN(input.value)){
} */
}else{
if(!input.value){
setRequired('Campo Obbligatorio')
setHandleVlidate(true)
}
}
},[input.value])
return (
<Grid item xs={12} md={6}>
<InputLabel
shrink
htmlFor="bootstrap-input"
className={classes.inputTitle}
>
{title}
</InputLabel>
<BootstrapInput
{...input}
defaultValue={value}
id="bootstrap-input"
placeholder={value}
/>
{meta.touched && !input.value && (
<span className={classes.requiredText}>{required}</span>
)}
</Grid>
/* <Grid item xs={6} lg={6}>
<Box>
<label className={classes.inputTitle} onClick={() => console.log('check')}>{title}</label>
</Box>
<input {...field.input} className={classes.inputField} placeholder={value} />
{field.meta.touched && field.meta.error && (
<span>{field.meta.error}</span>
)}
</Grid> */
)
}
InputField.displayName = 'InputField'
export default InputField
If your form is valid, the validation function needs to return {}, not { message: '' }.
Related
I created a component that accepts a label and an array and then returns a table for displaying the data that was passed. But for some reason, whenever the component re-renders, the table rows increments because the data repeats and displays it again below the table. Here's my code...
import React from 'react';
interface Props {
labels: any[];
data: any[];
}
const sampleData = [
{
rowLabel: 'Fever',
key: 'key',
value: 'value',
},
{
rowLabel: 'Colds',
key: 'key',
value: 'value',
},
];
const Display: React.FC<Props> = ({ labels, data }: Props) => {
return (
<>
<table
style={{
direction: 'ltr',
width: '100%',
flexDirection: 'row',
}}
>
{labels.map((label, index) => (
<tr key={index} style={{ border: '2px solid #e8e8e9' }}>
<td style={{ border: '2px solid #e8e8e9', textAlign: 'center' }}>
{label}
</td>
{data.map((data) => {
return data.rowLabel === label ? (
<tr
key={index}
style={{
display: 'flex',
flexDirection: 'row',
borderInline: '1px solid #e8e8e9',
borderBottom: '1px solid #e8e8e9',
paddingLeft: '5px',
}}
>
<td
style={{
width: '50%',
textOverflow: 'ellipsis',
}}
>
{data.key}
</td>
<td
style={{
borderLeft: '2px solid #e8e8e9',
width: '50%',
textAlign: 'right',
paddingRight: '5px',
}}
>
{data.value}
</td>
</tr>
) : null;
})}
</tr>
))}
</table>
</>
);
};
export default Display;
And here is me calling the component
<Display
labels={['Fever','Colds']}
data={sampleData}
/>
Im still a noob and any help would be greatly appreciated, thank you!
I have a page where I am rendering data that is paginated. The Next and Previous button work fine but when I click on next and previous buttons, the Page number value inside the select tag does not update. Can someone tell me a fix for the same?
Here is the function that renders next and previous and current page.
`
const renderShowMore = () => {
return (
<tr>
<td colSpan={5}>
<div
style={{
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
}}
>
<div
role={'presentation'}
style={
pagination.page > 0
? {
color: '#fff',
cursor: 'pointer',
}
: {}
}
onClick={() => {
if (pagination.page > 0) {
fetchData(pagination.page - 1);
}
}}
>
<i className="fa fa-chevron-left"></i>
Previous
</div>
<div className="btn" role={'presentation'}>
<select
className="form__select"
onChange={e => fetchData(parseFloat(e.target.value))}
>
{[...Array(pagination.totalPages).fill(10)].map((_, index) => {
return (
<option key={`Page ${index}`} value={index}>
Page {index + 1}
</option>
);
})}
</select>
</div>
<div
role={'presentation'}
style={
pagination.page < pagination.totalPages - 1
? {
color: '#fff',
cursor: 'pointer',
}
: {}
}
onClick={() => {
if (pagination.page < pagination.totalPages - 1) {
fetchData(pagination.page + 1);
}
}}
>
Next
<i className="fa fa-chevron-right"></i>
</div>
</div>
</td>
</tr>
);
};
`
Here is the fetchData function:
`
const fetchData = page => {
dispatch(
groupActions.showMore({
search: search,
page: page,
limit: pagination.limit,
}),
);
};
`
I need to make the select tag controlled but how do I do that?
I did a google map with a list of resellers using "react-google-maps"
The markers properly looping and showing in the map
How can I list the 'Marker'/'Resellers' in the Map into a list(UL) too, Also the list(li) click should pop the info window?
This is the one I used, https://gist.github.com/jwo/43b382fc60eb09d3a415c9953f4057f8
import React, { Component } from "react"
import { compose } from "recompose"
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
InfoWindow, Listing
} from "react-google-maps"
const MapWithAMarker = compose(withScriptjs, withGoogleMap)(props => {
const listStyle = {
width: '250px',
position: "relative"
}
return (
<div style={{ position: "relative" }}>
<GoogleMap defaultZoom={4} defaultCenter={{ lat: 56.263920, lng: 9.501785 }}>
{props.markers.map(marker => {
if (marker.lat != null) {
const onClick = props.onClick.bind(this, marker)
return (
<Marker
key={marker.customerNumber}
onClick={onClick}
position={{ lat: parseFloat(marker.lat), lng: parseFloat(marker.lng) }}
>
{props.selectedMarker === marker &&
<InfoWindow>
<div>
<h1> {marker.name}</h1>
<br />
{marker.address}
<br />
{marker.zipcode} {marker.city}
<br />
Telephone: {marker.phone}
</div>
</InfoWindow>}
{/* List resellers here, so info window can be reused I guess, not sure */}
</Marker>
)
}
})}
</GoogleMap>
</div>
)
})
export default class ResellerGoogleMap extends Component {
constructor(props) {
super(props);
this.state = {
showingInfoWindow: false,
selectedMarker: {},
selectedPlace: {}
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(marker, event) {
console.log(this.props)
this.setState({
selectedMarker: marker
});
}
render() {
return (
<div style={{ padding: "24px 13px 2px 2px" }}>
<input type="button" value="My Location" style={{ marginBottom: "20px" }} onClick={(e) => this.onBtnClick()} />
<MapWithAMarker
selectedMarker={this.state.selectedMarker}
markers={this.props.resellerData}
onClick={this.handleClick}
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%`, padding: `61px 55px 55px 55px` }} />}
containerElement={<div style={{ height: `800px` }} />}
mapElement={<div style={{ height: `511px` }} />}
/>
</div>
);
}
}
Expecting
a Google map/
Markers of 'resellers'/
List the resellers in a list too/
On click of list item should populate info window like when we click on Markers/
First two points are working, Need a hand on the rest, Please help somebody
Thank you all for your time, I found the solution
Simply we can add the following line of code just after the infowindow close tag
<a onClick={onClick}>{marker.name}</a>
I'm trying to build an app that has responsive font sizes based on the screen width with the new material-ui (v1.3). I have a drawer with a menu on it for navigation. I'd like to be able to shrink the font size (among other things on the page), when the screen size is smaller.
I have the following code and it doesn't seem to work when I shrink the screen down manually in the browser. The font size will change but I actually have to refresh the page to see the changes?? With the last version I used, (v.0.13) it would shrink as the screen size was changing, by manually making the browser size smaller with clicking and dragging with mouse and making it smaller. Does anyone have any ideas as to why this is happening?
class App extends Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
}
getDrawerFontSize() {
if (window.innerWidth <= 575) {
return '10px';
} else if (window.innerWidth <= 767) {
return '11px';
} else if (window.innerWidth <= 991) {
return '12px';
} else if (window.innerWidth <= 1199) {
return '13px';
}
return '14px';
}
render() {
const drawerFontSize = this.getDrawerFontSize();
const { open } = this.state;
const theme = createMuiTheme({
overrides: {
MuiDrawer: {
paper: {
background: '#333333',
borderRadius: '0',
width: '250px',
padding: '0 10px',
color: 'white',
marginTop: '80px',
},
paperAnchorDockedLeft: {
borderRight: '0px',
},
},
MuiTypography: {
subheading: {
color: '#999999',
fontSize: drawerFontSize,
},
},
MuiListItemIcon: {
root: {
color: '#999999',
},
},
MuiListItemText: {
root: {
paddingRight: '5px',
paddingLeft: '5px',
}
},
MuiDivider: {
root: {
backgroundColor: '#999999',
},
},
},
});
const styles = {
app: {
backgroundColor: 'black',
},
appBar: {
backgroundColor: '#333333',
},
titleBar: {
backgroundColor: '#111111',
height: '35px',
width: '100%',
},
venn: {
height: '50px',
display: 'inline-block',
verticalAlign: 'middle',
},
logo: {
height: '80px',
width: '80px',
display: 'inline-block',
verticalAlign: 'middle',
},
appHeader: {
backgroundColor: 'black',
height: '150px',
padding: '20px',
color: 'white',
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
appTitle: {
fontSize: '1.5em',
},
appIntro: {
fontSize: 'large',
},
rightImages: {
marginLeft: 'auto',
marginRight: -12,
},
drawer: {
width: '150',
position: 'relative',
},
title: {
marginRight: '15px',
verticalAlign: 'middle',
display:'inline-block',
},
activeLink: {
textDecoration: 'none',
color: 'white',
}
}
return (
<MuiThemeProvider theme={theme}>
<div style={styles.app}>
<AppBar style={styles.appBar} position="static">
<Toolbar >
<div style={styles.drawerHeader}>
<Typography style={styles.title} variant="display2" color="inherit">
My APP
</Typography>
<img src={venn} style={styles.venn}/>
</div>
<section style={styles.rightImages}>
<img src={logo} style={styles.logo}/>
</section>
</Toolbar>
</AppBar>
<Drawer variant={"permanent"} anchor="left">
<div
tabIndex={0}
role="button" >
<List component="nav">
<NavLink style={styles.activeLink} to="/" href="/">
<ListItem button >
<ListItemIcon>
<HomeIcon />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
</NavLink>
<Link style={styles.activeLink} to="/account" href="/account">
<ListItem button>
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
<ListItemText primary="My Account" />
</ListItem>
</Link>
<Link style={styles.activeLink} to={"/logout"} href="/logout">
<ListItem button>
<ListItemIcon>
<ExitIcon />
</ListItemIcon>
<ListItemText primary="Logout" />
</ListItem>
</Link>
</List>
<Divider />
<List component="nav">
<Link style={styles.activeLink} to={"/help"} href="/help">
<ListItem button>
<ListItemIcon>
<HelpIcon />
</ListItemIcon>
<ListItemText primary="Help" />
</ListItem>
</Link>
</List>
</div>
</Drawer>
<div>
<Router />
</div>
</div>
</MuiThemeProvider>
);
}
}
export default withRouter(connect( mapStateToProps, mapDispatchToProps)(App));
You can make responsive font by multiple way :
1) Using JavaScript :
- Like you can use java script Library (i.e. http://simplefocus.com/flowtype/)
2) Using CSS :
- In place of px use vw or vh -
- 1vw = 1% of viewport width
-1vh = 1% of viewport height
-1vmin = 1vw or 1vh, whichever is smaller
-1vmax = 1vw or 1vh, whichever is larger
OR
You can use Media Query in css
https://www.w3schools.com/css/css_rwd_mediaqueries.asp
Thanks!! Hope it Helps.
infowWindow onclick event and infowWindow options not working?
This is the code that i am trying:
import React from 'react'
import { withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps'
const mapOptions = {
scrollwheel: false,
draggable: true,
rotateControl: false,
scaleControl: false,
streetViewControl: false,
panControl: false
}
const num10 = 10
const descriptionLentgh = 35
const GettingStartedGoogleMap = withGoogleMap((props) => (
<GoogleMap
options={mapOptions}
ref={props.onMapLoad}
defaultZoom={4}
center={props.center}
onClick={props.onMapClick}
>
{props.markers.map((marker, index) => (
<Marker
key={index}
position={marker.position}
onClick={() => props.onMarkerClick(marker)}
icon={marker.markerIcon === true
? '/images/markerlogo2.png' : '/images/markerlogo.png'}
label={{ text: `$${marker.rate}`, color: 'white' }}
{...marker}
//onRightClick={() => props.onMarkerRightClick(index)}
>
{marker.showInfo && (
<InfoWindow onClick={props.onWindowClick}
onCloseClick={() => props.onMarkerClose(marker)}
>
<div className="">
<div className="map-info-box">
<div className="parking-inner-container">
<div className="parking-inner">
<div className="parking-container">
<div className="parking-image-wrapper">
<div className="parking-image"
style={{ 'backgroundImage': `url(${marker.photos})` }}
/>
</div>
</div>
</div>
</div>
<div className="parking-info">
<div className="parking-title">
<h5><b>${marker.rate} {marker.infoContent}</b></h5>
</div>
<div className="parking-location">
<h2>{`${marker.description.substring(0, descriptionLentgh)}...`}</h2>
</div>
<div className="">
<h2 className="label label-default" style={marker.secured === false
? { backgroundColor: 'gray', fontSize: '100%' }
: { backgroundColor: 'green', fontSize: '100%' } }
>
{marker.secured === false ? 'open' : 'secured'}</h2>
</div>
</div>
</div>
</div>
</InfoWindow>
)}
</Marker>
))}
</GoogleMap>
))
class GoogleMaps extends React.Component {
constructor(props) {
super(props)
this.state = {
center: {
lat: 47.853287,
lng: -93.646775
},
markers: [],
true: true
}
}
componentWillReceiveProps(nextProps) {
this.setState({ markers: [], center: nextProps.bound.location }, () => {
const m = nextProps.pageNo - 1
if (nextProps.markers[0] !== undefined) {
let obj = {}
let newArray = []
for (let i = m * num10; i <= nextProps.markers.length; i++) {
if (i === m * num10 + num10) { break }
obj = {
position: { lat: nextProps.markers[i].loc[1], lng: nextProps.markers[i].loc[0] },
rate: nextProps.markers[i].spaces[0].rate,
infoContent: nextProps.markers[i].listingName || nextProps.markers[i].spaces[0].name,
showInfo: false,
photos: nextProps.markers[i].photos[0],
description: nextProps.markers[i].basic_details.notes,
secured: nextProps.markers[i].isSecured,
markerIcon: false
}
newArray = this.state.markers
newArray.push(obj)
this.setState({ markers: newArray })
}
} else {
this.setState({ markers: this.props.markers })
}
})
}
handleMarkerClick(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (marker === targetMarker) {
return {
...marker,
showInfo: true,
markerIcon: true
}
} else {
return {
...marker,
showInfo: false
}
}
})
})
}
handleMarkerClose(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (marker === targetMarker) {
return {
...marker,
showInfo: false
}
}
return marker
})
})
}
handleMarkerClose2(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (targetMarker) {
return {
...marker,
showInfo: false
}
}
return marker
})
})
}
onWindowClick() {
console.log('fdgdsfg')
}
render() {
return (<div>
<div id="mapcanvas"
className="col-md-6"
style={{ 'height': '556px', 'width': '674px', paddingLeft: '0px', paddingRight: '0px' }}
>
<GettingStartedGoogleMap
containerElement={<div style={{ height: '100%' }} />}
mapElement={<div style={{ height: '100%' }} />}
//onMapLoad={_.noop}
onMapClick={this.handleMarkerClose2.bind(this)}
onMarkerClick={this.handleMarkerClick.bind(this)}
markers={this.state.markers}
center={this.state.center}
//onMarkerRightClick={this.handleMarkerRightClick.bind(this)}
onMarkerClose={this.handleMarkerClose.bind(this)}
/>
</div>
<style>{'\
.gm-style-iw + div {\
display: none;\
left: 26px;}\
'}</style>
</div>)}
}
GoogleMaps.propTypes = {
markers: React.PropTypes.array
}
export default GoogleMaps
I just ran into this issue some days ago and seems that the only way to attach an onClick to an InfoWindow is with google.maps.event in place.
I used 'react-dom/server' to set the content of it, where content is a React.Component.
Here you have an example:
const info = new googleMaps.InfoWindow({
map,
disableAutoPan: true,
maxWidth: 300,
content: ReactDom.renderToString(content)
});
google.maps.event.addListener(info, 'domready', () => {
document.querySelector('.gm-style-iw').addEventListener('click', onClick);
});