I am trying to add an image cropper component to my project, for that using the react-cropper package. But there is a problem that how to add a fixed width and height for cropper box like "width:200px; height:300px;"
import React, { useState } from "react";
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
import "./Demo.css";
type Props = {
LabelName?: string;
};
export const Demo = (props:Props) => {
const {LabelName} = props;
const [image, setImage] = useState("");
const [cropData, setCropData] = useState("#");
const [cropper, setCropper] = useState<any>();
const onChange = (e: any) => {
e.preventDefault();
let files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
const reader = new FileReader();
reader.onload = () => {
setImage(reader.result as any);
};
reader.readAsDataURL(files[0]);
};
const getCropData = () => {
if (typeof cropper !== "undefined") {
setCropData(cropper.getCroppedCanvas().toDataURL());
}
};
return (
<div>
<div style={{ width: "100%" }}>
<input type="file" onChange={onChange} />
<br />
<Cropper
zoomTo={0.5}
initialAspectRatio={1}
preview=".img-preview"
src={image}
viewMode={1}
minCropBoxHeight={10}
minCropBoxWidth={10}
background={false}
responsive={true}
autoCropArea={1}
checkOrientation={false}
onInitialized={(instance) => {
setCropper(instance);
}}
guides={true}
/>
</div>
<br style={{ clear: "both" }} />
</div>
);
};
export default Demo;
Now I can change like this,
But my requirement is to fix that copper size, Please give me a solution to fix this situation.
Size:
minCropBoxWidth: 200,
minCropBoxHeight: 300,
Required aspect ratio:
aspectRatio: 1 / 1.5,
Block change cropBox:
cropBoxResizable: false,
Related
I just starting to learn React. I've been having issues with the react-image-crop package. The document they have was not newbie friendly, I just barely made it work at this point. Now my issue is that the result cropped image is totally different from the user's selection. My thought is that it might be caused by the scaling of the original image when you select the cropping area. I have limited the window size because some people might choose to upload a large image. If you have any experience using this package, please let me know what I could do to fix this issue, thank you.
import "react-image-crop/dist/ReactCrop.css";
import React, { useState, useRef } from "react";
import ReactCrop from "react-image-crop";
export default function ImageUploader(props) {
const [imgSrc, setImgSrc] = useState();
const [crop, setCrop] = useState();
const [originalImg, setOrgImg] = useState(null);
const imgRef = useRef(null);
const handleImage = async (event) => {
setImgSrc(URL.createObjectURL(event.target.files[0]));
};
const getCroppedImg = async (image, pixelCrop) => {
try {
const canvas = document.createElement("canvas");
console.log(crop);
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
const ctx = canvas.getContext("2d");
// Here is what I think where the problem is at:
ctx.drawImage(
image,
pixelCrop.x,
pixelCrop.y,
pixelCrop.width,
pixelCrop.height,
0,
0,
pixelCrop.width,
pixelCrop.height
);
const base64Image = await canvas.toDataURL("image/jpeg", 1);
props.setCurrentImages(pushImage(props.images, base64Image));
console.log(base64Image);
console.log(props.images);
} catch (e) {
console.log(e);
}
};
function pushImage(array, newImage) {
if (array.lengh === 0) return [newImage];
return [...array, newImage];
}
function handleCropButton() {
getCroppedImg(imgRef.current, crop);
props.setUploadImg(false);
}
return (
<div style={{ height: "600px" }}>
<div>
<input type="file" onChange={handleImage} accept="image/*" />
<button onClick={handleCropButton}>Crop</button>
</div>
<ReactCrop
crop={crop}
aspect={1}
onChange={(c) => setCrop(c)}
onComplete={(crop) => setCrop(crop)}
>
<img
src={imgSrc}
alt=""
style={{ height: "600px" }}
onLoad={() => {
setOrgImg({
height: imgRef.current.clientHeight,
width: imgRef.current.clientWidth,
});
}}
ref={imgRef}
/>
</ReactCrop>
</div>
);
}
I'm beginner and I'm not sure if I solved the problem in a proper way.
I have a form with controlled components and I have to get values from these components in an async function to send them to a rest api backend. But If I'm trying to do it from an async function, I have got just empty values, because I don't have access to actual state of these component in async function (onSubmit).
I have tried to use a hook useEffect to solve this problem, it works, but I'm not sure if it a proper to do it?
const [comment1, setComment1] = useState('');
const [comment2, setComment2] = useState('');
const [comment3, setComment3] = useState('');
const [imageS3URL1, setImageS3URL1] = useState('');
const [imageS3URL2, setImageS3URL2] = useState('');
const [imageS3URL3, setImageS3URL3] = useState('');
const [submitData, setSubmitData] = useState(false);
const dataToSubmit = () => {
const data = {
comment1: comment1,
image1: imageS3URL1,
comment2: comment2,
image2: imageS3URL2,
comment3: comment3,
image3: imageS3URL3,
};
console.log('submitData', data);
};
useEffect(() => {
setSubmitData(false);
submitData && dataToSubmit();
}, [submitData]);
const onSubmit = async (e: any) => {
e.preventDefault();
Promise.all([
//#ts-ignore
refS3Uploader1.current.uploadFile(),
//#ts-ignore
refS3Uploader2.current.uploadFile(),
//#ts-ignore
refS3Uploader3.current.uploadFile(),
]).then(values => {
setSubmitData(true);
});
};
A second approach without useEffect, it works too.
...
const [submitData, setSubmitData] = useState(false);
const onSubmit = async (e: any) => {
e.preventDefault();
Promise.all([
//#ts-ignore
refS3Uploader1.current.uploadFile(),
//#ts-ignore
refS3Uploader2.current.uploadFile(),
//#ts-ignore
refS3Uploader3.current.uploadFile(),
]).then(values => {
setSubmitData(true);
});
};
const dataToSubmit = () => {
const data = {
comment1: comment1,
image1: imageS3URL1,
comment2: comment2,
image2: imageS3URL2,
comment3: comment3,
image3: imageS3URL3,
};
console.log('submitData', data);
};
if (submitData === true) {
setSubmitData(false);
dataToSubmit();
}
Maybe I completely wrong and there is another, more proper way to solve it?
================
full code
import * as React from 'react';
import { useState, useRef, useEffect } from 'react';
import { ImageS3Upload } from '../src';
const Example4 = () => {
const URL_GENERATE_S3_URLKEY = 'http://127.0.0.1:8000/api/citytree/s3/generate_signed_url/';
const SERVER_PHOTO = 'https://urbangis.s3.eu-central-1.amazonaws.com/';
const resizerOptions = {
enabled: true,
autoResize: true, // otherwise resizing will be preform before uploading
maxWidth: 1280,
maxHeight: 1280,
compressFormat: 'JPEG',
quality: 70,
rotation: 0, // rotation is limited to multiples of 90 degrees
};
const refS3Uploader1 = useRef(); // to be able to call method uploadFile
const refS3Uploader2 = useRef(); // to be able to call method uploadFile
const refS3Uploader3 = useRef(); // to be able to call method uploadFile
const [comment1, setComment1] = useState('');
const [comment2, setComment2] = useState('');
const [comment3, setComment3] = useState('');
const [imageS3URL1, setImageS3URL1] = useState('');
const [imageS3URL2, setImageS3URL2] = useState('');
const [imageS3URL3, setImageS3URL3] = useState('');
const [submitButtonDisabled, setSubmitButtonDisabled] = useState(false);
const [submitData, setSubmitData] = useState(false);
useEffect(() => {
setSubmitData(false);
submitData && dataToSubmit();
}, [submitData]);
const onSubmit = async (e: any) => {
e.preventDefault();
setSubmitButtonDisabled(true);
Promise.all([
//#ts-ignore
refS3Uploader1.current.uploadFile(),
//#ts-ignore
refS3Uploader2.current.uploadFile(),
//#ts-ignore
refS3Uploader3.current.uploadFile(),
]).then(values => {
setSubmitButtonDisabled(false);
let result = true;
values.forEach(value => {
if (!value) result = false;
});
result && setSubmitData(true);
//if I call dataToSubmit from here, I will get empty values
dataToSubmit();
});
};
const dataToSubmit = () => {
const data = {
comment1: comment1,
image1: imageS3URL1,
comment2: comment2,
image2: imageS3URL2,
comment3: comment3,
image3: imageS3URL3,
};
console.log('submitData', data);
};
// if (submitData === true) {
// setSubmitData(false);
// dataToSubmit();
// }
const onResizeStart1 = () => {
setSubmitButtonDisabled(true);
};
const onResizeStart2 = () => {
setSubmitButtonDisabled(true);
};
const onResizeStart3 = () => {
setSubmitButtonDisabled(true);
};
const onResizeFinish1 = () => {
setSubmitButtonDisabled(false);
};
const onResizeFinish2 = () => {
setSubmitButtonDisabled(false);
};
const onResizeFinish3 = () => {
setSubmitButtonDisabled(false);
};
return (
<>
<form
onSubmit={onSubmit}
style={{ margin: '10px', padding: '10px', border: 'solid 1px black' }}
>
<p><b>4. Form, multiple components, autoUpload=False</b></p>
<div style={{ display: 'flex' }}>
<div style={{ maxWidth: '200px', margin: '10px' }}>
<label htmlFor="id_comment1">Comment</label>
<input
type="text"
id="id_comment1"
value={comment1}
onChange={e => setComment1(e.target.value)}
/>
<br />
<br />
<ImageS3Upload
signingUrl={URL_GENERATE_S3_URLKEY}
autoUpload={false}
serverPhoto={SERVER_PHOTO}
resizer={resizerOptions}
onResizeStart={onResizeStart1}
onResizeFinish={onResizeFinish1}
ref={refS3Uploader1}
value={imageS3URL1}
onChange={(e: any) => setImageS3URL1(e.target.value)}
/>
</div>
<div style={{ maxWidth: '200px', margin: '10px' }}>
<label htmlFor="id_comment4">Comment</label>
<input
type="text"
id="id_comment4"
value={comment2}
onChange={e => setComment2(e.target.value)}
/>
<br />
<br />
<ImageS3Upload
signingUrl={URL_GENERATE_S3_URLKEY}
autoUpload={false}
serverPhoto={SERVER_PHOTO}
resizer={resizerOptions}
onResizeStart={onResizeStart2}
onResizeFinish={onResizeFinish2}
ref={refS3Uploader2}
value={imageS3URL2}
onChange={(e: any) => setImageS3URL2(e.target.value)}
/>
</div>
<div style={{ maxWidth: '200px', margin: '10px' }}>
<label htmlFor="id_comment5">Comment</label>
<input
type="text"
id="id_comment5"
value={comment3}
onChange={e => setComment3(e.target.value)}
/>
<br />
<br />
<ImageS3Upload
signingUrl={URL_GENERATE_S3_URLKEY}
autoUpload={false}
serverPhoto={SERVER_PHOTO}
resizer={resizerOptions}
onResizeStart={onResizeStart3}
onResizeFinish={onResizeFinish3}
ref={refS3Uploader3}
value={imageS3URL3}
onChange={(e: any) => setImageS3URL3(e.target.value)}
/>
</div>
</div>
<input
type="submit"
value="Submit"
style={{ width: '100%' }}
className="button"
disabled={submitButtonDisabled}
></input>
</form>
</>
);
}
export default Example4
import { useState } from 'react';
const AddNote = ({ handleAddNote }) => {
const [noteText, setNoteText] = useState('');
const [jobLink, setJobLink] = useState('');
const characterLimit = 200;
const handleDescChange = (event) => {
if (characterLimit - event.target.value.length >= 0) {
setNoteText(event.target.value);
}
};
const handleJobLinkChange = (event) => {
if (event.target.value.length >= 0) {
setJobLink(event.target.value);
console.log(event.target.value);
}
};
const handleSaveClick = () => {
if (noteText.trim().length > 0) {
handleAddNote(noteText);
setNoteText('');
setJobLink('');
}
};
return (
<div className='note new'>
<textarea
rows='8'
cols='10'
placeholder='Type to add a note...'
value={noteText}
onChange={handleDescChange}
></textarea>
<input style={{
paddingTop: "1.5%",
outline: "none",
color: "black",
marginRight: "0px",
marginLeft: "0px",
paddingRight: "0px",
backgroundColor: "white"
}}
className="form-control"
id="link"
name="link"
placeholder="Link to Job Posting"
value={jobLink}
type="link"
onChange={handleJobLinkChange}
/>
<div className='note-footer'>
<small>
{characterLimit - noteText.length} Remaining
</small>
<button className='save' onClick={handleSaveClick}>
Save
</button>
</div>
</div>
);
};
export default AddNote;
import { MdDeleteForever } from 'react-icons/md';
import AddNote from './AddNote';
const Note = ({ id, link, text, date, handleDeleteNote,}) => {
const go = e => {
link = "https://www.google.com";
e.preventDefault();
window.location.href = link;
}
return (
<div className='note'>
<span>{text}</span>
<div className='note-footer'>
<small>{date}</small>
<div className='note-btn'>
<button onClick={go} style={{ backgroundColor: "#001E49", borderRadius: "10px"
, borderColor: "none", margin: "auto", padding: "8px", marginLeft: "0px"
, marginRight: "0px", width: "fit-content"
, borderColor: "none"}}>Apply Here</button>
</div>
</div>
</div>
);
};
export default Note;
import Note from './Note';
import AddNote from './AddNote';
const NotesList = ({
notes,
handleAddNote,
handleDeleteNote,
}) => {
return (
<div className='notes-list'>
{notes.map((note) => (
<Note
id={note.id}
text={note.text}
date={note.date}
handleDeleteNote={handleDeleteNote}
/>
))}
<AddNote handleAddNote={handleAddNote} />
</div>
);
};
export default NotesList;
import React from 'react'
import './Internships.css'
import { useState, useEffect } from 'react';
import { nanoid } from 'nanoid';
import NotesList from '../components/NotesList';
import Search from '../components/Search';
import Header from '../components/Header';
const Internships = () => {
const [notes, setNotes] = useState([
]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
const savedNotes = JSON.parse(
localStorage.getItem('react-notes-app-data')
);
if (savedNotes) {
setNotes(savedNotes);
}
}, []);
useEffect(() => {
localStorage.setItem(
'react-notes-app-data',
JSON.stringify(notes)
);
}, [notes]);
const addNote = (text, link) => {
const date = new Date();
const newNote = {
id: nanoid(),
text: text,
date: date.toLocaleDateString(),
link: link,
};
const newNotes = [...notes, newNote];
setNotes(newNotes);
};
const deleteNote = (id) => {
const newNotes = notes.filter((note) => note.id !== id);
setNotes(newNotes);
};
return (
<div className={`${darkMode && 'dark-mode'}`}>
<div className='container'>
<Header />
<Search handleSearchNote={setSearchText} />
<NotesList
notes={notes.filter((note) =>
note.text.toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
/>
</div>
</div>
);
};
export default Internships
for the 'link= "www.google.com"'(it is a placeholder) the button redirects me to google.com but I want there to be a user inputted website and it redirects me there. I am not able to pass joblink into note.js and it is not reading it when I did inspect element on the button that was created. I don't know how to fix trying to get an input from the user and passing it into note.js.
I am a little bit confused by how the code was written out on the forum, but it seems like you could set a state for the target value in your input. So do something like
const[site, setSite] = useState("https://www.google.com");
<input style={{
paddingTop: "1.5%",
outline: "none",
color: "black",
marginRight: "0px",
marginLeft: "0px",
paddingRight: "0px",
backgroundColor: "white"
}}
className="form-control"
id="link"
name="link"
placeholder="Link to Job Posting"
value={jobLink}
type="link"
onChange={() => setSite(e.target.value)}
/>
And then use callback props and send it to your Note component, then replace your 'go' link to
link = site
I apologize if that made no sense lol.
From my understanding, you have the component AddNote where the user is able to generate a new job post, and these values saved here are needed in the Note component
Here not only the states are saved in the incorrect component, but when they hit save (handleSaveClick) this happens:
setNoteText('');
setJobLink('');
What I recommend is to have a Notes component, including a notes state array where you'll save a json with noteText and jobLink, and then map those to Note components, passing them as props.
And this Notes component can also contain the AddNote Component so this way you can pass the setNotes hook and efficiently work with dynamic job posts:
const [notes, setNotes] = useState([])
{notes?.length > 0 && notes.map(elem => (<Note noteText={elem.noteText} jobLink={elem.jobLink} />))}
(preferably pass the 'elem' as a whole, maybe you'll add new properties to it and the props quantity will grow)
and with the use of useRef hook:
const handleSaveClick = () => {
handleAddNote(prev => [...prev, {noteLink: noteLinkRef.current.value, jobLink: jobLinkRef.current.value}])
}
Hope this helps to understand, if you feel that I need to expand a little bit more, let me know!
I have simply a button 'Create screenshot' and live stream 'rtsp-relay'. When that button is cliked it should take a snapshot or screenshot and show that under image, my problem is it shows blank image, i have tried to do this in two ways and both of them show blank image (live stream works fine).
code works fine with a simple drawing on a canvas:
https://codesandbox.io/s/copy-canvas-c5l8et-c5l8et?file=/src/App.js
but when there is live video playing, it shows blank image when 'Create screenshot' button is clicked.
Any idea why ?
two ways i have tried:
1:
import React, { useRef, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { loadPlayer } from 'rtsp-relay/browser';
const StreamVideo = () => {
const canvasRef = useRef(null);
const createScreenshot = () =>
new Promise((resolve) => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.drawImage(canvasRef.current, 0, 0);
canvas.toBlob((blob) => {
const src = URL.createObjectURL(blob);
console.log('src', src);
const image = new Image();
image.onload = () => resolve(image);
image.src = src;
});
});
const handleButtonClick = () => {
createScreenshot().then((image) => {
document.body.append(image);
});
};
useEffect(() => {
if (!canvasRef.current) throw new Error('Ref is null');
loadPlayer({
url: 'ws://localho.../api/stream',
canvas: canvasRef.current,
});
}, []);
return (
<div style={{ border: '5px solid red' }}>
<canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />
<button onClick={handleButtonClick}>Create screenshot</button>
</div>
);
};
export default StreamVideo;
2:
import React, { useRef, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { loadPlayer } from 'rtsp-relay/browser';
const StreamVideo = () => {
const canvasRef = useRef(null);
const createScreenshot = () =>
new Promise((resolve) => {
canvasRef.current.toBlob((blob) => {
const src = URL.createObjectURL(blob);
const image = new Image();
image.onload = () => resolve(image);
image.src = src;
});
});
const handleButtonClick = () => {
createScreenshot().then((image) => {
document.body.append(image);
});
};
useEffect(() => {
if (!canvasRef.current) throw new Error('Ref is null');
loadPlayer({
url: 'ws://localh.../api/stream',
canvas: canvasRef.current,
});
}, []);
return (
<div style={{ border: '5px solid red' }}>
<canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />
<button onClick={handleButtonClick}>Create screenshot</button>
</div>
);
};
export default StreamVideo;
image:
As shown below, this TextInput component does a simple job: when input's value is empty, hides the title because placeholder shows the same words.
But the code doesn't work as expected. InputEvent does run, but reassign activeStyle has no effect.
import React, {useState} from 'react';
import './TextInput.css';
import * as CSS from 'csstype';
type TextInputProps = {
title: string
}
const TextInput: React.FC<TextInputProps> = ({title, children}) => {
const hiddenStyle: CSS.Properties = {
opacity: 0
};
const visibleStyle: CSS.Properties = {
opacity: 1
};
let activeStyle = hiddenStyle
const [rawTextInput, setRawTextInput] = useState("")
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
activeStyle = hiddenStyle
} else {
activeStyle = visibleStyle
}
}
return (
<div className="TextInput">
<p
className="TextInputTitle"
style={activeStyle}
>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
}
export default TextInput
import React, {useState} from 'react';
import './TextInput.css';
import * as CSS from 'csstype';
type TextInputProps = {
title: string
}
const TextInput: React.FC<TextInputProps> = ({title, children}) => {
const hiddenStyle: CSS.Properties = {
opacity: 0
};
const visibleStyle: CSS.Properties = {
opacity: 1
};
let activeStyle = hiddenStyle
const [rawTextInput, setRawTextInput] = useState("")
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
activeStyle = hiddenStyle
} else {
activeStyle = visibleStyle
}
}
return (
<div className="TextInput">
<p
className="TextInputTitle"
style={activeStyle}
>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
}
export default TextInput
Local variable doesn't affect re-render.
let activeStyle = hiddenStyle //local variable
You need to keep this in state and change using setter method.
const [activeStyle, setActiveStyle] = useState(hiddenStyle)
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
setActiveStyle(hiddenStyle)
} else {
setActiveStyle(visibleStyle)
}
}
I've tried a simpler approach
import React, { useState } from "react";
type TextInputProps = {
title: string;
};
const TextInput: React.FC<TextInputProps> = ({ title, children }) => {
const [rawTextInput, setRawTextInput] = useState("");
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
setRawTextInput(e.target.value)
};
return (
<div className="TextInput">
<p className="TextInputTitle" style={{opacity : rawTextInput == "" ? 0 : 1}}>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
};
export default TextInput;
I hope it'll be useful
Changing the value of a local variable does not cause a re-render. And even if something else causes a render, that new render won't have access to local variables from the previous render. You need to use useState for the active style, not a local variable.
just use useState for activeStyle instead of declaring it on the scope of the component,this could cause unsync effect which could be a problem and indeterministic
import React, {useState} from 'react';
import './TextInput.css';
import * as CSS from 'csstype';
type TextInputProps = {
title: string
}
const TextInput: React.FC<TextInputProps> = ({title, children}) => {
const hiddenStyle: CSS.Properties = {
opacity: 0
};
const visibleStyle: CSS.Properties = {
opacity: 1
};
const [rawTextInput, setRawTextInput] = useState("")
//------Put something you want to change dynamically inside useState()
const [titleStyle, setTitleStyle] = useState(hiddenStyle)
//------
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
setTitleStyle(hiddenStyle)
} else {
setTitleStyle(visibleStyle)
}
}
return (
<div className="TextInput">
<p
className="TextInputTitle"
style={titleStyle}
>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
}
export default TextInput