I'm making a puzzle game with React. I am trying to use the "draggable" function, but there is a phenomenon that it does not click at once and requires several times to work.
It doesn't seem to be a mouse problem.
Below is the code. I don't know which one is the problem.
board.tsx
const Board = () => {
const {blocks, handleSwap} = useBlocks();
const [hydrated, setHydrated] = useState(false);
const [grabItem, setGrabItem] = useState("");
useEffect(() => {
setHydrated(true);
}, []);
return (
<>
{hydrated && (
<StyledBoard>
{blocks.map((row) =>
row.map((block) => (
<Block
key={uuid()}
id={block.index}
color={block.color}
handleSwap={handleSwap}
grabItem={grabItem}
setGrabItem={setGrabItem}
/>
))
)}
</StyledBoard>
)}
</>
);
};
export default Board;
subcomponent
block.tsx
import styled from "styled-components";
const Block = ({id, color, handleSwap, grabItem, setGrabItem}: IBlockProps) => {
const [isDragging, setIsDragging] = useState(false);
const [isDragOver, setIsDragOver] = useState(false);
const handleDragStart = (e: React.DragEvent<HTMLElement>) => {
setIsDragging(true);
setGrabItem(e.currentTarget.id);
};
const handleDragOver = (e: React.DragEvent<HTMLElement>) => {
e.preventDefault();
setIsDragOver(true);
};
const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
e.preventDefault();
setIsDragOver(false);
};
const handleDrop = (e: React.DragEvent<HTMLElement>) => {
e.preventDefault();
setIsDragging(false);
setIsDragOver(false);
handleSwap(grabItem, e.currentTarget.id);
};
return (
<StyledBlock
onDragStart={handleDragStart}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
id={id}
isDragOver={isDragOver}
color={color}
/>
);
};
export default Block;
Related
import { useState } from 'react'
import { AiOutlinePlus } from 'react-icons/ai'
import { TimerForm } from './TimerForm'
export const ToggleAddTimer = () => {
const [formState, setFormState] = useState(false);
const handleFormOpen = () => {
setFormState(true);
}
const handleFormClose = () => {
setFormState(false);
}
if (formState) {
return (
<TimerForm formClose={handleFormClose()} />
)
}
else {
return (
<button onClick={() => {handleFormOpen()}}> this function isn't getting invoked
<AiOutlinePlus/>
</button>
)
}
}
Try <TimerForm formClose={handleFormClose} />
and in your else
<button onClick={() => handleFormOpen()}>
Do this:
const [formState, setFormState] = useState(false)
const toggleFormState = () => {
setFormState(state => !state);
}
<button onClick={toggleFormState}>
<AiOutlinePlus/>
</button>
Test:
function App() {
const [formState, setFormState] = useState(false)
const toggleFormState = () => {
setFormState(state => !state);
}
return (
<button onClick={toggleFormState}>
Form State is {formState ? "true": "false"}
</button>
)
}
In my project, I have an email field to implement using the chip component. But I am facing a problem here, first time, when I paste multiple email values it gets inserted into the field, but second time when I copy some other values and paste them into the field, it replaces the previous values.
In first time:
Secnod time when I paste "abc4#abc.com" :
previous values replace with the current value.
import Chip from "#material-ui/core/Chip";
import TextField from "#material-ui/core/TextField";
import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export const TagActions = () => {
const [items, setItem] = useState<string[]>([]);
const [value, setValue] = useState("");
const [error, setError] = useState("");
const divRef = useRef<HTMLDivElement>(null);
const [flag, setFlag] = useState(false);
const handleDelete = (item: any) => {
console.log("handleDelete", item);
const result = items.filter((i) => i !== item);
setItem(result);
};
const handleItemEdit = (item: any) => {
console.log("handleItemEdit", item);
const result = items.filter((i) => i !== item);
setItem(result);
setValue(item);
console.log("value", value);
};
const handleKeyDown = (evt: any) => {
if (["Enter", "Tab", ","].includes(evt.key)) {
evt.preventDefault();
var test = value.trim();
if (test && isValid(test)) {
items.push(test);
setValue("");
}
}
};
const isValid = (email: any) => {
let error = null;
if (isInList(email)) {
error = `${email} has already been added.`;
}
if (!isEmail(email)) {
setFlag(true);
// error = `${email} is not a valid email address.`;
}
if (error) {
setError(error);
return false;
}
return true;
};
const isInList = (email: any) => {
return items.includes(email);
};
const isEmail = (email: any) => {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
};
const handleChange = (evt: any) => {
setValue(evt.target.value);
// setError("")
};
const handlePaste = (evt: any) => {
evt.preventDefault();
var paste = evt.clipboardData.getData("text");
console.log("pppp", paste);
var emails = paste.match(/[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/g);
if (emails) {
console.log("inside if", emails);
var toBeAdded = emails.filter((email: any) => !isInList(email));
setItem(toBeAdded);
}
};
return (
<>
<div>
<TextField
id="outlined-basic"
variant="outlined"
InputProps={{
startAdornment: items.map((item) => (
<Chip
className={!isEmail(item) ? "chip-tag-error" : "chip-tag"}
key={item}
tabIndex={-1}
label={item}
onDelete={() => handleDelete(item)}
onClick={() => handleItemEdit(item)}
/>
)),
}}
ref={divRef}
value={value}
placeholder="Type or paste email addresses and press `Enter`..."
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => handleChange(e)}
onPaste={(e) => handlePaste(e)}
/>
</div>
{error && <p className="error">{error}</p>}
</>
);
};
I am a beginner in react typescript. Please give me a solution to solve this situation.
Append to the list instead of overwriting it like
setItem(i => [...i, ...toBeAdded]);
I'm trying to make a emotion detection web app using React and face-api.js
I have a switch that turns on the webcam, loads in the face-api.js models and starts detecting the face and emotions. My problem is whenever I 'turn off' the switch or leave the Camera.jsx component, I get spammed in the console:
This is the code to the Camera.jsx component:
import React, { useEffect, useState, useRef } from 'react'
import * as face from 'face-api.js';
import Switch from '#mui/material/Switch';
import './Camera.css';
import Webcam from 'react-webcam';
const Camera = () => {
const camHeight = 720;
const camWidth = 1280;
const videoRef = useRef(null);
const canvasRef = useRef(null);
const displaySize = {
width: camWidth,
height: camHeight
}
const [checked, setChecked] = useState(false);
const handleChange = (event) => {
setChecked(event.target.checked);
};
useEffect(() => {
if (checked) {
const MODEL_URL = `/models`
const initModels = async () => {
Promise.all([
face.loadTinyFaceDetectorModel(MODEL_URL),
face.loadFaceLandmarkModel(MODEL_URL),
face.loadFaceRecognitionModel(MODEL_URL),
face.loadFaceExpressionModel(MODEL_URL)
]);
}
initModels();
}
}, [checked]);
const faceAnalysis = () => {
face.matchDimensions(canvasRef.current, displaySize);
setInterval(async () => {
const detections = await face.detectAllFaces(videoRef.current.video, new face.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions();
const resizedDetections = face.resizeResults(detections, displaySize);
canvasRef.current.getContext('2d').clearRect(0, 0, camWidth, camHeight);
face.draw.drawDetections(canvasRef.current, resizedDetections);
face.draw.drawFaceLandmarks(canvasRef.current, resizedDetections);
face.draw.drawFaceExpressions(canvasRef.current, resizedDetections);
}, 50);
}
return (
<div className="analysis">
<Switch
checked={checked}
onChange={handleChange}
inputProps={{ 'aria-label': 'controlled' }}
/>
{ checked ?
<div className="camera">
<Webcam
ref={videoRef}
videoConstraints={displaySize}
onUserMedia={faceAnalysis}
/>
<canvas ref={canvasRef} />
</div>
: null}
</div>
)
}
export default Camera;
Would this be a problem with the way I'm loading in the models for the emotion detection?
I found a solution by creating a function in the Camera.jsx component and surrounding it all with a try/catch block and clearing the interval in the catch like so:
const drawFaceInterval = () => {
setInterval(async () => {
try {
const detections = await face.detectAllFaces(videoRef.current.video, new face.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions();
const resizedDetections = face.resizeResults(detections, displaySize);
canvasRef.current.getContext('2d').clearRect(0, 0, camWidth, camHeight);
face.draw.drawDetections(canvasRef.current, resizedDetections);
face.draw.drawFaceLandmarks(canvasRef.current, resizedDetections);
face.draw.drawFaceExpressions(canvasRef.current, resizedDetections);
} catch (error) {
clearInterval(drawFaceInterval);
}
}, 50);
}
And calling it back in my faceAnalysis() function:
const faceAnalysis = () => {
face.matchDimensions(canvasRef.current, displaySize);
drawFaceInterval();
}
I have a functional component which had a button to call a method in it. Now i want to get rid of the button and call that method without any actions once the component loads.
I am making API calls inside this method and passing on the results to another component.
Also I am replacing the button with a progress bar meaning when a "search" is taking place, display the progress bar but I am having no luck. What am I doing wrong ?
export const Search = (props) => {
const { contacts, setContacts, onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const onSearch = async () => {
setLoading(true);
const emails = contacts
.filter(x => x.isChecked)
.map(item => item.emailAddress);
try {
const searchResults = await AppApi.searchMany(emails);
let userList = [];
for (let i = 0; i < searchResults.length; i++) {
//process the list and filter
}
userList = [...userList, ..._users];
}
onSearchComplete(userList); //passing the results.
} catch (err) {
console.log({ err });
setMsgBox({ message: `${err.message}`, type: 'error' });
}
setLoading(false);
}
return (
<Box>
{loading ? <LinearProgress /> : <Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
/*{onSearch()}*/ // function that was executed onclick.
</Box>
);
}
You will want to use the useEffect hook with an empty dependency array which will make it act as componentDidMount source.
export const Search = (props) => {
const { contacts, setContacts, onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const onSearch = async () => {
...
}
useEffect(() => {
onSearch();
}, []);
return (
<Box>
{loading ? <LinearProgress /> : <Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
</Box>
);
}
In the following code I can access a context at the parent and then it's undefined in a child. It works locally with simple FC setups, but fails downstream in a class component.
const HookDialog = () => {
const { data, setData } = useDialog(1); // I work fine
return (
<DialogHook>
<DialogContent>
<h1>Value: {data}</h1>
</DialogContent>
<Footer>
<FooterButton name="positive">Positive</FooterButton>
</Footer>
</DialogHook>
);
}
export const FooterButton: React.FC<FooterButtonProps> = (
{
children,
name,
className,
...props
}) => {
const dialogHook = useDialog(); // I'm undefined!
return(
<Button {...props} className={cssNames} ...>
{children}
</Button>
);
}
export const DialogProvider = props => {
const [dialog, setDialog] = useState<ReactElement<typeof Dialog>>();
const [data, setData] = useState<any>();
return (
<DialogContextHook.Provider value={{ dialog, setDialog, data, setData }} {...props} >
{props.children}
{dialog}
</DialogContextHook.Provider>
)
}
type CloseEvent = (source: string, data:any) => void;
interface useDialog extends DialogContextHook {
close: (source: string) => void;
}
export const useDialog = (
initialValue?: any,
onClose?: CloseEvent) => {
const context = useContext(DialogContextHook);
if (!context) {
throw new Error('useDialog must be used within a DialogProvider');
}
useEffect(() => {
context.setData(initialValue);
},[])
const close = (source: string) => {
context.setDialog(undefined);
onClose?.(source, context.data);
}
return { ...context, close };
}
<DialogProvider>
<VOITable/>
</DialogProvider>
Update
I recreated FooterButton in the downstream project and the same code works, just not when imported.