Cannot read properties of null (reading 'video') - reactjs

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();
}

Related

How to mock custom hook which returns scrolled element's data with jest and enzyme

In my react component I am adding a css class to a div when another div get scrolled :
import useOnScroll from '../utils';
const MyComponent = (props) => {
const scrollableContainerRef = useRef();
const { scrollTop: containerScrollTop } = useOnScroll(scrollableContainerRef);
...
return (
<div className="commonBtmSheet">
<div data-test="btmSheet" className="OverlayWrap">
<div data-test="btmSheet-header" className={`header ${containerScrollTop > 0 ? 'scrolled' : '' }`}>
...
<div data-test="btmSheet-body" ref={scrollableContainerRef} className="OverlayOuter">
...
useOnScroll Hook:
import { useEffect, useRef, useState } from "react";
function useOnScroll(ElRef) {
const prevScrollTop = useRef();
const [scrollData, setScrollData] = useState({});
const handleScroll = (e) => {
const el = e.target;
const { scrollTop } = el;
let direction = "down";
if (prevScrollTop.current > scrollTop) {
direction = "up";
}
setScrollData((prev) => ({
...prev,
scrollTop,
direction,
}));
};
useEffect(() => {
const elm = ElRef.current;
if (elm) {
elm.addEventListener("scroll", handleScroll);
}
return () => {
if (elm) {
elm.removeEventListener("scroll", handleScroll);
}
};
}, [ElRef.current]);
return scrollData;
}
export default useOnScroll;

useState and useRef don't work together properly

I'm in a case where I need to combine useState and useRef in a way, but I'm having a problem when it comes to deleting, so for example when I call the delete the update function firstly I update the state and then update the user jeff, but the deleted item firstly is deleted from the iterated and showing list, then again pop-ups, but kinda messed, the content is not the same.
Here is how I manage delete:
const Delete = (props: any, index: number, editorsRef: any): void => {
const newEmployment = cloneDeep(props.resume.employmentHistory.positions)
newEmployment.splice(index, 1)
props.setResume(
{
...props.resume, employmentHistory:
{
...props.resume.employmentHistory,
positions: newEmployment
}
}
)
const refs = {} as any;
newEmployment.map(
(position: any, index: number) =>
refs[index] = position.description
)
editorsRef.current = refs;
console.log(newEmployment, editorsRef.current)
}
And here is the iteration of the editor that has the value of useReff, and the interaction and everything related to how I change the state, something Is missing but I don't know, I really don't.
//#ts-nocheck
import { Item } from '../..';
import { WorkItemValidation } from '../../../../../utils/Validation';
import { Date, Input } from '../../../Shared';
import { Editor } from '../../..';
import { Items } from '../Components';
import { Container, Liner, Linerier } from './Components'
import { useCallback, useEffect, useRef, useState } from 'react';
import { Employment as Work } from '../../../../../utils/operations/Resume';
import { debounce } from 'lodash';
const Employment: any = (props: any) => {
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
const [loading, setLoading] = useState(true);
const editorsRef = useRef({})
const currentIndexRef = useRef('')
console.log(props, 'props', editorsRef, 'editorsRef')
useEffect(() => {
if (loading === true) {
const refs = {}
props.resume.employmentHistory.positions.map(
(position: any, index: number) =>
refs[index] = position.description
)
editorsRef.current = refs;
setLoading(false);
}
}, [])
const isFocusedRef = useRef(false);
const autoUpdateCallbackRef = useRef(null);
const callWorkDescription = useCallback(
debounce((props, currentIndex, editor) => {
Work.Description(props, currentIndex, editor);
currentIndexRef.current = ''
}, 1000),
[]
);
const handleDescription = (e: any, index: number) => {
currentIndexRef.current = index
editorsRef.current = {
...editorsRef.current,
[index]: e,
}
callWorkDescription(props, index, e)
};
useEffect(() => {
const intervalId = setInterval(() => {
if (isFocusedRef.current) autoUpdateCallbackRef.current?.();
}, 1000);
return () => clearInterval(intervalId);
}, []);
const handleFocus = () => isFocusedRef.current = true;
const handleBlur = () => isFocusedRef.current = false;
return (
<Container>
{
loading === false &&
<Items>
{
props.resume.employmentHistory.positions.map(
(position: any, index: number) => {
return (
<Item delete={() => Work.Delete(props, index, editorsRef)}
>
<Liner style={{ width: '100%' }}>
<Editor
id={`editor-${index}`}
label="Description"
value={editorsRef.current[index] && editorsRef.current[index] || ''}
placeholder="Brief description of the role..."
onChange={(e) => handleDescription(e, index)}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</Liner>
</Item>
)
}
)
}
</Items>
}
</Container >
)
}
export default Employment;

useEffect only runs on hot reload

I have a parent/child component where when there is a swipe event occurring in the child the parent component should fetch a new profile. The problem is the useEffect in the child component to set up the eventListeneners currently is not running, only occasionally on hot-reload which in reality should run basically every time.
Child component
function Profile(props: any) {
const [name] = useState(`${props.profile.name.title} ${props.profile.name.first} ${props.profile.name.last}`);
const [swiped, setSwiped] = useState(0)
const backgroundImage = {
backgroundImage: `url(${props.profile.picture.large})`
};
const cardRef = useRef<HTMLDivElement>(null);
const card = cardRef.current
let startX:any = null;
function unify (e:any) { return e.changedTouches ? e.changedTouches[0] : e };
function lock (e:any) { if (card) {startX = unify(e).clientX; console.log(startX)} }
function move (e: any) {
console.log('move')
if(startX) {
let differenceX = unify(e).clientX - startX, sign = Math.sign(differenceX);
if(sign < 0 || sign > 0) {
setSwiped((swiped) => swiped +1)
props.parentCallback(swiped);
startX = null
}
}
}
// Following code block does not work
useEffect(() => {
if (card) {
console.log(card)
card.addEventListener('mousedown', lock, false);
card.addEventListener('touchstart', lock, false);
card.addEventListener('mouseup', move, false);
card.addEventListener('touchend', move, false);
}
})
return (
<div>
<h1 className="heading-1">{name}</h1>
<div ref={cardRef} className="card" style={backgroundImage}>
</div>
</div>
);
}
Parent component
function Profiles() {
const [error, setError] = useState<any>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [profiles, setProfiles] = useState<any[]>([]);
const [swiped, setSwiped] = useState(0)
useEffect(() => {
getProfiles()
}, [swiped])
const callback = useCallback((swiped) => {
setSwiped(swiped);
console.log(swiped);
}, []);
const getProfiles = () => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setProfiles(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
if (error) {
return <h1 className="heading-1">Error: {error.message}</h1>;
} else if (!isLoaded) {
return <h1 className="heading-1">Loading...</h1>;
} else {
return (
<div id="board">
{profiles.map(profile => (
<Profile key={profile.id.value} profile={profile} parentCallback={callback}/>
))}
</div>
);
}
}
If you want the parent components swiped state to change, you need to pass "setSwiped" from the parent to the child compenent. You will also need to pass "swiped" to the child to use its current value to calculate the new value. I'm going to assume you declared the useState in the child component trying to set the parents state of the same name, so I'm going to remove that useState Declaration in the child altogether.
Here's an example of passing the setSwiped method and swiped value to the child:
PARENT
import React, {useState, useEffect, useCallback} from 'react';
import './Index.css';
import Profile from './Profile'
function Profiles() {
const [error, setError] = useState<any>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [profiles, setProfiles] = useState<any[]>([]);
const [swiped, setSwiped] = useState(0)
useEffect(() => {
getProfiles()
}, [swiped])
const callback = useCallback((swiped) => {
setSwiped(swiped);
console.log(swiped);
}, []);
const getProfiles = () => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setProfiles(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
if (error) {
return <h1 className="heading-1">Error: {error.message}</h1>;
} else if (!isLoaded) {
return <h1 className="heading-1">Loading...</h1>;
} else {
return (
<div id="board">
{profiles.map(profile => (
<Profile key={profile.id.value} profile={profile} parentCallback={callback} setSwiped={setSwiped} swiped={swiped}/>
))}
</div>
);
}
}
export default Profiles;
CHILD
import React, {useState, useRef, useEffect } from 'react';
import './Index.css';
function Profile(props: any) {
const [name] = useState(`${props.profile.name.title} ${props.profile.name.first} ${props.profile.name.last}`);
const backgroundImage = {
backgroundImage: `url(${props.profile.picture.large})`
};
const cardRef = useRef<HTMLDivElement>(null);
const card = cardRef.current
let startX:any = null;
function unify (e:any) { return e.changedTouches ? e.changedTouches[0] : e };
function lock (e:any) { if (card) {startX = unify(e).clientX; console.log(startX)} }
function move (e: any) {
console.log('move')
if(startX) {
let differenceX = unify(e).clientX - startX, sign = Math.sign(differenceX);
if(sign < 0 || sign > 0) {
props.setSwiped((props.swiped) => props.swiped +1)
props.parentCallback(props.swiped);
startX = null
}
}
}
useEffect(() => {
if (card) {
console.log(card)
card.addEventListener('mousedown', lock, false);
card.addEventListener('touchstart', lock, false);
card.addEventListener('mouseup', move, false);
card.addEventListener('touchend', move, false);
}
})
return (
<div>
<h1 className="heading-1">{name}</h1>
<div ref={cardRef} className="card" style={backgroundImage}>
</div>
</div>
);
}
export default Profile;
I'm hoping I didn't miss anything here.
Best of luck.

useEffect dosn't save data in localstorage

I have a simple app, sorta for chat purpuses. I fetch data from static file in json format. So this app shows all the messages from that file but also I want to edit the messeges, delete them and add via local storage. For that I used useEffect, but after refresh all the changes I do disappear.
This is my component:
export const WorkChat = (props) => {
const [messageValue, setMessageValue] = useState('');
const [edit, setEdit] = useState(null);
const [editmessageValue, setMessageEditValue] = useState('')
const submitMessage = () => {
const newMessage = {
id: Math.floor(Math.random() * 10000),
message: messageValue
}
props.addMessage(newMessage);
setMessageValue('')
}
const removeMsg = (id) => {
props.deleteMessage(id)
}
const goToEditMode = (message) => {
setEdit(message.id);
setMessageEditValue(message.message)
}
const saveChanges = (id) => {
const newMessagesArray = props.messages.map(m => {
if(m.id === id){
m.message = editmessageValue
}
return m
})
props.updateMessage(newMessagesArray);
setEdit(null)
}
useEffect(()=> {
let data = localStorage.getItem('work-messages');
if(data){
props.setMessages(JSON.parse(data))
}
}, []);
useEffect(()=> {
localStorage.setItem('work-messages', JSON.stringify(props.messages))
},[props.messages])
return (
<div className={s.workChatContainer}>
<input className={s.workInput} placeholder='Enter work message...' onChange={(e)=> setMessageValue(e.target.value)} value={messageValue}/>
<button className={`${s.btn} ${s.sendBtn}`} onClick={()=>submitMessage()}><SendIcon style={{fontSize: 20}}/></button>
<div>
{props.messages.map(m => (
<div key={m.id} className={s.messages}>
{edit !== m.id ? <div>
<span className={s.message}>{m.message}</span>
<button className={`${s.btn} ${s.deleteBtn}`} onClick={()=> removeMsg(m.id)}><DeleteOutlineIcon style={{fontSize: 15}}/></button>
<button className={`${s.btn} ${s.editBtn}`} onClick={()=> goToEditMode(m)}><EditIcon style={{fontSize: 15}}/></button>
</div>
:
<form>
<input className={s.editInput} value={editmessageValue} onChange={(e)=> setMessageEditValue(e.target.value)}/>
<button className={`${s.btn} ${s.saveBtn}`} onClick={()=> saveChanges(m.id)}><BeenhereIcon style={{fontSize: 15}}/></button>
</form>
}
</div>
))}
</div>
</div>
)
}
Just in case, this is my container component:
import { connect } from "react-redux"
import { setFloodMessagesAC, addFloodMessageAC, deleteFloodMessageAC, upadateMessageAC } from "../../redux/flood-reducer"
import { FloodChat } from "./FloodChat"
import { useEffect } from 'react'
import data from '../../StaticState/dataForFlood.json'
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
Why useEffect doesn't work? It seems to me like it should, but it doesnt.
I figured it out. Since I use data from static file, I need to implement functions that get/set data from/to local storage right where I import it which is container component. Once I put those useEffect functions in container component it works perfectly well.
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
useEffect(()=> {
let data = JSON.parse(localStorage.getItem('flood-messages'));
if(data){
props.setFloodMessages(data)
}
console.log('get')
}, [])
useEffect(() => {
localStorage.setItem('flood-messages', JSON.stringify(props.messages));
console.log('set')
}, [props.messages]);
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)

Using Draft js mention plugin with react hooks

I have been trying to get draft js mention plugin to work with react hooks but can't seem to figure what's wrong with the code. Appreciate any help on this.
import React, { useRef, useState, useEffect } from "react";
import { EditorState } from "draft-js";
import Editor from "draft-js-plugins-editor";
import createMentionPlugin, { defaultSuggestionsFilter } from "draft-js-mention-plugin";
import mentions from "./mentions";
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [suggestions, setSuggestions] = useState(mentions);
const editor = useRef(null);
useEffect(() => {
editor.current.focus();
}, [])
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
};
return (
<div style={{ border: "1px solid gray" }}>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
plugins={plugins}
ref={editor}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
/>
</div>
);
}
You need to move the draft-js plugin configuration outside the component arrow function. This is a pretty basic Draft-JS implementation using a functional component and hooks:
import React, { useState, useRef } from 'react'
import { EditorState } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
import 'draft-js/dist/Draft.css'
import 'draft-js-mention-plugin/lib/plugin.css'
import mentions from "./mentions"
// Draft-JS-Mentions plugin configuration
const mentionPlugin = createMentionPlugin()
const { MentionSuggestions } = mentionPlugin
const plugins = [mentionPlugin]
const MyEditor= () => {
const [suggestions, setSuggestions] = useState(mentions)
// Draft-JS editor configuration
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
)
const editor = useRef(null)
// Check editor text for mentions
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
}
const onAddMention = () => {
}
// Focus on editor window
const focusEditor = () => {
editor.current.focus()
}
return (
<div onClick={() => focusEditor()}>
<Editor
ref={editor}
editorState={editorState}
plugins={plugins}
onChange={editorState => setEditorState(editorState)}
placeholder={'Type here...'}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
onAddMention={onAddMention}
/>
</div>
)
}
export default MyEditor
Just move these lines outside component and it will work:
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
.. ... ...
}
!!!!!!!!!!!!!!!! PAY ATTENTION !!!!!!!!!!!!
The onSearchChange method will be triggered once the '#' character is typed, so in this case it will return just 5 items that fit the empty string...
To prevent this to be happened, just check that the value we want to search is not empty:
const onSearchChange = ({ value }) => {
if (value) {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}
};

Resources