I have two react arrow function components stacked on top of each other (using absolute positioning) and both of them have onClick attributes. The problem is, I want to click on the one that is on top, and both onClick functions trigger. Is there anyway to work around this?
This is a simplified version of the code:
const Card = ({...}) => {
const styles = {
optionsButton: {
minWidth:0,
minHeight: 0,
padding: "2px",
position: "absolute",
color: "#808080",
zIndex: 1,
right: 5,
top: 5,
'&:hover':{
backgroundColor: 'rgb(0, 0, 0, 0.1)'
}
},
}
const [hovering, setHovering] = useState(false)
const [isCardExpanded, setIsCardExpanded] = useState(false)
const expandCard = () => {
setIsCardExpanded(true)
}
const closeCard = () => {
setIsCardExpanded(false)
}
const mainPaperStyle = () => {
let style = {
padding: "10px",
cursor: "pointer",
position: "absolute",
"&:hover": {
filter: "brightness(97%)"
}
}
//Extra code here modifying color of the style, no positioning modifications
return style
}
const buttonAction = () => {
console.log("Do action!")
}
return(
<>
<Paper sx={mainPaperStyle()} onClick={expandCard} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
Lorem Ipsum
{hovering &&
<Button variant="filled"
id="card-button"
sx={styles.optionsButton}
onClick={() => buttonAction()}>
<MoreVertIcon/>
</Button>
}
</Paper>
</>
)
}
And here is a screenshot of why I want two components stacked on top of each other:
Before hovering:
After hovering:
I want a Button to appear when hovering on top of the Paper component. The problem is, when I click the button, both expandCard and buttonAction trigger. (I am using Material UI btw)
You can use $event.stopPropagation();.
const firstFn = () => { // first function body };
const secondFn = (event: MouseEventHandler<HTMLButtonElement>) => {
$event.stopPropagation();
// second function body
}
So in your case you need to change function buttonAction to this
const buttonAction = (event) => {
$event.stopPropagation();
console.log("Do action!")
}
and return clause with
return(
<>
<Paper sx={mainPaperStyle()} onClick={expandCard} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)}>
Lorem Ipsum
{hovering &&
<Button variant="filled"
id="card-button"
sx={styles.optionsButton}
onClick={() => buttonAction($event)}>
<MoreVertIcon/>
</Button>
}
</Paper>
</>
)
You can learn about this more in here
If I have a console output from here:
const PeriodicTable = () => {
return (
<div layout className="periodic-table">
{data.elements.map((element) => (
<button
className="element"
key={element.name}
onClick={()=> {console.log(element)}}
style={{
gridRow: element.ypos,
gridColumn: element.xpos,
borderColor: colorMap[element.category],
}}
So when I click on an element on the periodic table the console.log(element) is an output of element data from a JSON file. All I want is to take the output and print it out onto a html tag. I've already tried the document.write() method but to no avail.
perhaps you can pass that value from button to the state on parent components, and just pass it to Card Component
let's say you have json like this
const data = {
elements: [
{
name: "steve",
ypos: 1,
xpos: 1,
color: "red"
},
{
name: "james",
ypos: 2,
xpos: 2,
color: "red"
}
]
};
and PeriodicTabel will look like this
const PeriodicTable = (props) => {
return (
<div className="periodic-table">
{data.elements.map((element, id) => (
<button
key={id}
className="element"
onClick={() => props.handleOnCLick(element)}
style={{
width: "100px",
gridRow: element.ypos,
gridColumn: element.xpos,
borderColor: element.color
}}
>
click me
</button>
))}
</div>
);
};
then in app component you can make handleOnclick to make it as props on PeridocTable component
export default function App() {
const [val, setVal] = useState({});
const handleOnCLick = (val) => {
setVal(val);
};
return (
<div className="App">
<PeriodicTable handleOnCLick={handleOnCLick} />
<Card value={val} />
</div>
);
}
so in app component you can make a state to accept value from the button on PeriodicTable component
const [val, setVal] = useState({});
and then you can pass it to the card
<Card value={val} />
Card component will be look like this
const Card = (props) => {
return (
<div
className="card"
style={{
width: "200px",
background: "red",
height: "250px",
margin: "0 auto"
}}
>
<h2>title card</h2>
<p>{props.value.name}</p>
</div>
);
};
this is final result of the source code
import { useState } from "react";
import "./styles.css";
const data = {
elements: [
{
name: "steve",
ypos: 1,
xpos: 1,
color: "red"
},
{
name: "james",
ypos: 2,
xpos: 2,
color: "red"
}
]
};
const Card = (props) => {
return (
<div
className="card"
style={{
width: "200px",
background: "red",
height: "250px",
margin: "0 auto"
}}
>
<h2>title card</h2>
<p>{props.value.name}</p>
</div>
);
};
const PeriodicTable = (props) => {
return (
<div className="periodic-table">
{data.elements.map((element, id) => (
<button
key={id}
className="element"
onClick={() => props.handleOnCLick(element)}
style={{
width: "100px",
gridRow: element.ypos,
gridColumn: element.xpos,
borderColor: element.color
}}
>
click me
</button>
))}
</div>
);
};
export default function App() {
const [val, setVal] = useState({});
const handleOnCLick = (val) => {
setVal(val);
};
return (
<div className="App">
<PeriodicTable handleOnCLick={handleOnCLick} />
<Card value={val} />
</div>
);
}
link to the preview in codesandbox
I'm making an app that makes a bill of items and the user can identify the quantity of every item
and at least there is a text tag which it shows the total price after hitting " get bill " button
it works fine , but the issue is after hitting the button the items are repeated again
[here a screenshot of the app after hitting a 'get bill' button]: https://i.stack.imgur.com/slOWN.jpg
this is the code :
import React, { Component } from "react";
import { View, FlatList, Dimensions, Text } from "react-native";
import * as SQLite from "expo-sqlite";
import { Button } from "react-native-elements";
import NumericInput from "react-native-numeric-input";
const db = SQLite.openDatabase("db.db");
class CreateBill extends Component {
constructor() {
super();
this.state = {
items: [],
itemsList: [],
products: [],
totalPrice: 0,
};
}
async componentDidMount() {
console.log("did mount");
db.transaction((tx) => {
tx.executeSql("SELECT * FROM products2", [], (tx, results) => {
var temp = [];
for (let i = 0; i < results.rows.length; ++i) {
temp.push(results.rows.item(i));
}
this.setState({
products: temp,
});
});
});
}
checkItems = () => {
this.state.items.forEach((item) => {
this.state.products.forEach((product) => {
if (item === product.code) {
console.log("equals", item, product.code);
this.state.itemsList.push({ product, number: 0 });
} else {
console.log("doesnt equal", item.item, product.code);
}
});
});
};
getPrice = () => {
let price = 0;
this.state.itemsList.forEach((element) => {
price = element.number * element.product.price;
});
this.setState({ totalPrice: price });
};
render() {
this.state.items = this.props.route.params.items;
this.checkItems();
return (
<View style={{ flex: 1 }}>
<FlatList
data={this.state.itemsList}
extraData={this.state}
style={{ marginTop: 50, flex: 1, width: Dimensions.width }}
keyExtractor={(item) => item.product.id}
renderSeparator={() => (
<View
style={{
backgroundColor: "#1B73B4",
height: 0.6,
}}
/>
)}
renderItem={({ item, index }) => {
console.log("item", item);
return (
<View
style={{
flexDirection: "row",
width: Dimensions.width,
paddingStart: 20,
alignItems: "center",
}}
key={item}
>
<View style={{ flex: 0.5, alignItems: "center" }}>
<Text style={{ fontSize: 24 }}>{item.product.name}</Text>
</View>
<View style={{ flex: 0.5, justifyContent: "center" }}>
<NumericInput
value={this.state.itemsList[index].number}
onChange={(value) => {
let item1 = this.state.itemsList[index];
item1.number = value;
console.log(
this.state.itemsList[index].product.name,
this.state.itemsList[index].number
);
}}
totalWidth={140}
totalHeight={45}
iconSize={25}
maxValue={this.state.itemsList[index].product.units}
minValue={0}
valueType="integer"
rounded
textColor="#1B73B4"
iconStyle={{ color: "white" }}
rightButtonBackgroundColor="#1B73B4"
leftButtonBackgroundColor="#1B73B4"
/>
</View>
</View>
);
}}
/>
<Text>{this.state.totalPrice}</Text>
<Button title="get bill" onPress={() => this.getPrice()} />
</View>
);
}
}
export { CreateBill };
In your case reduce is the best option:
Documentation here
Example:
const totalPrice = this.state.itemsList.reduce((total, element) =>
total += element.number * element.product.price
,0);
this.setState({ totalPrice });
i want hide the uids from displaying on my display page, right now it displays -LrM8rlKq1-dSW6XRt0_({"color: 'blue"}), but i just want it to display ({"color: 'blue"}) without uid(-LrM8rlKq1-dSW6XRt0_) beside it enter image description here
enter image description here
import React, { Component } from 'react';
import { View, Text, StyleSheet, Button, TextInput, TouchableOpacity } from 'react-native';
import firebase from './firebase';
export default class App extends Component {
carDatabase = firebase.database().ref('car');
state = { cars: {}, selectedId: '' }
// Read
componentDidMount() {
this.carDatabase.on('value', cars => {
const carsJSON = cars.val();
this.setState({ cars: carsJSON === null ? {} : carsJSON });
})
// this.carDatabase.push({color: 'yellow'})
}
// Create
create() {
this.carDatabase.push({color: 'yellow'})
this.setState({selectedId: ''})
}
// Update
update() {
this.carDatabase.child(this.state.selectedId).set({color: 'blue'})
this.setState({selectedId: ''})
}
// Delete
deleteCar() {
if(this.state.selectedId === '') {
return;
}
this.carDatabase.child(this.state.selectedId).set(null)
this.setState({selectedId: ''})
}
render() {
return (
<View style={styles.container}>
<TextInput value={this.state.selectedId} style={styles.textInput}></TextInput>
<Button title="create" onPress={() => this.create()}></Button>
<Button title="update" onPress={() => this.update()}></Button>
<Button title="delete" onPress={() => this.deleteCar()}></Button>
{
Object.keys(this.state.cars).map( (carId, index) => (
<Text style={{color: this.state.cars[carId].color}}>hi !</Text>
)
}
{/* <Text>{JSON.stringify(this.state.cars, null, 2)}</Text> */}
</View>
);
}
}
const styles = StyleSheet.create({
textInput: {
backgroundColor: 'green',
height: 30,
width: '100%'
},
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
});
I can't get correct value into the store when trying to upload a file. Instead of file content, I get something like { 0: {} }.
Here's the code:
const renderInput = field => (
<div>
<input {...field.input} type={field.type}/>
{
field.meta.touched &&
field.meta.error &&
<span className={styles.error}>{field.meta.error}</span>
}
</div>
);
render() {
...
<form className={styles.form} onSubmit={handleSubmit(submit)}>
<div className={styles.interface}>
<label>userpic</label>
<Field
name="userpic"
component={renderInput}
type="file"
/>
</div>
<div>
<button type="submit" disabled={submitting}>Submit</button>
<div>
</form>
...
}
All the examples on the web that I found were made using v5 of redux-form.
How do I do file input in redux-form v6?
Create a Field Component like:
import React, {Component} from 'react'
export default class FieldFileInput extends Component{
constructor(props) {
super(props)
this.onChange = this.onChange.bind(this)
}
onChange(e) {
const { input: { onChange } } = this.props
onChange(e.target.files[0])
}
render(){
const { input: { value } } = this.props
const {input,label, required, meta, } = this.props //whatever props you send to the component from redux-form Field
return(
<div><label>{label}</label>
<div>
<input
type='file'
accept='.jpg, .png, .jpeg'
onChange={this.onChange}
/>
</div>
</div>
)
}
}
Pass this component to the Field component where you needed. No need of additional Dropzone or other libraries if you are after a simple file upload functionality.
My example of redux form input wrapper with Dropzone
import React, {Component, PropTypes} from 'react';
import Dropzone from 'react-dropzone';
import { Form } from 'elements';
import { Field } from 'redux-form';
class FileInput extends Component {
static propTypes = {
dropzone_options: PropTypes.object,
meta: PropTypes.object,
label: PropTypes.string,
classNameLabel: PropTypes.string,
input: PropTypes.object,
className: PropTypes.string,
children: PropTypes.node,
cbFunction: PropTypes.func,
};
static defaultProps = {
className: '',
cbFunction: () => {},
};
render() {
const { className, input: { onChange }, dropzone_options, meta: { error, touched }, label, classNameLabel, children, name, cbFunction } = this.props;
return (
<div className={`${className}` + (error && touched ? ' has-error ' : '')}>
{label && <p className={classNameLabel || ''}>{label}</p>}
<Dropzone
{...dropzone_options}
onDrop={(f) => {
cbFunction(f);
return onChange(f);
}}
className="dropzone-input"
name={name}
>
{children}
</Dropzone>
{error && touched ? error : ''}
</div>
);
}
}
export default props => <Field {...props} component={FileInput} />;
Hot to use it:
<FileInput
name="add_photo"
label="Others:"
classNameLabel="file-input-label"
className="file-input"
dropzone_options={{
multiple: false,
accept: 'image/*'
}}
>
<span>Add more</span>
</FileInput>
Another way to do it that will render a preview image (the below example uses React 16+ syntax and only accepts a single image file to send to an API; however, with some minor tweaks, it can also scale to multiple images and other fields inputs):
Working example: https://codesandbox.io/s/m58q8l054x
Working example (outdated): https://codesandbox.io/s/8kywn8q9xl
Before:
After:
containers/UploadForm.js
import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import DropZoneField from "../components/dropzoneField";
const imageIsRequired = value => (!value ? "Required" : undefined);
class UploadImageForm extends Component {
state = { imageFile: [] };
handleFormSubmit = formProps => {
const fd = new FormData();
fd.append("imageFile", formProps.imageToUpload.file);
// append any additional Redux form fields
// create an AJAX request here with the created formData
alert(JSON.stringify(formProps, null, 4));
};
handleOnDrop = (newImageFile, onChange) => {
const imageFile = {
file: newImageFile[0],
name: newImageFile[0].name,
preview: URL.createObjectURL(newImageFile[0]),
size: newImageFile[0].size
};
this.setState({ imageFile: [imageFile] }, () => onChange(imageFile));
};
resetForm = () => this.setState({ imageFile: [] }, () => this.props.reset());
render = () => (
<div className="app-container">
<h1 className="title">Upload An Image</h1>
<hr />
<Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
<Field
name="imageToUpload"
component={DropZoneField}
type="file"
imagefile={this.state.imageFile}
handleOnDrop={this.handleOnDrop}
validate={[imageIsRequired]}
/>
<button
type="submit"
className="uk-button uk-button-primary uk-button-large"
disabled={this.props.submitting}
>
Submit
</button>
<button
type="button"
className="uk-button uk-button-default uk-button-large"
disabled={this.props.pristine || this.props.submitting}
onClick={this.resetForm}
style={{ float: "right" }}
>
Clear
</button>
</Form>
<div className="clear" />
</div>
);
}
export default reduxForm({ form: "UploadImageForm" })(UploadImageForm);
components/dropzoneField.js
import React from "react";
import PropTypes from "prop-types";
import DropZone from "react-dropzone";
import ImagePreview from "./imagePreview";
import Placeholder from "./placeholder";
import ShowError from "./showError";
const DropZoneField = ({
handleOnDrop,
input: { onChange },
imagefile,
meta: { error, touched }
}) => (
<div className="preview-container">
<DropZone
accept="image/jpeg, image/png, image/gif, image/bmp"
className="upload-container"
onDrop={file => handleOnDrop(file, onChange)}
>
{({ getRootProps, getInputProps }) =>
imagefile && imagefile.length > 0 ? (
<ImagePreview imagefile={imagefile} />
) : (
<Placeholder
error={error}
touched={touched}
getInputProps={getInputProps}
getRootProps={getRootProps}
/>
)
}
</DropZone>
<ShowError error={error} touched={touched} />
</div>
);
DropZoneField.propTypes = {
error: PropTypes.string,
handleOnDrop: PropTypes.func.isRequired,
imagefile: PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.file,
name: PropTypes.string,
preview: PropTypes.string,
size: PropTypes.number
})
),
label: PropTypes.string,
onChange: PropTypes.func,
touched: PropTypes.bool
};
export default DropZoneField;
components/imagePreview.js
import React from "react";
import PropTypes from "prop-types";
const ImagePreview = ({ imagefile }) =>
imagefile.map(({ name, preview, size }) => (
<div key={name} className="render-preview">
<div className="image-container">
<img src={preview} alt={name} />
</div>
<div className="details">
{name} - {(size / 1024000).toFixed(2)}MB
</div>
</div>
));
ImagePreview.propTypes = {
imagefile: PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.file,
name: PropTypes.string,
preview: PropTypes.string,
size: PropTypes.number
})
)
};
export default ImagePreview;
components/placeholder.js
import React from "react";
import PropTypes from "prop-types";
import { MdCloudUpload } from "react-icons/md";
const Placeholder = ({ getInputProps, getRootProps, error, touched }) => (
<div
{...getRootProps()}
className={`placeholder-preview ${error && touched ? "has-error" : ""}`}
>
<input {...getInputProps()} />
<MdCloudUpload style={{ fontSize: 100, paddingTop: 85 }} />
<p>Click or drag image file to this area to upload.</p>
</div>
);
Placeholder.propTypes = {
error: PropTypes.string,
getInputProps: PropTypes.func.isRequired,
getRootProps: PropTypes.func.isRequired,
touched: PropTypes.bool
};
export default Placeholder;
components/showError.js
import React from "react";
import PropTypes from "prop-types";
import { MdInfoOutline } from "react-icons/md";
const ShowError = ({ error, touched }) =>
touched && error ? (
<div className="error">
<MdInfoOutline
style={{ position: "relative", top: -2, marginRight: 2 }}
/>
{error}
</div>
) : null;
ShowError.propTypes = {
error: PropTypes.string,
touched: PropTypes.bool
};
export default ShowError;
styles.css
img {
max-height: 240px;
margin: 0 auto;
}
.app-container {
width: 500px;
margin: 30px auto;
}
.clear {
clear: both;
}
.details,
.title {
text-align: center;
}
.error {
margin-top: 4px;
color: red;
}
.has-error {
border: 1px dotted red;
}
.image-container {
align-items: center;
display: flex;
width: 85%;
height: 80%;
float: left;
margin: 15px 10px 10px 37px;
text-align: center;
}
.preview-container {
height: 335px;
width: 100%;
margin-bottom: 40px;
}
.placeholder-preview,
.render-preview {
text-align: center;
background-color: #efebeb;
height: 100%;
width: 100%;
border-radius: 5px;
}
.upload-container {
cursor: pointer;
height: 300px;
}
I managed to do it with redux-form on material-ui wrapping TextField like this:
B4 edit:
After edit:
<Field name="image" component={FileTextField} floatingLabelText={messages.chooseImage} fullWidth={true} />
with component defined as:
const styles = {
button: {
margin: 12
},
exampleImageInput: {
cursor: 'pointer',
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
left: 0,
width: '100%',
opacity: 0
},
FFS:{
position: 'absolute',
lineHeight: '1.5',
top: '38',
transition: 'none',
zIndex: '1',
transform: 'none',
transformOrigin: 'none',
pointerEvents: 'none',
userSelect: 'none',
fontSize: '16',
color: 'rgba(0, 0, 0, 0.8)',
}
};
export const FileTextField = ({
floatingLabelText,
fullWidth,
input,
label,
meta: { touched, error },
...custom })=>{
if (input.value && input.value[0] && input.value[0].name) {
floatingLabelText = input.value[0].name;
}
delete input.value;
return (
<TextField
hintText={label}
fullWidth={fullWidth}
floatingLabelShrinkStyle={styles.FFS}
floatingLabelText={floatingLabelText}
inputStyle={styles.exampleImageInput}
type="file"
errorText={error}
{...input}
{...custom}
/>
)
}
If you need base64 encoding to send it to your backend, here is a modified version that worked for me:
export class FileInput extends React.Component {
getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
onFileChange = async (e) => {
const { input } = this.props
const targetFile = e.target.files[0]
if (targetFile) {
const val = await this.getBase64(targetFile)
input.onChange(val)
} else {
input.onChange(null)
}
}
render() {
return (
<input
type="file"
onChange={this.onFileChange}
/>
)
}
}
Then your field component would look like:
<Field component={FileInput} name="primary_image" type="file" />
For React >= 16 and ReduxForm >= 8 (tested version are 16.8.6 for React and 8.2.5)
works following component.
(Solution posted in related GitHub issue by DarkBitz)
const adaptFileEventToValue = delegate => e => delegate(e.target.files[0]);
const FileInput = ({
input: { value: omitValue, onChange, onBlur, ...inputProps },
meta: omitMeta,
...props
}) => {
return (
<input
onChange={adaptFileEventToValue(onChange)}
onBlur={adaptFileEventToValue(onBlur)}
type="file"
{...props.input}
{...props}
/>
);
};
export const FileUpload = (props) => {
const { handleSubmit } = props;
const onFormSubmit = (data) => {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onFormSubmit)}>
<div>
<label>Attachment</label>
<Field name="attachment" component={FileInput} type="file"/>
</div>
<button type="submit">Submit</button>
</form>
)
}
With Redux Form
const { handleSubmit } = props;
//make a const file to hold the file prop.
const file = useRef();
// create a function to replace the redux-form input-file value to custom value.
const fileUpload = () => {
// jsx to take file input
// on change store the files /file[0] to file variable
return (
<div className='file-upload'>
<input
type='file'
id='file-input'
accept='.png'
onChange={(ev) => {
file.current = ev.target.files;
}}
required
/>
</div>
);
};
//catch the redux-form values!
//loop through the files and add into formdata
//form data takes key and value
//enter the key name as multer-config fieldname
//then add remaining data into the formdata
//make a request and send data.
const onSubmitFormValues = (formValues) => {
const data = new FormData();
for (let i = 0; i < file.current.length; i++) {
data.append("categoryImage", file.current[i]);
}
data.append("categoryName", formValues.categoryName);
Axios.post("http://localhost:8080/api/v1/dev/addNewCategory", data)
.then((response) => console.log(response))
.catch((err) => console.log(err));
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You can also use react-dropzone for this purpose. The below code worked fine for me
filecomponent.js
import React from 'react'
import { useDropzone } from 'react-dropzone'
function MyDropzone(props) {
const onDrop = (filesToUpload) => {
return props.input.onChange(filesToUpload[0]);
}
const onChange = (filesToUpload) => {
return props.input.onChange(filesToUpload[0]);
}
const { getRootProps, getInputProps } = useDropzone({ onDrop });
return (
<div {...getRootProps()}>
<input {...getInputProps()} onChange={e => onChange(e.target.files)} />
<p> Drop or select yout file</p>
</div>
)
}
export default MyDropzone;
In form use this
<Field
name="myfile"
component={renderFile}
/>