Input file with label not updating state - reactjs

I want to make input type file with custom style, I hide input and styling the label using css. There are 2 problems:
I chose file, but coverFileState is undefinded (if I remove css style and choose file then everything is okay).
I want to set file name inside label, but it isn't working. I set text using setCoverUploadText inside handleCoverChange, but no effect.
If you help me with that problem I will really appreciate this, thanks!
const useStyles = makeStyles((theme) => ({
fileInput: {
marginBottom: '1em',
width: '0.1px',
height: '0.1px',
opacity: '0',
overflow: "hidden",
position: "absolute"
},
uploadLabel: {
fontSize: '1rem',
cursor: 'pointer',
color: "gray",
border: "1px solid gray"
}
}));
export default function AddingBook(props: ParamsProps) {
const classes = useStyles();
const coverFile = useRef(null);
const [coverFileState, setCoverFile] = useState<string | Blob>();
const [coverUploadText, setCoverUploadText] = useState("Upload cover photo *");
const handleSubmit = () => {
if (coverFileState === undefined) {
setImageErrorMsg("*Please add cover photo.");
return;
}
api.post('/image/add', JSON.stringify(REST))
.then(res => {
uploadCoverPhoto();
props.close();
}).catch(err => {
const errorMsg = APIServices.onError(err);
showErrorPopup(errorMsg);
})
};
const handleCoverChange = () => {
// #ts-ignore
setCoverFile(coverFile.current.files[0]);
// #ts-ignore
setCoverUploadText(coverFile.current.files[0].name)
};
return (
<>
<div>
<input
id="copy-file-upload"
type="file"
accept="image/*"
ref={coverFile}
onChange={handleCoverChange}
data-testid="inputFile"
className={classes.fileInput}
/>
<label className={classes.uploadLabel} htmlFor="copy-file-upload">{coverUploadText}</label>
</div>
</>
);

Try to replace "className" by "style" for the input file and for the label.
Hope it will helps you
EDIT:
it worked for me when i set the style as :
const useStyles = {
fileInput: {
marginBottom: '1em',
width: '0.1px',
height: '0.1px',
opacity: '0',
overflow: "hidden",
position: "absolute"
},
uploadLabel: {
fontSize: '1rem',
cursor: 'pointer',
color: "gray",
border: "1px solid gray"
}
};
and use it in the return as :
....
<label style={useStyles.uploadLabel} htmlFor="copy-file-upload">{coverUploadText}</label>
....

Related

Arrow is broken after style changes React-select

I'm kind of new to React-Select, and after changing its styles the arrow broke, I've spent 3 hours searching for the arrow placement controls to no avail, I've found how to replace it but it doesn't solve the issue, I'm deciding if I should abandon the lib approach and make the dropdown from scratch since the code looks horrendous, I apologize
This is how it currently looks like:
This is how I'm trying to make it behave like:
import Select from "react-select";
interface DropdownV2 {
color?: string[];
options: string[];
name: string;
}
export const DropdownV2: React.FC<DropdownV2> = ({ color, options, name }) => {
let optionsList: any[] = [];
let counter = 0;
options.map((option) => {
if (color) {
optionsList.push({ label: option, value: option });
} else {
optionsList.push({ label: option, value: option });
}
});
return (
<Select
defaultValue={options[0]}
isMulti={false}
placeholder={"Choose " + name}
styles={customStyles}
options={optionsList}
/>
);
};
const customStyles = {
option: (provided: any, state: { isSelected: any }) => ({
...provided,
borderBottom: "1px solid #e9e9e2",
borderLeft: "1px solid #e9e9e2",
borderRight: "1px solid #e9e9e2",
borderRadius: "4px",
color: state.isSelected ? "#290363" : "#150132",
backgroundColor: state.isSelected ? "#e9e9e2" : "white",
cursor: "pointer",
padding: 20,
}),
control: () => ({
// none of react-select's styles are passed to <Control />
width: 200,
}),
menu: (provided: any) => ({
...provided,
border: "1px solid #e9e9e2",
}),
placeholder: (base: any) => ({
...base,
color: "#150132",
fontFamily: "inter",
fontStyle: "normal",
fontWeight: "400",
fontSize: "14px",
lineHeight: "24px",
boxSizing: "border-box",
border: "1px solid #150132",
borderRadius: "4px",
padding: "12px 18px",
gap: "10px",
}),
singleValue: (provided: any, state: { isDisabled: any }) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = "opacity 300ms";
const width = "100%";
const height = "50px";
const textAlign = "left";
const fontFamily = "inter";
const fontStyle = "normal";
const fontWeight = "400";
const fontSize = "14px";
const lineHeight = "24px";
const boxSizing = "border-box";
const display = "block";
const flexDirection = "row";
const justifyContent = "space-between";
const alignItems = "center";
const padding = "12px 18px";
const gap = "10px";
const border = "1px solid #150132";
const borderRadius = "4px";
const flex = "none";
const order = "0";
const alignSelf = "stretch";
const flexGrow = "0";
const backgroundPositionX = "244px";
const marginBottom = "10px";
return {
...provided,
opacity,
transition,
border,
width,
height,
textAlign,
padding,
fontFamily,
fontStyle,
fontWeight,
fontSize,
lineHeight,
boxSizing,
display,
flexDirection,
justifyContent,
alignItems,
gap,
borderRadius,
flex,
order,
alignSelf,
flexGrow,
backgroundPositionX,
marginBottom,
};
},
};
<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>
I provide a custom DropdownIndicator to my react-select.
import React from 'react';
import { components } from 'react-select';
import classnames from 'classnames';
import Icon from '../../../../components/Icon.component';
import styles from '../Select.module.scss';
const DropdownIndicator = ({
className,
selectProps,
...props
}) => (
<components.DropdownIndicator
{...props}
className={classnames(styles.selectIndicator, className)}
selectProps={selectProps}
>
<Icon glyph={`caret-${selectProps.menuIsOpen ? 'up' : 'down'}`} />
</components.DropdownIndicator>
);
You can also change base styling of your component using a custom styles function
export const dropdownIndicatorStyles = (base, state) => {
const { isDisabled } = state;
return {
...base,
...(isDisabled && { display: 'none' }),
};
};
That I then pass it all in to my Select
import DropdownIndicator from './DropdownIndicator';
import {dropdownIndicatorStyles} from './styleFunctions';
// ...
const {components, styles} = useMemo(() => ({
components: {
DropdownIndicator
},
styles: {
dropdownIndicator: dropdownIndicatorStyles
}
}), []);
// ...
<Select components={components} styles={styles} {...otherProps} />

Changing the arrow/icon colour material ui select

I'm trying to change my icon to white
Here is my current code but not sure how to change icon colour property:
Link to codesandbox sample
<NativeSelect
disableUnderline
style={{
paddingLeft: 10,
borderRadius: 20,
color: "white",
background: "#121c30",
boxShadow: "0px 5px 8px -3px rgba(99,0,0,0.14)",
}}
defaultValue={"Last 7 days"}
onChange={handleChange}
>
......
</NativeSelect>
The material provides you, one class, for changing the color of that icon.
.MuiNativeSelect-icon {
color: red
}
const MyIcon = (props)=> {
const { style, ...otherProps } = props;
const colorStyle = {
color: 'white',
};
const styles = { ...style, ...colorStyle };
return <ArrowDropDownIcon {...otherProps} style={styles} />
}
<NativeSelect
IconComponent={MyIcon}
...
/>
I would recommend not to use inline styles, as you cannot apply them in classes property, in your case this should work for you
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
iconSelect: {
color: "white"
},
});
...
export default function App() {
const classes = useStyles();
...
<NativeSelect
disableUnderline
classes={{icon: classes.iconSelect}}
...
>
https://codesandbox.io/s/table-forked-7w6nw
Create .css, where is your choice.
there. instead of writing: color:white; you can write color:white !important;

Styled-components - dynamic CSS?

I have a simple React app that gets CSS from an external source in JSON form, the styles look like this:
{
"DOMElements": [
{
"padding": "1rem",
"margin": "5rem",
},
{
"boxSizing": "border-box",
"height": "10px",
}
]
}
So when I get a response like above I want end up with something like this:
import styled from 'styled-components';
const DOMElement1 = styled.div`
padding: 1rem,
margin: 5rem,
`;
const DOMElement2 = styled.div`
boxSizing: border-box,
height: 10px,
`;
const Page = ({ children }) => (
<>
<DOMElement1>{children[1]}</DOMElement1>
<DOMElement2>{childrem[2]}</DOMElement2>
</>
);
It's worth noting that number of DOMElements in unknown, it might be 1, it might be 50.
The component part is easy, I can do just do indexed map and increase index every loop.
The problem I face is - how do I create dynamic styled-components props based on JSON response? I need to do it within the component itself as that's where I know how DOMElements look like, but styled-components are supposed to be outside of the component function... What am I missing? Is it even doable?
You can give props for styled components.
const DOMElement1 = styled.div`
padding: ${({padding}) => padding}rem,
margin: ${({margin}) => margin}rem,
`;
const Page = ({ children }) => (
<>
<DOMElement1 padding={valueFromJson} margin={valueFromJson}>{children[1]}</DOMElement1>
<DOMElement2 padding={valueFromJson} margin={valueFromJson}>{childrem[2]}</DOMElement2>
</>
);
Something like this:
const items = [
{
background: "blue",
width: "30px",
height: "30px",
padding:'10px'
},
{
height: "40px",
background: "red",
width: "30px",
padding:'10px'
}
];
const components = items.map((item) =>
styled("div")({
...item
})
);
export default function App() {
return (
<div style={{ background: "lightblue", width: "100vw", height: "100vh" }}>
{components.map(Comp => <Comp>hi</Comp>)}
</div>
);
}
A simple & straightforward solution would be taking props dynamically in your styled component and this way you don't need to worry about any incoming css property.
Refer the following example/ this snippet -
const DOMElement = styled.div`
${props => props}
`;
const Page = () => {
const data = {
"DOMElements": [
{
"padding": "1rem",
"margin": "5rem",
},
{
"boxSizing": "border-box",
"height": "10px",
}
]
};
return (
<>
{data?.DOMElements?.map((obj, index) =>
<DOMElement {...obj} key={index}>abcd</DOMElement>
)}
</>
);
}
Output snap -

StopPropagation react google maps autocomplete

I would like to stopPropagation of https://react-google-maps-api-docs.netlify.app/#autocomplete.
The autocomplete works well but I need to use it in a popover and when I select a place the popover automatically closes.
If there is another library that works well with popovers and modals
const [autocomplete, setAutocomplete] = useState<any>(null);
const onLoad = (auto: any) => {
if (!autocomplete) {
setAutocomplete(auto);
}
};
const onPlaceChanged = () => {
if (autocomplete) {
console.log(autocomplete?.getPlace());
} else {
console.log('Autocomplete is not loaded yet!');
}
};
<Autocomplete
onLoad={onLoad}
onPlaceChanged={onPlaceChanged}
>
<chakra.input
type='text'
as={Input}
placeholder='Customized your placeholder'
style={{
boxSizing: `border-box`,
border: `1px solid transparent`,
width: `240px`,
height: `32px`,
padding: `0 12px`,
borderRadius: `3px`,
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.3)`,
fontSize: `14px`,
outline: `none`,
textOverflow: `ellipses`,
position: 'absolute',
left: '50%',
marginLeft: '-120px',
animationName: 'none',
zIndex: 9999,
}}
/>
</Autocomplete>
I'm using chakraUI, I get help from discord(dodas user) here is the solution he make. Basically he catch the mousedown event(google block click event) and when the popover is open, select item or click trigger button just do nothing for others events just close popovers.
https://codesandbox.io/s/popovergoogle-autocomplete-6nvtb?file=/src/App.js:0-3329
import React, { useState, useEffect, useRef } from "react";
import {
Button,
Stack,
Popover,
PopoverTrigger,
PopoverContent,
PopoverHeader,
PopoverBody,
PopoverArrow,
PopoverCloseButton,
Box,
Portal,
PopoverFooter,
useRadioGroup,
useRadio
} from "#chakra-ui/core";
import { useEvent } from "react-use";
import {
GoogleMap,
useJsApiLoader,
Autocomplete
} from "#react-google-maps/api";
let autoComplete;
export function App() {
const { isLoaded, loadError } = useJsApiLoader({
googleMapsApiKey: "YOUR_KEY", // ,
libraries: ["places"]
// ...otherOptions
});
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const openPopover = () => setIsPopoverOpen(true);
const closePopover = () => setIsPopoverOpen(false);
const triggerRef = useRef(null);
const popoverContentRef = useRef(null);
const [autocomplete, setAutocomplete] = useState(null);
const onLoad = (autocompleteInstance) => {
console.log("autocomplete: ", autocomplete);
setAutocomplete(autocompleteInstance);
};
const onPlaceChanged = () => {
if (autocomplete) {
console.log(autocomplete.getPlace());
} else {
console.log("Autocomplete is not loaded yet!");
}
};
useEvent("mousedown", (ev) => {
if (!isPopoverOpen) {
return;
}
const clickedInsideTrigger = triggerRef.current.contains(ev.target);
const clickedInsidePopover = popoverContentRef.current.contains(ev.target);
const clickedInsideAutocomplete =
ev.target.closest(".pac-container") != null;
if (
clickedInsideTrigger ||
clickedInsidePopover ||
clickedInsideAutocomplete
) {
return;
}
closePopover();
});
return (
<>
<Box width="100vw" height="100vh">
<Popover
isOpen={isPopoverOpen}
onOpen={openPopover}
onClose={closePopover}
closeOnBlur={false}
isLazy
placement="bottom-end"
>
<PopoverTrigger>
<Button ref={triggerRef}>Trigger</Button>
</PopoverTrigger>
<PopoverContent ref={popoverContentRef}>
<PopoverArrow />
<PopoverCloseButton />
<PopoverHeader>Confirmation!</PopoverHeader>
<PopoverBody minH="20rem">
{isLoaded && (
<Autocomplete onLoad={onLoad} onPlaceChanged={onPlaceChanged}>
<input
type="text"
placeholder="Customized your placeholder"
style={{
boxSizing: `border-box`,
border: `1px solid transparent`,
width: `240px`,
height: `32px`,
padding: `0 12px`,
borderRadius: `3px`,
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.3)`,
fontSize: `14px`,
outline: `none`,
textOverflow: `ellipses`,
position: "absolute",
left: "50%",
marginLeft: "-120px"
}}
/>
</Autocomplete>
)}
</PopoverBody>
</PopoverContent>
</Popover>
</Box>
</>
);
}

In React, how to pass a dynamic variable to a const CSS Style list?

I'm using react-dropzone to allow a user to upload a profile photo.
I define the custom CSS like so:
const dropzoneStyle = {
width: `200px`,
height: `200px`,
backgroundColor: `#1DA1F2`,
};
Inside the method to render the DropZone input, I can detect if their is a file preview which is populated after a user selects an image to be uploaded..
What I want to do is, if the file.preview exists, send the file.preview the the dropzoneStyle so a background-image is added to the CSS.
const renderDropzoneInput = (field) => {
const files = field.input.value;
let dropzoneRef;
if (files[0]) {
console.log(files[0].preview)
}
return (
<div>
<Dropzone
name={field.name}
ref={(node) => { dropzoneRef = node; }}
accept="image/jpeg, image/png"
style={dropzoneStyle}
>
How can I pass files[0].preview to the style dropzoneStyle with React?
I usually just define the style as an arrow function that returns the style object, and pass in whatever parameters are needed for the style. There is a shorthand notation for returning an object literal from an arrow function that works nicely for this.
const style = () => ({});
Just remember to only use ternary operators if using the shorthand, otherwise you would just need to explicitly return an object.
So, for your style:
const dropzoneStyle = (isPreview) => ({
width: `200px`,
height: `200px`,
backgroundColor: `#1DA1F2`,
backgroundImage: (isPreview) ? 'url(/path/to/image.jpg)' : 'none',
});
This adds the image is isPreview is true, but keeps it blank if not.
Then in your component, call the function where the style goes:
return (
<div>
<Dropzone
{...otherProps}
style={ dropzoneStyle(isPreview) }
>
</div>
);
Assuming files[0].preview returns a file (image) URL, you should be able to set a new style and pass it to the Dropzone component.
Something along these lines:
const renderDropzoneInput = (field) => {
const files = field.input.value;
let dropzoneRef;
render() {
let dropzoneStyle = {
width: `200px`,
height: `200px`,
backgroundColor: `#1DA1F2`,
};
if (files[0]) {
dropzoneStyle = {
width: `200px`,
height: `200px`,
backgroundColor: `#1DA1F2`,
backgroundImage: `url(${files[0].preview})`,
// or to use a fixed background image
// backgroundImage: `url(/path/to/static/preview.png)`,
backgroundPosition: `center center`,
backgroundRepeat: `no-repeat`
};
}
return (
<Dropzone
name={field.name}
ref={(node) => { dropzoneRef = node; }}
accept="image/jpeg, image/png"
style={dropzoneStyle}
/>
)
}
}
a spread operator could be used to DRY this code a bit, with:
let dropzoneStyle = {
width: `200px`,
height: `200px`,
backgroundColor: `#1DA1F2`,
};
if (files[0]) {
dropzoneStyle = {
...dropzoneStyle,
backgroundImage: `url(/path/to/static/preview.png)`,
backgroundPosition: `center center`,
backgroundRepeat: `no-repeat`
};
}

Resources