React-webcam enable/disable flash - reactjs

I'm using react JS to create a camera application
I want to be able to save a photo snapshot, change the facingMode, to be able to use the font and back camera of the phone. Also, I would like to enable and disable the flash when the facingMode is set to the environment.
I used react-webcam from npm to start a video stream and create a snapshot.
At this point, I can flip the camera (to use the front and back camera), but can I enable/disable the flash when the back camera is active?
In here https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints i found a constant property called torch that is used for the mobile flash, but i don't know how to dynamycally set the vale to true/false
import React from 'react';
import Webcam from 'react-webcam';
import { useState, useRef } from 'react';
const WebcamComponent = () => <Webcam />;
const WebcamCapture = () => {
const webcamRef = React.useRef(null);
const [hasEnvCamera, setEvnCamera] = useState(false);
const [image, setImage] = useState('');
const [flash, setFlash] = useState(false);
const capture = React.useCallback(() => {
const imageSrc = webcamRef.current.getScreenshot();
setImage(imageSrc);
}, [webcamRef]);
const videoConstraints = {
width: 220,
height: 200,
facingMode: 'user',
};
const videoFlipConstraints = {
width: 220,
height: 200,
facingMode: { exact: 'environment' },
};
const changeCamera = (e) => {
e.preventDefault();
if (hasEnvCamera) {
console.log(' back camera');
setEvnCamera(false);
} else {
console.log(' front camera');
setEvnCamera(true);
}
};
const changeFlash = (e) => {
e.preventDefault();
console.log('change flash');
if (flash) {
setFlash(false);
console.log('flash off');
} else {
setFlash(true);
console.log('flash on');
}
};
return (
<div>
{hasEnvCamera ? (
<div>
<p> camera flip</p>
<Webcam
audio={false}
height={200}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={220}
videoConstraints={videoFlipConstraints}
/>
<button onClick={changeFlash}>
{' '}
{flash ? 'Disable flesh' : 'Enable flash'}{' '}
</button>
</div>
) : (
<div>
<p>selfie camera</p>
<Webcam
audio={false}
height={200}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={220}
videoConstraints={videoConstraints}
/>
</div>
)}
<button
onClick={(e) => {
e.preventDefault();
capture();
}}
>
Photo
</button>
<button
onClick={(e) => {
e.preventDefault();
const imageSrc = null;
setImage(imageSrc);
}}
>
Take another photo{' '}
</button>
<button onClick={changeCamera}> Change camera </button>
<img src={image} />
</div>
);
};
export default WebcamCapture;

Related

How to save an image to a custom directory using React Webcam

I integrated a webcam capture file in my application while working in ReactJS. I'd like to know if there's any way that the captured image gets saved in a particular code directory when I click the button. The code looks something like this :
import React, { useState } from 'react';
import Webcam from "react-webcam";
const videoConstraints = {
width: 220,
height: 200,
facingMode: "user"
};
const WebcamCapture = () => {
const [image,setImage]=useState('');
const webcamRef = React.useRef(null);
const capture = React.useCallback(
() => {
const imageSrc = webcamRef.current.getScreenshot();
setImage(imageSrc)
});
return (
<div className="webcam-container">
<div className="webcam-img">
{image == '' ? <Webcam
audio={false}
height={200}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={220}
videoConstraints={videoConstraints}
/> : <img src={image} />}
</div>
<div>
{image != '' ?
<button onClick={(e) => {
e.preventDefault();
setImage('')
}}
className="webcam-btn">
Retake Image</button> :
<button onClick={(e) => {
e.preventDefault();
capture();
}}
className="webcam-btn">Capture</button>
}
</div>
</div>
);
};
export default WebcamCapture;

How to use vanilla JS with React component (zoom to bounds on load react mapbox gl)

I am looking to be able to zoom to bounds when my component mounts.
I know this vanilla JS code can achieve the zoom to bounds, but not sure how to massage this into the React code below (I think a need a REF?):
var bounds = new mapboxgl.LngLatBounds();
parkDate.features.forEach(function(feature) {
bounds.extend(feature.geometry.coordinates);
});
map.fitBounds(bounds, {
padding: {top: 20, bottom:20, left: 20, right: 20}
})
Component:
import React, { useState } from "react";
import ReactMapGL, { Marker } from "react-map-gl";
import * as parkDate from "./data.json";
const mapIcon: any = require('../images/mapIcon.png');
export default function App() {
const [viewport, setViewport] = useState({
latitude: 45.4211,
longitude: -75.6903,
width: "100%",
height: "400px",
zoom: 10
});
const [selectedPark, setSelectedPark] = useState(null);
return (
<div>
<ReactMapGL
{...viewport}
mapboxApiAccessToken="pk.eyJ1IjoiYmVubmtpbmd5IiwiYSI6ImNrY2ozMnJ5dzBrZ28ycnA1b2Vqb2I0bXgifQ.ZOaVtzsDQOrAovh9Orh13Q"
mapStyle="mapbox://styles/mapbox/streets-v11"
onViewportChange={viewport => {
setViewport(viewport);
}}
>
{parkDate.features.map(park => (
<Marker
key={park.properties.PARK_ID}
latitude={park.geometry.coordinates[1]}
longitude={park.geometry.coordinates[0]}
>
<button
className="marker-btn"
onClick={e => {
e.preventDefault();
setSelectedPark(park);
}}
>
<img src={mapIcon} alt="Map Pointer Icon" />
</button>
</Marker>
))}
</ReactMapGL>
{selectedPark ? (
<div>
<h2>{selectedPark.properties.NAME}</h2>
<p>{selectedPark.properties.DESCRIPTIO}</p>
<button onClick={e => {
e.preventDefault();
setSelectedPark(null);
}}>X</button>
</div>
) : null}
</div>
);
}
Got it working like this:
import React, { useState } from "react";
import ReactMapGL, { Marker, WebMercatorViewport } from "react-map-gl";
import * as parkData from "./data.json";
const mapIcon: any = require('../images/mapIcon.png');
const applyToArray = (func, array) => func.apply(Math, array)
const getBoundsForPoints = (points) => {
console.log('Points:', points)
// Calculate corner values of bounds
const pointsLong = points.map(point => point.geometry.coordinates[0])
const pointsLat = points.map(point => point.geometry.coordinates[1])
const cornersLongLat = [
[applyToArray(Math.min, pointsLong), applyToArray(Math.min, pointsLat)],
[applyToArray(Math.max, pointsLong), applyToArray(Math.max, pointsLat)]
]
// Use WebMercatorViewport to get center longitude/latitude and zoom
const viewport = new WebMercatorViewport({ width: 600, height: 600 })
// #ts-ignore
.fitBounds(cornersLongLat, { padding: {top:150, bottom:200, left:100, right:150} })
const { longitude, latitude, zoom } = viewport
return { longitude, latitude, zoom }
}
const myMap = () => {
const bounds = getBoundsForPoints(parkData.features);
const [viewport, setViewport] = useState({
width: "100%",
height: "600px",
...bounds
});
const [selectedPark, setSelectedPark] = useState(null);
return (
<div>
<ReactMapGL
{...viewport}
mapboxApiAccessToken="pk.eyJ1IjoiYmVubmtpbmd5IiwiYSI6ImNrY2ozMnJ5dzBrZ28ycnA1b2Vqb2I0bXgifQ.ZOaVtzsDQOrAovh9Orh13Q"
mapStyle="mapbox://styles/mapbox/streets-v11"
onViewportChange={viewport => {
setViewport(viewport);
}}
>
{parkData.features.map(park => (
<Marker
key={park.properties.PARK_ID}
latitude={park.geometry.coordinates[1]}
longitude={park.geometry.coordinates[0]}
>
<button
className="marker-btn"
onClick={e => {
e.preventDefault();
setSelectedPark(park);
}}
>
<img src={mapIcon} alt="Map Pointer Icon" />
</button>
</Marker>
))}
</ReactMapGL>
{selectedPark ? (
<div>
<h2>{selectedPark.properties.NAME}</h2>
<p>{selectedPark.properties.DESCRIPTIO}</p>
<button onClick={e => {
e.preventDefault();
setSelectedPark(null);
}}>X</button>
</div>
) : null}
</div>
);
}
export default myMap;

Why the wrong element is being updated only when uploading files?

I have built a component CreatePost which is used for creating or editing posts,
the problem is if I render this component twice even if I upload a file from the second component they are changed in the first one, why? Here is the code:
import FileUpload from "#components/form/FileUpload";
import { Attachment, Camera, Video, Writing } from "public/static/icons";
import styles from "#styles/components/Post/CreatePost.module.scss";
import { useSelector } from "react-redux";
import { useInput, useToggle } from "hooks";
import { useRef, useState } from "react";
import StyledButton from "#components/buttons/StyledButton";
import Modal from "#components/Modal";
import { post as postType } from "types/Post";
import Removeable from "#components/Removeable";
interface createPostProps {
submitHandler: (...args) => void;
post?: postType;
isEdit?: boolean;
}
const CreatePost: React.FC<createPostProps> = ({ submitHandler, post = null, isEdit = false }) => {
console.log(post);
const maxFiles = 10;
const [showModal, setShowModal, ref] = useToggle();
const [description, setDescription] = useInput(post?.description || "");
const user = useSelector((state) => state.user);
const [files, setFiles] = useState<any[]>(post?.files || []);
const handleFileUpload = (e) => {
const fileList = Array.from(e.target.files);
if (fileList.length > maxFiles || files.length + fileList.length > maxFiles) {
setShowModal(true);
} else {
const clonedFiles = [...files, ...fileList];
setFiles(clonedFiles);
}
e.target.value = "";
};
const removeHandler = (id) => {
const filtered = files.filter((file) => file.name !== id);
setFiles(filtered);
};
return (
<div className={styles.createPost}>
<div className={styles.top}>
<span>
<img src="/static/images/person1.jpg" />
</span>
<textarea
onChange={setDescription}
className="primaryScrollbar"
aria-multiline={true}
value={description}
placeholder={`What's on your mind ${user?.name?.split(" ")[0]}`}
></textarea>
{description || files.length ? (
<StyledButton
background="bgPrimary"
size="md"
className={styles.submitButton}
onClick={() => {
if (!isEdit)
submitHandler({
files: files,
author: { name: user.name, username: user.username },
postedTime: 52345,
id: Math.random() * Math.random() * 123456789101112,
comments: [],
likes: [],
description,
});
else {
submitHandler({
...post,
description,
files,
});
}
setDescription("");
setFiles([]);
}}
>
{isEdit ? "Edit" : "Post"}
</StyledButton>
) : null}
</div>
<div className={styles.middle}>
<div className={styles.row}>
{files.map((file) => {
return (
<Removeable
key={file.name + Math.random() * 100000}
removeHandler={() => {
removeHandler(file.name);
}}
>
{file.type.includes("image") ? (
<img src={URL.createObjectURL(file)} width={150} height={150} />
) : (
<video>
<source src={URL.createObjectURL(file)} type={file.type} />
</video>
)}
</Removeable>
);
})}
</div>
</div>
<div className={styles.bottom}>
<FileUpload
id="uploadPhoto"
label="upload photo"
icon={
<span>
<Camera /> Photo
</span>
}
className={styles.fileUpload}
multiple
onChange={handleFileUpload}
accept="image/*"
/>
<FileUpload
id="uploadVideo"
label="upload video"
icon={
<span>
<Video /> Video
</span>
}
className={styles.fileUpload}
multiple
onChange={handleFileUpload}
accept="video/*"
/>
<FileUpload
id="writeArticle"
label="write article"
icon={
<span>
<Writing /> Article
</span>
}
className={styles.fileUpload}
multiple
onChange={handleFileUpload}
/>
</div>
{showModal && (
<Modal size="sm" backdrop="transparent" ref={ref} closeModal={setShowModal.bind(null, false)} yPosition="top">
<p>Please choose a maximum of {maxFiles} files</p>
<StyledButton size="md" background="bgPrimary" onClick={setShowModal.bind(null, false)}>
Ok
</StyledButton>
</Modal>
)}
</div>
);
};
export default CreatePost;
Now on my main file I have:
const Main = () => {
const [posts, setPosts] = useState<postType[]>([]);
const addPost = (post: postType) => {
setPosts([post, ...posts]);
};
const editPost = (post: postType) => {
const updated = posts.map((p) => {
if (post.id === post.id) {
p = post;
}
return p;
});
setPosts(updated);
};
const deletePost = (id) => {
const filtered = posts.filter((post) => post.id !== id);
setPosts(filtered);
};
return (
<>
<CreatePost submitHandler={addPost} key="0" />
<CreatePost submitHandler={addPost} key="1"/>
{posts.map((post) => {
return <PostItem {...post} editHandler={editPost} key={post.id} deleteHandler={deletePost.bind(null, post.id)} />;
})}
</>
);
};
export default Main;
I tried to add/remove the key but doesn't change anything, also tried to recreate this problem in a simpler way in sandbox but I can't it works fine there. And the problem is only when I upload files not when I write text inside the <textarea/>
Note: The second in reality is shown dynamically inside a modal when clicked edit in a post, but I just showed it here for simplicity because the same problem occurs in both cases.
Okay after some hours of debugging I finally found the problem.
Because my <FileUpload/> uses id to target the input inside the <CreatePost/> the <FileUpload/> always had same it, so when I used <CreatePost/> more than 1 time it would target the first element that found with that id that's why the first component was being updated

Clicking Image to link to another website React.JS

I am currently working on a app similar to MyAnimeList and I'm using React. I want to click the image of the anime and have it redirect to MyAnimeList for more information. The API i am using is called Jikan APi(https://jikan.moe/). I have seen many ways to do it however most have not worked for my app? I am new to React so im still getting the hang of it.
I am currently using react-bootstrap to for the card.
import React, { useState, useEffect } from 'react';
import { Card, CardColumns, Button } from 'react-bootstrap';
import Nav from '../components/Nav';
import Auth from '../utils/auth';
import { Link } from 'react-router-dom';
import { searchJikanApi } from '../utils/API';
import { saveAnimeIds, getSavedAnimeIds } from '../utils/localStorage';
import video from '../imgs/video.mp4';
import { SAVE_ANIME } from '../utils/mutations';
import { useMutation } from '#apollo/react-hooks';
import Carousel from 'react-elastic-carousel';
import Item from '../components/Carousel/item';
import PopularAnime from '../components/PopularAnime';
import Footer from '../components/Footer';
function Home() {
const [searchedAnimes, setSearchedAnimes] = useState([]);
// create state for holding our search field data
const [searchInput, setSearchInput] = useState('');
// create state to hold saved animeId values
const [savedAnimeIds, setSavedAnimeIds] = useState(getSavedAnimeIds());
const [saveAnime] = useMutation(SAVE_ANIME);
useEffect(() => {
return () => saveAnimeIds(savedAnimeIds);
});
const breakPoints = [
{ width: 1, itemsToShow: 1 },
{ width: 550, itemsToShow: 2, itemsToScroll: 2 },
{ width: 768, itemsToShow: 3 },
{ width: 1200, itemsToShow: 4 },
];
const handleFormSubmit = async (event) => {
event.preventDefault();
if (!searchInput) {
return false;
}
try {
const response = await searchJikanApi(searchInput);
if (!response.ok) {
throw new Error('something went wrong!');
}
const { results } = await response.json();
const animeData = results.map((anime) => ({
animeId: anime.mal_id,
rating: anime.rated || ['No rating to display'],
title: anime.title,
score: anime.score,
description: anime.synopsis,
image: anime.image_url || '',
link: anime.url,
}));
setSearchedAnimes(animeData);
setSearchInput('');
} catch (err) {
console.error(err);
}
};
// function to handle saving an anime to our database
const handleSaveAnime = async (animeId) => {
// find the book in `searchedAnime` state by the matching id
const animeToSave = searchedAnimes.find(
(anime) => anime.animeId === animeId
);
// get token
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
await saveAnime({
variables: { ...animeToSave },
});
// if book successfully saves to user's account, save book id to state
setSavedAnimeIds([...savedAnimeIds, animeToSave.animeId]);
} catch (err) {
console.error(err);
}
};
return (
<div className='container'>
<section className='header'>
<Nav></Nav>
<video className='bg-video' autoPlay muted loop>
<source src={video} type='video/mp4' />
Your browser is not supported!
</video>
<div className='heading-primary'>
<form className='search-bar' onSubmit={handleFormSubmit}>
<input
className='heading-search-bar'
name='searchInput'
type='text'
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
<button className='heading-search-btn' type='submit'>
Search
</button>
</form>
</div>
</section>
<div>
<h2>
{searchedAnimes.length
? `Viewing ${searchedAnimes.length} results:`
: 'Search Anime!'}
</h2>
<Link to=''>
<CardColumns className='search-container carousel'>
<Carousel breakPoints={breakPoints}>
{searchedAnimes.map((anime) => {
return (
<Item>
<Card key={anime.animeId} className='anime-card'>
{anime.image ? (
<Card.Img
src={anime.image}
alt={`The cover for ${anime.title}`}
variant='top'
/>
) : null}
<Card.Body>
<Card.Title>{anime.title}</Card.Title>
<p className='small'>Rating: {anime.rating}</p>
<p className='small'>Score: {anime.score}/10</p>
<Card.Text>{anime.description}</Card.Text>
{Auth.loggedIn() && (
<Button
disabled={savedAnimeIds?.some(
(savedAnimeId) => savedAnimeId === anime.animeId
)}
className='btn-block btn-info'
onClick={() => handleSaveAnime(anime.animeId)}
>
{savedAnimeIds?.some(
(savedAnimeId) => savedAnimeId === anime.animeId
)
? 'This anime has already been saved!'
: 'Save this anime!'}
</Button>
)}
</Card.Body>
</Card>
</Item>
);
})}
</Carousel>
</CardColumns>
</Link>
<PopularAnime />
</div>
<Footer />
</div>
);
}
export default Home;
Thank You For Any Help!
try to add onPress to your image to go to our link
<TouchableHighlight
onPress={() => Linking.openURL('https://website.com')}>
<Image
source={{uri: image source}}
/>
</TouchableHighlight>

Reactjs app doesn't show image preview in safari

I am trying to make an app where a user can upload an image and send it off to an email, it's working fine on all browsers except Safari. For both mobile and web browsers, when I choose an image to upload nothing seems to be previewed nor is it even loaded (ready to be sent). Is there anything I can do to fix this? My code as it stands is really simple:
const EnterDetailPage = props => {
const [imageUrl, setImageUrl] = useState("");
const [imageFile, setImageFile] = useState();
const [upload, setUpload] = useState(null);
const handleUploadChange = async e => {
setLoading(true);
const file = e.target.files[0];
if (!file) {
return;
}
setUpload(URL.createObjectURL(file));
setImageFile(file);
const ref = firebase
.storage()
.ref()
.child(uuid.v4());
const snapshot = await ref.put(file);
let getImageUrl = await snapshot.ref.getDownloadURL();
setImageUrl(getImageUrl);
setLoading(false);
console.log(getImageUrl);
};
let imgPreview = null;
if (upload) {
imgPreview = (
<Avatar
variant="square"
src={upload}
alt="Avatar"
className={classes.bigAvatar}
/>
);
}
return(
<div className="m-auto p-16 sm:px-24 sm:mx-auto max-w-xl">
<input
accept="image/jpeg,image/gif,image/png"
className="hidden"
id="button-file"
type="file"
// onChange={handleUploadChange}
onInput={handleUploadChange}
onClick={event => {
event.target.value = null;
}}
/>
<label
htmlFor="button-file"
className={`${classes.bigAvatar} mt-8 bg-gray-300 m-auto flex items-center justify-center relative w-128 h-128 rounded-4 a-mr-16 a-mb-16 overflow-hidden cursor-pointer shadow-1 hover:shadow-xl`}
>
<div className="absolute flex items-center justify-center w-full h-full z-50">
{imageUrl ? null :
<Icon fontSize="large" color="primary" className="cloud-icon">
cloud_upload
</Icon>}
</div>
{imgPreview}
</label>
);
}:
I compared my code to this article here: https://w3path.com/react-image-upload-or-file-upload-with-preview/
and it seems like I've done exactly the same thing...how come I'm not getting the same results?
There's quite a bit going on your codesandbox example, but by stripping it down its bare bones, I was able to track down the issue...
Safari doesn't seem to support input elements that try to use the onInput event listener -- the callback is never executed. Instead, you can use the onChange event listener.
For the example below, I faked an API call by setting a Promise with a timeout, but this not needed and is only for demonstration purposes. In addition, I like using objects over multiple individual states, especially when the state needs to be synchronous -- it also is cleaner, easier to read, and functions more like a class based component.
Demo: https://jd13t.csb.app/
Source:
components/DetailPage.js
import React, { useRef, useState } from "react";
import { CircularProgress, Icon, Fab } from "#material-ui/core";
const initialState = {
isLoading: false,
imageName: "",
imagePreview: null,
imageSize: 0
};
const EnterDetailPage = () => {
const [state, setState] = useState(initialState);
const uploadInputEl = useRef(null);
const handleUploadChange = async ({ target: { files } }) => {
setState(prevState => ({ ...prevState, isLoading: true }));
const file = files[0];
await new Promise(res => {
setTimeout(() => {
res(
setState(prevState => ({
...prevState,
imageName: file.name,
imagePreview: URL.createObjectURL(file),
imageSize: file.size,
isLoading: false
}))
);
}, 2000);
});
};
const resetUpload = () => {
setState(initialState);
uploadInputEl.current.value = null;
};
const uploadImage = async () => {
if (state.imagePreview)
setState(prevState => ({ ...prevState, isLoading: true }));
await new Promise(res => {
setTimeout(() => {
res(alert(JSON.stringify(state, null, 4)));
resetUpload();
}, 2000);
});
};
const { imagePreview, imageName, imageSize, isLoading } = state;
return (
<div style={{ padding: 20 }}>
<div style={{ textAlign: "center" }}>
<div>
<input
accept="image/jpeg,image/gif,image/png"
className="hidden"
id="button-file"
type="file"
ref={uploadInputEl}
onChange={handleUploadChange}
/>
<label htmlFor="button-file">
<div>
{imagePreview ? (
<>
<img
src={imagePreview}
alt="Avatar"
style={{ margin: "0 auto", maxHeight: 150 }}
/>
<p style={{ margin: "10px 0" }}>
({imageName} - {(imageSize / 1024000).toFixed(2)}MB)
</p>
</>
) : (
<Icon fontSize="large" color="primary" className="cloud-icon">
cloud_upload
</Icon>
)}
</div>
</label>
<Fab
variant="extended"
size="large"
color="primary"
aria-label="add"
className=""
type="button"
onClick={uploadImage}
>
{isLoading ? (
<CircularProgress style={{ color: "white" }} />
) : (
"Submit"
)}
</Fab>
{imagePreview && (
<Fab
variant="extended"
size="large"
color="default"
aria-label="add"
className=""
type="button"
onClick={resetUpload}
>
Cancel
</Fab>
)}
</div>
</div>
</div>
);
};
export default EnterDetailPage;

Resources