this is undefined when calling child function through a parent function - javascript

UPDATE: I have figured out a muuuuch simpler workaround by sing in the typescript file so the JS parent is no longer needed. ~facepalm~ Thanks for all your suggestions!
I am trying to get a button to trigger the function affTimer() inside the child function component but I keep getting the error "this is undefined" in relation to the function call. Here is the two code files:
affType.js
import React, {Component} from 'react';
import ReactPlayer from 'react-player'
import { Link } from 'react-router-dom';
import affirmationService from '../Services/requestService'
import affTrack from '../audio/inner.wav';
import warn from '../audio/warning.wav';
import Player from '../Player/Player';
import videoBG from '../videos/InnerStrength.mp4';
import Type from '../Type/Type.tsx';
import Button from "../customButton";
import {tXP} from '../Type/Type.tsx';
class affType extends Component {
constructor(props) {
super(props);
this.state = {character: undefined};
this.child = React.forwardRef();
this.startGame = this.startGame.bind(this);
}
async componentDidMount() {
const { match: { params } } = this.props;
//let affirmation_id = params.affirmation_id;
//let response = await affirmationService.getById(affirmation_id);
//this.setState({character: response.data});
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.display = 'none';
}, 1000);
}, 10);
}
spawnDialog() {
document.getElementById('overlay_1').style.display = 'block';
setTimeout(() => {
document.getElementById('overlay_1').style.opacity = 1;
}, 10);
}
destroyDialog() {
document.getElementById('overlay_1').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_1').style.display = 'none';
}, 1000);
}
repeat() {
document.getElementById('overlay_2').style.opacity = 0;
document.querySelector('video').play();
setTimeout(() => {
document.getElementById('overlay_2').style.display = 'none';
}, 1000);
}
test_ended() {
document.getElementById('overlay_2').style.display = 'block';
setTimeout(() => {
document.getElementById('audio_end').play();
document.getElementById('overlay_2').style.opacity = 1;
}, 10);
}
startGame() {
var track = document.getElementById('aff');
track.play();
this.child.current.affTimer();
}
render() {
return (
<div>
<div className="contentplayer">
<audio id='aff'><source src={affTrack} /></audio>
<video autoPlay muted loop id="myVideo">
<source src={videoBG} type="video/mp4" />
</video>
<audio id="audio_end" src="/Audio/Inner Strength completed quest - play with completed quest prompt.wav"/>
</div>
<p>{tXP}</p>
<Button
border="none"
color="pink"
height = "200px"
onClick={this.startGame}
radius = "50%"
width = "200px"
children = "Start!"
/>
<Type ref={this.child}>
</Type>
<div className="aligntopright" onClick={() => {this.spawnDialog()}}>
<div className="backbtn-white"></div>
</div>
<div className="overlay_blk_fast" id="overlay_blk_fast"></div>
<div className="overlay" id="overlay_1">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Are you sure you would like to go back to the selection page?</h3>
<h2 className="no" onClick={() => {this.destroyDialog()}}>No</h2>
<Link to="/affirmation"><h2 className="yes">Yes</h2></Link>
</div>
</div>
</div>
<div className="overlay" id="overlay_2">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Would you like to repeat this quest?</h3>
<Link to="/affirmation"><h2 className="no">Go back</h2></Link>
<h2 className="yes" onClick={() => {this.repeat()}}>Repeat</h2>
</div>
</div>
</div>
</div>
)
}
}
export default affType;
type.tsx
import React, {Component} from 'react';
import useTypingGame from "react-typing-game-hook";
import { textSpanContainsTextSpan } from 'typescript';
var xpM = 0;
var i = 0;
var err = 0;
var xp = 5;
var tXP = 0;
var addXP = 1;
var bonus = 0;
var bonusCounter = 0;
//var warnP = new Audio({warn});
//var affTrackP = new Audio('../audio/inner.wav');
function TypeF() {
let text_array = [
"There is strength and solidity within me",
"Courage is flooding through my veins",
"I possess strength within my heart",
"I am leading the charge with courage, and a vigorous resolution",
"There is a force inside me that is unbelievably powerful",
"There is a brave, radiant spirit inside me",
"I am a tall tree, with thick and strong roots",
"I was born for this",
"There is a divinity within",
"I am a force of nature",
"I possess the mental fortitude of those who climb the highest peaks",
"I was born with a determined spirit",
"There is an intensity in my eyes"
];
let text = text_array[i];
const {
states: {
charsState,
length,
currIndex,
currChar,
correctChar,
errorChar,
phase,
startTime,
endTime
},
actions: { insertTyping, resetTyping, deleteTyping }
} = useTypingGame(text);
const handleKey = (key: any) => {
if (key === "Escape") {
resetTyping();
} else if (key === "Backspace") {
deleteTyping(false);
} else if (key.length === 1) {
insertTyping(key);
}
};
if (currIndex + 1 === length) {
xpM = xpM + 1;
bonusCounter = bonusCounter + 1;
err = err + errorChar;
addXP = ((xp * correctChar) - (err * 2)) * xpM;
if (err > correctChar) {
addXP = correctChar * 3;
}
tXP = tXP + addXP;
if (bonusCounter >= 5) {
bonus = bonus + 1;
bonusCounter = 0;
}
resetTyping();
}
var tmr;
var cd = 18;
function affTimer() {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
function tock() {
if (cd > 0) {
cd = cd - 1;
console.log(cd);
}
else if (cd <= 0) {
if (i < text_array.length) {
i = i + 1;
cd = 18;
resetTyping();
}
else {
i = text_array.length;
}
}
}
return (
<div className='container'>
<div
className="typing-test"
id="start"
onKeyDown={(e) => {
handleKey(e.key);
e.preventDefault();
}
}
tabIndex={0}
>
{text.split("").map((char: string, index: number) => {
let state = charsState[index];
let color = state === 0 ? "white" : state === 1 ? "green" : "red";
return (
<span
key={char + index}
style={{ color }}
className={currIndex + 1 === index ? "curr-letter" : ""}
>
{char}
</span>
);
})}
</div>
<h2 className='debug'> TIMER: {cd}, I: {i}, ERRORS: {err}, MULTIPLIER: {xpM}, Type XP: {correctChar * xp}, CurrXP: {correctChar * xp * xpM} XPTotal: {tXP} bonusCounter: {bonusCounter}, BONUS: {bonus}</h2>
</div>
);
}
export {tXP};
export default TypeF;
Any help would be amazing, I have been stuck on this for 2 days and it is the last bit I need to complete so I can move to the next phase.

Your child component is a function component. You can't get a ref to an instance of a function component, because there is no instance. If you really need to use a function component and also expose some custom object as a ref, then you can use the useImperativeHandle hook plus forwardRef to define what the parent component should receive on its ref. For example:
const Type = React.forwardRef((props, ref) => {
// ...
useImperativeHandle(ref, () => {
// The following object is what will get assigned to the
// parent component's this.child.current
return {
afftimer: function () {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
}
});
// ...
})
But useImperativeHandle is not a common thing to use. There is likely a more standard way to solve your problem. The normal way for a parent component to tell a child component what to do is with props, not with refs.

Related

React video player progress bar for multiple videos behaves strange

I am creating a react application where I am using three videos with custom progress bar for each one to be displayed at screen. I am facing a problem with progress bar update for all the three videos. When one video is fully played it's progress works fine but as soon as second video start playing, It also updates the bar width together (video1 and video2). I am using "timeupdate" event for updating the video progress bar.
`
import * as React from 'react';
import './style.css';
export default function App() {
var v3 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4`;
var v2 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4`;
var v1 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4`;
const [id, setId] = React.useState(1);
const [currentVideo, setCurrentVideo] = React.useState('');
const [barCompleted, setBarCompleted] = React.useState<any>({
myBar1: false,
myBar2: false,
myBar3: false,
});
const videoRef = React.useRef<any>();
const progress1 = React.useRef<any>();
const progress2 = React.useRef<any>();
const progress3 = React.useRef<any>();
const myBar1 = React.useRef<any>();
const myBar2 = React.useRef<any>();
const myBar3 = React.useRef<any>();
const handleOnEnded = () => {
videoRef.current.removeEventListener('timeupdate', () => {});
// setBarCompleted(prev=>{myBar1:true,...prev})
setId((prev) => prev + 1);
};
const getVideoById = (id) => {
if (id == 1) {
setCurrentVideo(v1);
} else if (id == 2) {
setCurrentVideo(v2);
} else if (id == 3) {
setCurrentVideo(v3);
}
};
React.useEffect(() => {
getVideoById(id);
}, [id]);
React.useEffect(() => {
videoRef?.current?.load();
configureProgress(id);
}, [currentVideo, id]);
const configureProgress = (id) => {
videoRef?.current?.addEventListener('timeupdate', (e) => {
// console.log(e.target.currentTime);
let i = 0;
if (i == 0) {
i = 1;
// var elem = document.getElementById('myBar1');
let width = 1;
let sid = setInterval(frame, 1000);
function frame() {
if (videoRef.current.ended || width >= 100) {
clearInterval(sid);
i = 0;
if (id == 1) {
progress1.current.style.backgroundColor = 'green';
myBar1.current.style.width = 0 + '%';
} else if (id == 2) {
progress2.current.style.backgroundColor = 'green';
myBar2.current.style.width = 0 + '%';
} else if (id == 3) {
progress3.current.style.backgroundColor = 'green';
myBar3.current.style.width = 0 + '%';
}
} else {
width = Math.ceil(
(videoRef.current.currentTime / videoRef.current.duration) * 100
);
if (id == 1) {
myBar1.current.style.width = width + '%';
console.log('width update id1');
} else if (id == 2) {
myBar2.current.style.width = width + '%';
console.log('width update id2');
} else if (id == 3) {
myBar3.current.style.width = width + '%';
console.log('width update id3');
}
}
}
}
});
};
return (
<div>
<div className="progress-container">
<div id="myProgress1" ref={progress1}>
<div id="myBar1" ref={myBar1}></div>
</div>
<div id="myProgress2" ref={progress2}>
<div id="myBar2" ref={myBar2}></div>
</div>
<div id="myProgress3" ref={progress3}>
<div id="myBar3" ref={myBar3}></div>
</div>
</div>
{currentVideo && (
<video
autoPlay={true}
controls
muted={true}
id="myVideo1"
width={300}
height={250}
ref={videoRef}
crossOrigin="anonymous"
onEnded={handleOnEnded}
preload="metadata"
playsInline
>
<source src={currentVideo} type="video/mp4" />
</video>
)}
</div>
);
}
`
.progress-container {
display: flex;
}
#myProgress1,
#myProgress2,
#myProgress3 {
width: 33%;
background-color: #ddd;
}
#myBar1,
#myBar2,
#myBar3 {
width: 1%;
height: 30px;
background-color: #04aa6d;
}
[enter image description here](https://i.stack.imgur.com/usYQq.png)
I am expecting that "timeupdate" event should not run for second video as it also updates width for previous video(1st video).

minesweeper game logic problem (fetching another components props from a component)

I'm new to React and I'm trying to grasp the broad strokes by building a simple minesweeper game. I'm able to render a 10x10 board (Grid), and reveal squares (Square). However, I've hit a wall with the step in which multiple squares are revealed with a single click.
The way I've written it (I think) requires a square component to know if the surrounding square components have a mine. How should I go about this? Or am I writing it in a way that isn't really consistent with React's intended use?
Code:
----------
Grid.js
----------
import React from 'react';
import Square from './Square';
export default function Grid(props) {
const grid = new Array(10);
for (let i = 0; i < 10; i++) {
grid[i] = new Array(10).fill(null);
}
return (
<div>
{
grid.map((row, x) => {
return (
<div key={x}>
{
row.map((col, y) => {
const randInt = Math.floor(Math.random() * 5);
return (
<Square
row={x}
col={y}
id={`square-${x}-${y}`}
key={`square-${x}-${y}`}
hasMine={randInt === 0}
/>
)
})
}
</div>
)
})
}
</div>
)
}
---------
Square.js
---------
export default function Square(props) {
function uncover(e) {
if (!props.hasMine && e.target.className !== 'white-square') {
e.target.className = 'white-square';
let right = document.getElementById(`square-${props.row}-${props.col + 1}`);
let bottomRight = document.getElementById(`square-${props.row + 1}-${props.col + 1}`);
let bottom = document.getElementById(`square-${props.row + 1}-${props.col}`);
let bottomLeft = document.getElementById(`square-${props.row + 1}-${props.col - 1}`);
let left = document.getElementById(`square-${props.row}-${props.col - 1}`);
let topLeft = document.getElementById(`square-${props.row - 1}-${props.col - 1}`);
let top = document.getElementById(`square-${props.row - 1}-${props.col}`);
let topRight = document.getElementById(`square-${props.row - 1}-${props.col + 1}`);
if (right) {
right.click();
}
if (bottomRight) {
bottomRight.click();
}
if (bottom) {
bottom.click();
}
if (bottomLeft) {
bottomLeft.click()
}
if (left) {
left.click()
}
if (topLeft) {
topLeft.click()
}
if (top) {
top.click()
}
if (topRight) {
topRight.click()
}
}
}
return (
<div
className={props.hasMine ? 'red-square' : 'grey-square'}
id={props.id}
onClick={uncover}
/>
)
}

Count using useState doesnt update state until question 6 even when answered correctly react

im trying to create a quiz app in react using hooks. i want the answers to be clickable and once clicked the user will then move onto the next question. My problem is that, the score state isnt updating until question 6! i am making an API call to get the questions and answers using useEffect. i know that useState is asynchronous and thats why state doesnt update straight away, its just even if i answer the first 6 questions correctly, after question 6 the score is still showing 1. Does anyone have a way around this?
My API call using useEffect:
useEffect(() => {
axios.get("https://my-quiz-server.herokuapp.com/api/newq").then((res) => {
const allQuestions = res.data;
setResult([allQuestions]);
})
.catch((error) => {
console.log(error);
});
}, [setResult]);
My Quiz component:
import React, {
useState,
useContext,
useEffect
} from "react";
import {
QuizContext
} from "../Helpers/context";
const MainQuiz = () => {
const {
score,
setScore,
result
} = useContext(QuizContext);
const [currentQuestion, setCurrentQuestion] = useState(0);
const [optionChosen, setOptionChosen] = useState("");
console.log(optionChosen);
console.log(result);
const nextQuestion = (correctAnswer) => {
if (optionChosen === correctAnswer) {
setScore((score) => score + 1);
}
setCurrentQuestion((currentQuestion) => currentQuestion + 1);
};
useEffect(() => {
console.log("score updated", score);
}, [score]);
return ( <
div className = "quiz" > {
result.map((question, index) => {
if (currentQuestion < question.allQuiz.length) {
return ( <
h3 key = {
index
} > {
question.allQuiz[currentQuestion].q_prompt
} < /h3>
);
} else {
return null;
}
})
} <
div className = "answer__container" > {
result.map((answers, index) => {
if (currentQuestion < answers.allQuiz.length) {
return ( <
div className = "answer__options" > {
" "
} <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("a");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].a
} <
/button> <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("b");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].b
} <
/button> <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("c");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].c
} <
/button> <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("d");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].d
} <
/button> <
/div>
);
} else {
return null;
}
})
} <
/div> <
span > {
score
} < /span> <
/div>
);
};
export default MainQuiz;

React Animations - change only one logo at a time from a list of 6 initial logos displayed?

I am working on a feature where I need to change only one logo at a time out of a list of 6 logos displayed initally.
The example is shown here:: https://www.loom.com/share/6a282423368a46418248a789ce4fc139
And its there in this website also in the bottom:: https://www.wonderlandams.com/about?fbclid=IwAR0wfFrqYVwor1UJfZGcWK2MaU0kBWNiaacg8kGb_IC--VaziorY6BDt7lA.
import React, { useState, useEffect } from 'react'
import tw from 'twin.macro'
import Image from './image'
const LogoGrid = ({ logos, style }) => {
const groupDisplay = logos.slice(0, 6);
const [group, setGroup] = useState(groupDisplay);
const [groupLength, setGroupLength] = useState(0);
let shuffledLogos, i;
const shuffle = (array) => array.sort(() => Math.random() - 0.5);
useEffect(() => {
console.log("UEFFECT RUNNING");
const timer = setInterval(() => {
i = Math.floor(Math.random() * 6);
shuffledLogos = shuffle(logos);
let gl = groupLength + 1 ;
if(groupDisplay[i] == logos[i]) {
let k = i + 1;
groupDisplay[i] = shuffledLogos[k];
}
else {
groupDisplay[i] = shuffledLogos[i];
}
setGroupLength(gl)
setGroup(groupDisplay)
}, 2000)
return () => clearInterval(timer)
}, [group, groupLength])
return (
<div css={[tw`relative`, style]}>
<div
css={[
tw`opacity-0 grid-cols-3 grid-rows-2 gap-12
lg:(gap-x-16 gap-y-12 mt-26) xl:gap-x-32`,
tw`grid transition transition-opacity duration-300 ease-in-out opacity-100`,
]}
>
{(group || []).map((logo, index) => (
<div key={index} css={tw`h-12`}>
<Image image={logo} />
</div>
))}
</div>
</div>
)
}
export default LogoGrid
I have made one component using tailwind css but it does not work as expected. Can anyone kindly point me in the right direction or any changes in the logic that can give me similar effects ?

Logic for rotating a square

The following code is using React.
I have a square positioned at 45deg, that has four elements. Upon click on each element, I want the square to rotate the relevant degrees so that the clicked element is on top. (In the image, 3 is currently selected).
Please see the notes within the code. I hope it explains what my logic is.
Here is the component code:
class CentreCtrls extends React.Component {
constructor(props) {
super(props);
this.state = {
aboutUs: {
activeCtrl: 0
}
}
}
// animate the square to rotate the relevant degrees
// so that the clicked element is at the top.
// this logic is flawed and not working correctly
componentDidUpdate() {
const activeHeading = this.state.aboutUs.activeCtrl;
const { centreCtrl } = this.refs;
const style = centreCtrl.style;
const currentDeg = parseInt(style.transform.slice(7, -4), 10);
const position = this.getPosition(activeHeading, 0);
const degRotation = position * 90;
console.log('pos:', position, 'deg:', currentDeg, '-', degRotation);
anime({
targets: this.refs.centreCtrl,
rotate: `-${currentDeg + degRotation}deg`,
duration: 150,
easing: 'linear',
});
}
onClickHandler = e =>
const ele = e.target;
const i = ele.parentNode.getAttribute('data-i');
this.setState({
aboutUs: {
activeCtrl: parseInt(i, 10),
},
});
};
// the purpose of this function is to find the current value
// to be assigned to the 'data-i' property on element, this 'data-i'
// is read in the above 'componentDidUpdate' function to animate
// the square the relevant degrees so that the clicked square is at
// top (where the purple 3 is in the image).
getPosition = (i, activeHeading) => {
const total = 3; // start: 0, ttl: 4
let correctSeq = i;
if (i === activeHeading) {
correctSeq = 0;
// if i == 2, then for our rotation purpose, its at position 3 in DOM
// because of the way, DOM renders elements from left to right within
// square
} else if (i === 2) {
correctSeq = 3;
} else if (i === 3) {
correctSeq = 2;
}
let pos = total - activeHeading + (correctSeq + 0);
if (pos > 3) {
pos = pos - 3;
}
return pos;
};
render() {
const { data } = this.props;
const activeHeading = this.state.aboutUs.activeCtrl;
return (
// using the react v15 style ref
<div
className="centreCtrl"
ref="centreCtrl"
style={{ transform: 'rotate(45deg)' }}
>
{data.map((entry, i) => {
const j = this.getPosition(i, activeHeading);
return (
<div
className="ctrl"
key={i}
data-i={j}
id={`${entry.narrative.heading}`}
onClick={this.onClickHandler}
>
<div
className="textContainer"
id={entry.narrative.heading}
ref={`textRef${i}`}
>
{j}
</div>
</div>
);
})}
</div>
);
}
}
Edit: How can I get the rotation logic to work correctly. More to the point, I am looking for input into what is the best logic for rotation calculation of this square. It looks far easier than I estimated.
Assuming you want to rotate clockwise, something like this might help:
import React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { Container, Square, Block, Top, Bottom } from './components';
const rotateRight = items => {
const last = items.pop();
items.unshift(last);
};
class App extends React.Component {
state = {
items: ["a", "b", "c", "d"],
rotation: 0,
};
handleClick = i => {
let start = i;
let count = 0;
let items = [...this.state.items];
const end = items.length - 1;
while (start <= end) {
rotateRight(items);
start += 1;
count += 1;
}
this.setState(state => ({
rotation: ((count * 90) % 360),
}));
};
render() {
const { items, rotation } = this.state;
return (
<Container>
<Square rotation={rotation}>
<Top>
<Block onClick={() => this.handleClick(0)}>
{items[0]}
</Block>
<Block onClick={() => this.handleClick(1)}>
{items[1]}
</Block>
</Top>
<Bottom>
<Block onClick={() => this.handleClick(2)}>{
items[2]}
</Block>
<Block onClick={() => this.handleClick(3)}>
{items[3]}
</Block>
</Bottom>
</Square>
</Container>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
StackBlitz link here.

Categories