Using Use Effect with hooks - javascript

I am trying to create a demo weather fetching app , I need to display weather data of city name as typed , can it be achieved with us effect or there is any other approach for the same
also I need to display results only if data exits currently in my code if data is not available .. default state data is showing kindly suggest to fix both issues
import React, {
useState,
useEffect
} from "react";
import Clock from "react-live-clock";
import "./Weather.css";
import {
ToastContainer,
toast
} from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import {
API_KEY
} from "./Api";
export default function Weather() {
const [city, setcity] = useState("goa");
const [currentweather, setcurrentweather] = useState("");
const [icon, seticon] = useState("");
const [weathertext, setweathertext] = useState("")
const lpsearch = () => {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`;
if (city) {
fetch(url)
.then((res) => res.json())
.then((data) => {
if (data.cod === "404") {
// console.log("City Not found");
toast.error(`Entered City-Name ${city} Not Found `, {
position: "top-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
setcity("");
} else {
console.log(data);
setcurrentweather(data.main.temp);
setweathertext(data.weather[0].description)
seticon(data.weather[0].icon);
setcity("");
}
// setcity("");
});
}
};
let inputhndl = (event) => {
setcity(event.target.value);
};
return ( <
div >
<
ToastContainer position = "top-center"
autoClose = {
2000
}
hideProgressBar = {
false
}
newestOnTop = {
false
}
closeOnClick rtl = {
false
}
pauseOnFocusLoss draggable pauseOnHover /
> {
" "
} <
div className = "container-fluid" > {
" "
} <
h2 className = "text-center alert-success font-weight-bolder " >
<
Clock format = {
"dddd, DD MMMM , YYYY, h:mm:ss"
}
ticking = {
true
}
/> <
/h2> <
/div> <
div className = "lpcentered " >
<
h3 className = "alert-info text-capitalize text-center" >
Enter City 's <i className="fas fa-city text-secondary"></i> Name <
/h3> <
input onChange = {
inputhndl
}
value = {
city
}
placeholder = "Enter Here"
className = "form-control btn-outline-warning font-weight-bolder mb-3 mt-3" >
< /input> <
button className = "form-control btn-success font-weight-bold "
onClick = {
lpsearch
} >
Search < i className = "fas fa-search " > < /i> <
/button> <
div className = "text-center" >
<
h3 className = "alert-secondary text-capitalize" > {
city
} < /h3>
<
h3 className = "text-capitalize alert-info" > {
city
} < i class = "fas fa-temperature-low" > < /i> - {currentweather} <span>℃</span > < /h3> <
h3 > {
weathertext
} < img src = {
`http://openweathermap.org/img/wn/${icon}#2x.png`
}
alt = "icon"
srcset = "" /
> < /h3>
<
/div> <
/div> <
/div>
);
}

Related

this is undefined when calling child function through a parent function

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.

How render two different contents of a modal window?

Good afternoon, I have a modal window in which the content is displayed depending on the condition.
I can't to do when the subscription is disabled, the content of the modal changed to "Subscription successful disabled".
And when you click on the "Enable subscription" button, the content of the modal changed to "Enable subscription by clicking on the button"
Now it turns out that "Subscription successful disabled" and "Enable subscription by clicking on the button" are displayed at once.
The enableSub and disableSub functions emulate sending data to the server, and the state emulates receiving data from the server.
const {
useState
} = React;
const state = {
isSubscriptionEnabled: true,
}
const enableSub = () => {
state.isSubscriptionEnabled = true
}
const disableSub = () => {
state.isSubscriptionEnabled = false
}
function App() {
const [isOpen, setIsOpen] = useState(false)
return ( <
div > {
isOpen &&
< div className = "modalWrapper" >
<
div className = "modal" >
<
button onClick = {
() => setIsOpen(false)
} > close < /button> <
div > {
state.isSubscriptionEnabled &&
< div >
Are you sure disable subscription ?
<
button className = "button"
onClick = {
disableSub
} >
Disalbe subscription <
/button> <
/div>
} {
!state.isSubscriptionEnabled &&
< div >
Subscription successful disabled <
button onClick = {
() => setIsOpen(false)
} > Thanks < /button> <
/div>
} {
!state.isSubscriptionEnabled &&
< div >
Enable subscription by clicking on button <
button className = "button"
onClick = {
enableSub
} >
Enable subscription <
/button> <
/div>
} <
/div> <
/div> <
/div>
}
{
state.isSubscriptionEnabled &&
< button className = "button"
onClick = {
() => setIsOpen(true)
} >
Disalbe subscription <
/button>
} {
!state.isSubscriptionEnabled &&
< button className = "button"
onClick = {
() => setIsOpen(true)
} >
Enable subscription <
/button>
} <
/div>
)
}
ReactDOM.render( < App / > , document.getElementById('app'));
.app {
position: relative;
}
.button {
margin: 20px;
}
.modalWrapper {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.modal {
width: 300px;
height: 150px;
padding: 30px;
position: absolute;
border: 1px solid teal;
background: #fff;
}
<div id="app"></div>
jsfiddle
import {React,useState} from 'react'
function App() {
const [isOpen, setIsOpen] = useState(false);
const [isSubscriptionEnabled, enableSubscription] = useState(false)
const enableSub = () => {
enableSubscription(true)
}
const disableSub = () => {
enableSubscription(false)
}
return (
<div> {
isOpen &&
<div className = "modalWrapper" >
<div className = "modal" >
<button onClick = {
() => setIsOpen(false)
} > close </button>
<div> {
isSubscriptionEnabled&&
<div>
Are you sure disable subscription ?
<button className = "button"
onClick = {
disableSub
} >
Disable subscription
</button>
</div>
} {
!isSubscriptionEnabled&&
<div>
Subscription successful disabled
<button onClick = {
() => setIsOpen(false)
} > Thanks </button>
</div>
} {
!isSubscriptionEnabled&&
<div>
Enable subscription by clicking on button
<button className = "button"
onClick = {
enableSub
} >
Enable subscription
</button>
</div>
} </div>
</div>
</div>
}
{
isSubscriptionEnabled&&
<button className = "button"
onClick = {
() => setIsOpen(true)
} >
Disable subscription
</button>
} {
!isSubscriptionEnabled&&
<button className = "button"
onClick = {
() => setIsOpen(true)
} >
Enable subscription
</button>
}
</div>
)
}
ReactDOM.render( < App / > , document.getElementById('app'));
React components only render when we change a component using its useState inside that particular component. it will not rerender on your global variable
Please try to format your code at least before posting here,

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;

Using jQuery in React to modify CSS of an element

Hello,
I know its not recommended to use jQuery with react & I am aware of method on react for changing CSS of element but here I am just trying to see if my req can be achieved or not , all i want is to change the colour of li element when corresponding tick icon is clicked for it, I am using a jQuery code
const markdone = () => {
let c = $("#ll")
console.log(c)
$(c).closest("li").css("background-color", "green");
};
but when i am clicking the css gets applied but not on its corresponding li element in my case for ex have attached image when i click on 3 tick icon css gets changed for 1 is there any way i can fix it
attaching whole code below
check markdone function for making css change :
const [input, setValue] = useState("");
const [todos, setTodos] = useState([]);
// passing entered
const handleInput = (event) => {
setValue(event.target.value);
};
const lp = (event) => {
// let c = [1,2,34,55]
event.preventDefault();
// if no input nothing will happen return none
if (!input) return;
// using spread operator its used whenever we need to add array data to exiting array
const newTodos = [...todos, input];
setTodos(newTodos);
// clearing input text
setValue("");
};
const handeldel = (index) => {
// console.log(index)
todos.splice(index, 1);
setTodos([...todos]);
// const newTodos = todos.splice(index, 1);
// setTodos([...newTodos]);
};
const [line, setline] = useState(false);
// const [ll, setll] = useState(false);
const markdone = () => {
let c = $("#ll")
console.log(c)
$(c).closest("li").css("background-color", "green");
};
useEffect(() => {
$(document).ready(function() {
$("#pk").click(function(e) {
// e.preventDefault();
alert('hello')
});
});
});
return ( <
div >
<
h1 id = "pk"
className = "text-center font-weight-bolder alert-info mb-5" >
Tasks To Do < i class = "fas fa-clipboard-list text-success" > < /i> <
/h1> <
div class = "input-group mb-3 container" >
<
input className = "form-control border-primary font-weight-bold"
style = {
{
height: 60
}
}
placeholder = "Enter Text here"
type = "text"
value = {
input
}
onChange = {
handleInput
}
/> <
div class = "input-group-append" >
<
button className = "input-group-append font-weight-bolder "
style = {
{
fontSize: 20
}
}
onClick = {
lp
} >
{
" "
} <
i class = "fas fa-plus-square fa-2x p-2" > < /i>{" "} <
/button> <
/div> <
/div> {
todos.map((x, index) => ( <
ol style = {
{
listStyle: "outside"
}
}
className = "container" >
<
li className = "font-weight-bolder table-bordered text-capitalize alert-secondary "
style = {
{
fontSize: 30,
textDecoration: line ? "line-through" : "none",
// backgroundColor: ll ? "Chartreuse" : "none",
}
} >
{
x
} <
i class = "fas fa-check-circle float-md-right text-success"
id = "ll"
onClick = {
markdone
} >
< /i>{" "} <
i class = "fas fa-trash-alt text-danger float-md-right"
onClick = {
() => handeldel(index)
} >
< /i> <
/li> <
/ol>
))
}
{ /* for future ref */ } {
/* <div >
{data.map((user) => (
<div className="user">{user.id + " " + user.name
}</div>
))}
</div> */
} <
/div>
I suppose using a Ref should do the trick, as Refs provide a way to access DOM nodes or React elements created in the render method.
Just put it on the element you'd like to style using jQuery and access it with RefName.current
IDs must be unique
You do not need jQuery, just delegation
Plain JS - there are other ways in React
I am assuming .input-group-append is the container
document.querySelector(".input-group-append").addEventListener("click",function(e) {
const tgt = e.target;
if (tgt.classList.contains("text-success")) {
tgt.closest("li").style.backgroundColor = "green";
}
})

reactjs setState before render on update

I have a react component and the requirement is to setState before render on update (url hash change). Below is code snippet:
componentConfig: function() {
.....
this.setState({rows: rows});
this.setState({loadMoreBtn: loadMoreIsVisible})
},
I was it working before, I was calling this method from getInitialState and it was working fine. getInitialState fires only once so I was not able to update it on url change. I tried various other inbuilt update methods such as componentWillReceiveProps but they are one step behind. Seems like render happens before this method gets called. I also tried to call it from render but obviously states get confused and it breaks.
As this image demonstrates, componentRillReceiveProps always behind render. I need something that fires before render each time on url update. Hope it makes sense.
or in other words, I would like to fire getInitialState on hash change.
var React = require('react'),
projectsData = require('./../projects'),
ProjectsRow = require('./projects_row'),
itemsInRow = 3,
noOfDefaultRows = 2,
projects = [],
currentProjects = [],
currentPageRows,
currentParamKey;
var Projects = React.createClass({
componentWillMount: function() {
this.componentConfig(this.props);
},
componentWillReceiveProps: function(nextProps){
this.componentConfig(nextProps);
},
componentConfig: function(props) {
var rows = [],
currentParamKey = 'projects',
loadMoreIsVisible = true,
i;
if(props.params.key) {
currentParamKey = props.params.key;
}
projects = projectsData.getByKey(currentParamKey);
projectsData.currentState[currentParamKey] = noOfDefaultRows;
currentProjects = projects.slice(); //Create a copy or array
noOfDefaultRows = projectsData.currentState[currentParamKey] || noOfDefaultRows;
for (i = 0; i < noOfDefaultRows; i++) {
if(currentProjects.length) {
rows.push(currentProjects.splice(0, itemsInRow));
}
}
currentProjects.length ? loadMoreIsVisible = true : loadMoreIsVisible = false;
this.setState({rows: rows});
this.setState({loadMoreBtn: loadMoreIsVisible})
console.log('Finished executing componentConfig and currentParamKey = ' ,currentParamKey);
},
loadMoreProjects: function(e) {
e.preventDefault();
var addRow = this.state.rows;
if(currentProjects.length) {
currentPageRows++;
addRow.push(currentProjects.splice(0, itemsInRow));
this.setState({rows: addRow});
}
if(!currentProjects.length) {
this.setState({loadMoreBtn: false})
}
},
render: function() {
console.log('Now in render and currentParamKey = ' ,currentParamKey);
var projectUrl;
//currentParamKey = this.props.params.key;
if(currentParamKey === 'projects') {
projectUrl = '#/project';
} else {
projectUrl = '#/project/' + currentParamKey
}
return (
< div className="projects">
< div className = "jumbotron" >
< div className = "container" >
< h1 > Projects < /h1>
< /div>
< /div>
< div className = "container" >
{this.state.rows.map(function(row, i) {
return <ProjectsRow url={projectUrl} row={row} key={i} />
}.bind(this))}
< /div>
< div className = "container text-center" >
<a id="loadMore" className= {this.state.loadMoreBtn ? 'linkStyle1' : 'hide'}
onClick = {this.loadMoreProjects}
role="button" > <i className="fa fa-angle-down"></i><span>Load More Projects</span>
</a>
<br />
<br />
<div className="contact-me-link">
<a className="linkStyle1" href="#/contact">
<i className="fa fa-angle-right"></i><span>Contact</span>
</a>
</div>
</div>
< /div>
);
}
});
module.exports = Projects;
The componentWillReceiveProps get new props as a parameter. The old props remain unchanged on this method execution, so you need to initialize your state based on this parameter. Try passing the props as a parameter to your componentConfig method:
componentWillMount: function() {
this.componentConfig(this.props);
},
componentWillReceiveProps: function(nextProps){
this.componentConfig(nextProps);
},
componentConfig: function(data) {
...
}

Categories