So this is very weird. First of all here's part of my code responsible for the timeouts:
constructor(props) {
super(props);
this.state = {
assignments: [],
answers: [],
uploadedFile: '',
showTimeoutModal: false,
};
this.onImageDrop = this.onImageDrop.bind(this);
this.startCountDown = this.startCountDown.bind(this);
this.handleTimeoutButtonClick = this.handleTimeoutButtonClick.bind(this);
}
componentDidMount() {
clearTimeout(this.state.timer);
firebase.database().ref('Works').child(this.props.assignmentId).once('value', () => {
// var startTime = r.val().acceptedDate.time;
var startTime = Time.generate().time; //todo change to db specific time
var waitingTime = Time.makeMinutes(5);
var endTime = Time.generate().time + waitingTime;
this.setState({timer: this.startCountDown(startTime, endTime)});
}).catch(e => console.log(e));
}
componentWillUnmount() {
clearTimeout(this.state.timer);
}
startCountDown(startTime, endTime) {
var timeout = setTimeout(function () {
var now = startTime;
console.log(endTime - now);
if (now >= endTime) {
clearTimeout(timeout);
console.log(true);
this.setState({showTimeoutModal: true});
} else {
now = Time.generate().time;
this.setState({timer: this.startCountDown(now, endTime)});
}
}.bind(this), 1000);
return timeout;
}
handleTimeoutButtonClick() {
var startTime = Time.generate().time;
var waitingTime = Time.makeMinutes(0.15);
var endTime = Time.generate().time + waitingTime;
this.setState({showTimeoutModal: false, timer: this.startCountDown(startTime, endTime)});
}
The way my project works is: A user picks a task to solve. I don't fully understand React yet so I don't fully know what's going on in the background. But the fact is, when I construct the component for the first time, all goes well. When I leave the component to a different route, all works as well. But if I recreate this component it all breaks.
Here's the full component:
import React from 'react';
import firebase from '../../firebase';
import {Col, Button, Modal} from 'react-bootstrap';
import Dropzone from 'react-dropzone';
import CancelAnswerButton from '../answer/CancelAnswerButton';
import FinishAnswerButton from '../answer/FinishAnswerButton';
import AnswerComponent from '../answer/AnswerComponent';
import MetaTags from 'react-meta-tags';
import {Redirect} from 'react-router';
import './AssignmentComponent.css';
import 'font-awesome/css/font-awesome.min.css';
import Time from '../globalMethods/Time';
export default class AssignmentComponent extends React.Component {
async componentWillMount() {
firebase.database().ref('Users').child(firebase.auth().currentUser.uid).on('value', snap => {
if (snap.val().assignedWork) {
this.setState({hasAssignedTask: true});
} else {
this.setState({hasAssignedTask: false});
}
});
const assignmentsRef = firebase.database().ref('Works').orderByChild('firebaseKey').equalTo(this.props.assignmentId);
await assignmentsRef.on('value', snapshot => {
var assignments = snapshot.val();
var newState = [];
for (let assignment in assignments) {
let currentAssignment = assignments[assignment];
newState.push({
id: assignment,
category: currentAssignment.category,
level: currentAssignment.level,
pointAmount: currentAssignment.pointAmount,
pointBoost: currentAssignment.pointBoost,
points: currentAssignment.pointBoost + currentAssignment.pointAmount,
photoURL: currentAssignment.photoURL,
workText: currentAssignment.workText,
answers: currentAssignment.answers,
});
}
var answers = [];
for (let answer in newState[0].answers) {
let currAns = newState[0].answers[answer];
answers.push({
id: answer,
textAnswer: currAns.textAnswer,
downloadURL: currAns.downloadURL,
})
}
this.setState({
assignments: newState,
answers: answers,
});
});
}
constructor(props) {
super(props);
this.state = {
assignments: [],
answers: [],
uploadedFile: '',
showTimeoutModal: false,
};
this.onImageDrop = this.onImageDrop.bind(this);
this.startCountDown = this.startCountDown.bind(this);
this.handleTimeoutButtonClick = this.handleTimeoutButtonClick.bind(this);
}
componentDidMount() {
clearTimeout(this.state.timer);
firebase.database().ref('Works').child(this.props.assignmentId).once('value', function () {
// var startTime = r.val().acceptedDate.time;
var startTime = Time.generate().time; //todo change to db specific time
var waitingTime = Time.makeMinutes(5);
var endTime = Time.generate().time + waitingTime;
this.setState({timer: this.startCountDown(startTime, endTime)});
}.bind(this)).catch(e => console.log(e));
}
componentWillUnmount() {
clearTimeout(this.state.timer);
}
startCountDown(startTime, endTime) {
var timeout = setTimeout(function () {
var now = startTime;
console.log(endTime - now);
if (now >= endTime) {
clearTimeout(timeout);
console.log(true);
this.setState({showTimeoutModal: true});
} else {
now = Time.generate().time;
this.setState({timer: this.startCountDown(now, endTime)});
}
}.bind(this), 1000);
return timeout;
}
handleTimeoutButtonClick() {
var startTime = Time.generate().time;
var waitingTime = Time.makeMinutes(0.15);
var endTime = Time.generate().time + waitingTime;
this.setState({showTimeoutModal: false, timer: this.startCountDown(startTime, endTime)});
}
async onImageDrop(files) {
await this.setState({
uploadedFile: files[0],
});
await AnswerComponent.handleImageSubmit(files[0], this.props);
}
render() {
return (
this.state.assignments.map(assignment => {
return (
this.state.hasAssignedTask ?
<section key={assignment.id} className='display-assignment'>
<MetaTags>
<title>Zadanie</title>
</MetaTags>
<div style={{height: '150px', background: '#fff', borderBottom: '2px solid #e0e0e0'}}>
<div className='container'>
<h4 style={{
paddingTop: '75px',
fontSize: '24px',
textAlign: 'left'
}}>Aktualnie rozwiązywane zadanie</h4>
</div>
</div>
<div className='wrapper show-grid task_info container-fluid task_solve_content'>
<Col xs={12} md={6}>
<img className='task_img' alt='' src={assignment.photoURL}/>
<p>{assignment.workText}</p>
</Col>
<Col xs={12} md={6}>
<div className='row task_info_content'>
<div className='col-sm-4 col-md-4 col-lg-4'>
<h3 className='text-center gradient_text'>
{assignment.category}
</h3>
<h4 className='text-center'>przedmiot</h4>
</div>
<div className='col-sm-4 col-md-4 col-lg-4'>
<h3 className='text-center gradient_text'>
{assignment.level}</h3>
<h4 className='text-center'>poziom</h4>
</div>
<div className='col-sm-4 col-md-4 col-lg-4'>
<h3 className='text-center gradient_text'>
{assignment.points}</h3>
<h4>punkty</h4>
</div>
</div>
<form encType="multipart/form-data">
<textarea placeholder='Wpisz rozwiązanie...' name="textAnswer" id="textAnswer"
style={{
width: '100%',
height: '80vh',
background: 'white',
color: 'black',
}}/>
<div style={{width: '100%', height: '60px', position: 'relative'}}>
<Button className="send_text_answer_button"
onClick={() => AnswerComponent.handleTextSubmit(this.props)}
style={{display: 'block'}}>
<span>Wyslij odpowiedź tekstową</span>
</Button>
</div>
<Dropzone
className='dropzone'
multiple={false}
accept='image/*'
style={{
backgroundColor: '#fff',
border: '1px solid #fff',
borderBottom: '2px solid #e0e0e0',
borderRadius: '4px',
width: '100%',
height: '300px',
marginBottom: '20px'
}}
onDrop={this.onImageDrop.bind(this)}>
<i className='fa fa-image' style={{
fontSize: '64px',
marginRight: '5px',
color: '#e0e0e0',
margin: 'auto',
marginTop: '120px'
}}/>
</Dropzone>
<h3 style={{display: 'none', color: 'red'}}
id='error'>Upewnij sie ze dodana jest przynajmniej jedna odpowiedz!</h3>
<div id='answers'>
<h3 style={{
fontSize: '18px',
fontWeight: 'lighter',
marginTop: '10px',
marginBottom: '10px'
}}>Odpowiedzi przeslane do tej pory</h3>
{
this.state.answers.map(answer => {
return (
<Col xs={12} md={12} key={answer.id}>
{
answer.textAnswer !== undefined &&
answer.textAnswer.length > 0 ?
<div className='task_info_text_answer'>
<p>{answer.textAnswer}</p>
</div>
:
<img className="your_answer_img"
src={answer.downloadURL}
alt=""/>
}
<br/>
<Button className="delete_answer_button"
onClick={() => AnswerComponent.removeAnswer(this.props, answer.id)}>
<span>Usuń odpowiedź</span>
</Button>
</Col>
)
})
}
</div>
<div className="row" style={{marginTop: '50px', marginBottom: '100px'}}>
<div className="col-sm-6 col-md-6 col-lg-6 cancel_answer_button_content"
style={{height: '200px'}}>
<CancelAnswerButton className="send_text_cancel_answer_button"
points={assignment.pointAmount + assignment.pointBoost}
assignmentId={assignment.id}/>
<div className="cancel_answer_button_info">
<p>Za anulowanie zadania grozi odjęcie punktów z rangi!</p>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-6 finish_answer_button_content"
style={{height: '200px'}}>
<FinishAnswerButton className="send_text_finish_button"
points={assignment.pointAmount + assignment.pointBoost}
assignmentId={assignment.id}/>
<div className="finish_answer_button_info">
<p>Upewnij się, że uczeń dostał wszystko, czego potrzebuje!</p>
</div>
</div>
</div>
</form>
</Col>
</div>
<Modal show={this.state.showTimeoutModal}>
<Modal.Header>
Hej! Rozwiazujesz dalej to zadanie?
</Modal.Header>
<Modal.Body>
<h3>Pozostaly czas: {this.state.timeLeft / 1000}</h3>
<Button className='btn btn-primary' onClick={this.handleTimeoutButtonClick}>Tak! Daj
mi jeszcze 5 minut! </Button>
</Modal.Body>
</Modal>
</section>
:
<Redirect to='/assignments'/>
)
})
)
}
}
So how can I make the timer run just once? What am I missing?
Logs in the console after the first click:
300000 AssignmentComponent.js:89
298999 AssignmentComponent.js:89
297997 AssignmentComponent.js:89
296996 AssignmentComponent.js:89
295994 AssignmentComponent.js:89
294992 AssignmentComponent.js:89
293990 AssignmentComponent.js:89
292988 AssignmentComponent.js:89
291986 AssignmentComponent.js:89
290984 AssignmentComponent.js:89
289983 AssignmentComponent.js:89
Logs after the second click:
300000 AssignmentComponent.js:89
298993 AssignmentComponent.js:89
298999 AssignmentComponent.js:89
297992 AssignmentComponent.js:89
297997 AssignmentComponent.js:89
296990 AssignmentComponent.js:89
296996 AssignmentComponent.js:89
295981 AssignmentComponent.js:89
295993 AssignmentComponent.js:89
294980 AssignmentComponent.js:89
294991 AssignmentComponent.js:89
Also I just realized, it doesn't only run twice. It starts a new timer, for each click. So if you recreate the component 5 times, there will be 5 timers running next to each other.
Here's what I did for my app.
Add a timer variable in the constructor, assign the setTimeout function to that timer variable wherever you want to start the timer. And clear timeout using that variable.
class TimerComponent extends Component {
constructor() {
super();
this.timer = null; //Timer initialisation
}
startTimeout = () => {
this.timer = setTimeout(() => {
}, 500)
}
componentDidMount() {
this.startTimeout();
}
componentWillUnmount() {
clearTimeout(this.timer); //Clearing timeout
this.timer = null;
}
}
Related
In my web app there is a hamburger making portion. The user starts off with a hamburger that has nothing but the top bun and the bottom bun. The user can choose from an assortment of "toppings" to add to the hamburger, and the changes are then shown. I have SVG files for the top bun, bottom bun, lettuce, patty, cheese, etc, and I wish to combine them (stack them) together dynamically in run time. I am currently able to stack them dynamically, but I wish to have certain SVG files overlap others.
I'll show you what I mean
Step 1
Above you can see that the hamburger starts off with nothing on it.
The react DOM looks something like
<img src = {topBunSVG}> </img>
<img src = {buttonBunSVG}> </img>
Let's click on tomato to add it.
Step 2
Now you can see that the tomato is now between the two buns. The DOM now looks like this
<img src = {topBunSVG}> </img>
<img src = {tomatoSVG}> </img>
<img src = {buttonBunSVG}> </img>
Same thing with adding a patty
Step 3
The DOM now looks like this
<img src = {topBunSVG}> </img>
<img src = {pattySVG}> </img>
<img src = {tomatoSVG}> </img>
<img src = {buttonBunSVG}> </img>
Lets try something more complex, like adding some cheese
Step 4
You can see that the cheese overlaps the other components
The DOM now looks like this
<img src = {topBunSVG}> </img>
<img src = {cheeseSVG}> </img>
<img src = {pattySVG}> </img>
<img src = {tomatoSVG}> </img>
<img src = {buttonBunSVG}> </img>
However, React actually displays it like this:
React Actual
Which is not what I want.
How would I make them stack correctly?
Also when I make the window smaller it looks like this:
Responsive component resized
Here's the relevant code for the component:
import React, { useEffect, useReducer, useState } from "react";
import "./makeHamburger.scss";
import bottomBun from "../../assets/foodParts/hamburger/bottombun.svg"
import cheese from "../../assets/foodParts/hamburger/cheese.svg"
import lettuce from "../../assets/foodParts/hamburger/lettuce.svg"
import patty from "../../assets/foodParts/hamburger/patty.svg"
import tomato from "../../assets/foodParts/hamburger/tomato.svg"
import topBun from "../../assets/foodParts/hamburger/topbun.svg"
const parts = {
topBun: {
view: topBun,
price: 1.5,
macros: {
carbs: 1,
protein: 0,
fiber: 0,
fat: 0,
},
},
bottomBun: {
view: bottomBun,
price: 1.5,
macros: {
carbs: 1,
protein: 0,
fiber: 0,
fat: 0,
},
},
patty: {
view: patty,
price: 5,
macros: {
carbs: 0,
protein: 1,
fiber: 0,
fat: 0,
},
},
cheese: {
view: cheese,
price: 1,
macros: {
carbs: 0,
protein: 0,
fiber: 0,
fat: 1,
},
},
lettuce: {
view: lettuce,
price: 2,
macros: {
carbs: 0,
protein: 0,
fiber: 1,
fat: 0,
},
},
tomato: {
view: tomato,
price: 2,
macros: {
carbs: 0,
protein: 0,
fiber: 1,
fat: 0,
},
},
}
const initialState = { contents: [parts.topBun, parts.bottomBun] };
function reducer(state, action) {
let tmp = JSON.parse(JSON.stringify(state))
switch (action.type) {
case "addPatty":
tmp.contents.splice(1, 0, parts.patty)
return {
contents: tmp.contents
}
break;
case "addCheese":
tmp.contents.splice(1, 0, parts.cheese)
return {
contents: tmp.contents
}
break;
case "addLettuce":
tmp.contents.splice(1, 0, parts.lettuce)
return {
contents: tmp.contents
}
break;
case "addTomato":
tmp.contents.splice(1, 0, parts.tomato)
return {
contents: tmp.contents
}
break;
case "deletePartsById":
if ((action.payload == 0) || (action.payload == (state.contents.length - 1))) {
return state;
}
tmp.contents.splice(action.payload, 1)
return {
contents: tmp.contents
}
break;
default:
break;
}
}
const MakeHamburger = ({recieveFood}) => {
const [state, dispatch] = useReducer(reducer, initialState);
const getTotalPrice = () => {
let price = 0;
state.contents.forEach((item,index) => {
price += item.price
})
return price;
}
const getTotalCarbs = () => {
let carbs = 0;
state.contents.forEach((item,index) => {
carbs += item.macros.carbs
})
return carbs;
}
const getTotalFat = () => {
let fat = 0;
state.contents.forEach((item,index) => {
fat += item.macros.fat
})
return fat;
}
const getTotalFiber = () => {
let fiber = 0;
state.contents.forEach((item,index) => {
fiber += item.macros.fiber
})
return fiber;
}
const getTotalProtein = () => {
let protein = 0;
state.contents.forEach((item,index) => {
protein += item.macros.protein
})
return protein;
}
const getTotalMacros = () => {
return{
carbs: getTotalCarbs(),
protein: getTotalProtein(),
fiber: getTotalFiber(),
fat: getTotalFat(),
}
}
return (
<div id="food" className="container">
<h1>Let's make Max a burger</h1>
<p>Select what goes on the burger</p>
<div className="container">
<div className="row justify-content-center">
<div className="col-6">
<div className="row">
<div className="col">
<button className="transparentButton" onClick={() => { dispatch({ type: 'addCheese' }) }}>
<img className="selectPartImg" src={cheese}></img>
</button>
</div>
<div className="col">
<p>Cheese</p>
</div>
</div>
<div className="row mt-3">
<div className="col">
<button className="transparentButton" onClick={() => { dispatch({ type: 'addLettuce' }) }}>
<img className="selectPartImg" src={lettuce}></img>
</button>
</div>
<div className="col">
<p>Lettuce</p>
</div>
</div>
<div className="row mt-3">
<div className="col">
<button className="transparentButton" onClick={() => { dispatch({ type: 'addPatty' }) }}>
<img className="selectPartImg" src={patty}></img>
</button>
</div>
<div className="col"><p>Patty</p></div>
</div>
<div className="row mt-3">
<div className="col">
<button className="transparentButton" onClick={() => { dispatch({ type: 'addTomato' }) }} >
<img className="selectPartImg" src={tomato}></img>
</button>
</div>
<div className="col"><p>Tomato</p></div>
</div>
</div>
<div className="col-6 ">
{state.contents.map(function (part, i) {
return <div className="row no-gutters">
<div className="col">
<button className="transparentButton" onClick={() => { dispatch({ type: 'deletePartsById', payload: i }) }} >
<img className="partImg" src={part.view}></img>
</button>
</div>
</div>
})}
<h1>Total price: {getTotalPrice()}</h1>
<p>({getTotalCarbs() ? getTotalCarbs() + " carbs" : null}{getTotalFat() ? ", " + getTotalFat() + " fat" : null}
{getTotalFiber() ? ", " + getTotalFiber() + " fiber" : null}{getTotalProtein() ? ", " + getTotalProtein() + " protein" : null})</p>
<button onClick = {() => {recieveFood(getTotalMacros())}}>Feed this burger</button>
</div>
</div>
</div>
</div>
);
};
export default MakeHamburger;
If the height of one layer is not identical with the height of the image, you need to specify the height of each individual layer. (The information about the desired height just does not exist in the image).
(A) You can create SVG data dynamically:
import React, { useEffect, useState } from 'react';
export const Patties = (props) => {
return (<>
{ [...props.patties].reverse().map( function(patty, index){
return <>
<path d={ 'M 0 ' + (60 - index * 20) + ' h 100 l -50 30 z' } fill={ patty.color } />
</>
})}
</>);
};
export const Hamburger = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={ 200 }
height={ 200 }
viewBox={ [ 0, 0, 100, 100 ].join(' ') }
>
<Patties patties={[
{ color: '#fa0' },
{ color: '#0a0' },
{ color: '#c20' },
]}/>
</svg>
);
};
(B) You can use absolute positioning:
export const Hamburger2 = (props)=>{
const patties = [
{ url: imgUrlA },
{ url: imgUrlB },
{ url: imgUrlC },
];
return (<div style={{ position: 'relative' }}>
{ patties.reverse().map( function(patty, index){
return <img
src={ patty.url }
style={{ position: 'absolute', top: 60 - index * 20 }}
/>;
})}
</div>);
};
Thanks for the suggested answers everyone, I managed to solve the program with this code snippet:
const drawParts = () => {
let parts = [];
let positionOffSet = 0;
let z = state.contents.length;
state.contents.map(function (part, index) {
parts.push(
<div className="row no-gutters" style={{ position: 'relative', zIndex: z, top: positionOffSet }}>
<div className="col">
<button className="transparentButton" onClick={() => { dispatch({ type: 'deletePartsById', payload: index }) }} >
<img className="partImg" src={part.view}></img>
</button>
</div>
</div>
)
if(part.view == cheese){
positionOffSet -= 90;
z--;
}
if(part.view == lettuce){
positionOffSet -= 15;
z--;
}
})
return parts;
}
Basically having a global offset and z-value variable that gets incremented everytime a cheese or lettuce shows up. The offset and z-value is applied to all elements.
I'm new to React and basically I'm trying to update a parent App.js components' state and its child components (Team.js and Player.js) props at once. Currently only the parent components' state is being updated. I will try to explain it better with a step by step.
Here I have a parent component App.js
export default function App() {
const teams = [
{
Name: "Chicago Bulls",
Players: ["Michael Jordan", "Dennis Rodman", "Scottie Pippen"],
Championships: 6
},
{
Name: "Golden State Warriors",
Players: ["Stephen Curry", "Klay Thompson", "Draymond Green"],
Championships: 5
},
{
Name: "Los Angeles Lakers",
Players: ["Kobe Bryant", "LeBron James", "Magic Johnson"],
Championships: 17
}
];
const [selectedTeam, setSelectedTeam] = useState({});
const players = [
{ Name: "LeBron James", MVPs: 4 },
{ Name: "Michael Jordan", MVPs: 5 },
{ Name: "Stephen Curry", MVPs: "2" }
];
const [selectedPlayer, setSelectedPlayer] = useState({});
const [modalContent, setModalContent] = useState(false);
const clickedComponent = useRef(null);
const [show, setShowModal] = useState(false);
const showModal = () => {
setShowModal(true);
};
const hideModal = () => {
setShowModal(false);
};
const handleModalContent = (clicked) => {
switch (clicked) {
case "Team":
clickedComponent.current = (
<Team
teams={teams}
selectedTeam={selectedTeam}
setSelectedTeam={setSelectedTeam}
/>
);
break;
case "Player":
clickedComponent.current = (
<Player
players={players}
selectedPlayer={selectedPlayer}
setSelectedPlayer={setSelectedPlayer}
/>
);
break;
default:
clickedComponent.current = null;
break;
}
};
return (
<div className="App" style={{ justifyContent: "space-evenly" }}>
<div
style={{
justifyContent: "center",
width: "100%",
display: "flex",
flexWrap: "wrap",
margin: "40px 0px 0px 0px"
}}
>
<div
className="table-cell"
onClick={() => {
handleModalContent("Team");
setModalContent(true);
showModal();
}}
>
<div className="table-cell-text">Click to access Team component</div>
</div>
<div
className="table-cell"
onClick={() => {
handleModalContent("Player");
setModalContent(true);
showModal();
}}
>
<div className="table-cell-text">
Click to access Player component
</div>
</div>
</div>
<h3 style={{ marginTop: "30px" }}>
The last selected team was: {selectedTeam.Name}
<br />
The last selected player was: {selectedPlayer.Name}
</h3>
<Modal show={show} modalClosed={hideModal}>
{(modalContent && clickedComponent.current) || null}
</Modal>
</div>
);
}
This component has two arrays of objects (teams and players) that is sent to Team and Player components, respectively, as props. Team also receives selectedTeam and setSelectedTeam as props. Player receives selectedPlayer and setSelectedPlayer. Both components have a Modal component and a select input. In the Team component, the user will select a team and them it will be displayed the selected teams' players, while in the Player component a player will be select and them it will be displayed the amount of MVP of the selected player.
Team.js
const Team = (props) => {
return (
<div style={{ position: "relative", margin: "0 auto", width: "10em" }}>
<h3>Select a team</h3>
<div className="input-group col">
<select
onChange={(e) => {
if (e === "") props.setSelectedTeam({});
else {
let foundTeam = props.teams.find(
(team) => team.Name === e.target.value
);
props.setSelectedTeam(foundTeam);
}
}}
>
<option value="">Select a team...</option>
{props.teams.map((team) => (
<option key={team.Name} value={team.Name}>
{team.Name}
</option>
))}
</select>
</div>
{Object.keys(props.selectedTeam).length > 0 ? (
<div>
<h3>{props.selectedTeam.Name} players: </h3>
<br />
{props.selectedTeam.Players.map((player, index) => (
<div key={index}>{player}</div>
))}
</div>
) : null}
</div>
);
};
export default Team;
Player.js
const Player = (props) => {
return (
<div style={{ position: "relative", margin: "0 auto", width: "10em" }}>
<h3>Select a player</h3>
<div className="input-group col">
<select
onChange={(e) => {
if (e === "") props.setSelectedPlayer({});
else {
let foundPlayer = props.players.find(
(player) => player.Name === e.target.value
);
props.setSelectedPlayer(foundPlayer);
}
}}
>
<option value="">Select a player...</option>
{props.players.map((player) => (
<option key={player.Name} value={player.Name}>
{player.Name}
</option>
))}
</select>
</div>
{Object.keys(props.selectedPlayer).length > 0 ? (
<div>
<h3>
{props.selectedPlayer.Name} MVPs: {props.selectedPlayer.MVPs}
</h3>
</div>
) : null}
</div>
);
};
export default Player;
So my problem is, if I select an option in the child components, they don't receive the updated selected option (I mean selectedTeam for Team component and selectedPlayer for Player component) immediatelly but in the father component App I have them updated. So, if I want them to get updated, I need to select an option, close the modal and reopen them again.
For example, here I have App.js visual:
If I open Team.js and select a team, I have selectedTeam updated in App.js but not in Team.js:
So, if I close the modal and reopen Team component again, then I have props.selectedTeam updated. So I have the following:
I have the same problem with Player component, but in this case regarding props.selectedPlayer
How can I make it work properly, I mean, how can I have props.selectedTeam and props.selectedPlayer updated at once in App such as in Team and Player, respectively? Thank you!
CodeSandbox
https://codesandbox.io/s/young-sun-gs117?file=/src/Team.js:51-1127
Here is what you need to do, I refactored your code and add some comments on that so you know what I did that. one thing to remember is that almost you don't want to store a component in a state.
export default function App() {
const teams = [
{
Name: "Chicago Bulls",
Players: ["Michael Jordan", "Dennis Rodman", "Scottie Pippen"],
Championships: 6,
},
{
Name: "Golden State Warriors",
Players: ["Stephen Curry", "Klay Thompson", "Draymond Green"],
Championships: 5,
},
{
Name: "Los Angeles Lakers",
Players: ["Kobe Bryant", "LeBron James", "Magic Johnson"],
Championships: 17,
},
];
const players = [
{ Name: "LeBron James", MVPs: 4 },
{ Name: "Michael Jordan", MVPs: 5 },
{ Name: "Stephen Curry", MVPs: "2" },
];
// This makes typo mistake less and will give you auto complete option
const componentType = {
team: "Team",
player: "Player",
};
const [selectedTeam, setSelectedTeam] = useState({});
const [selectedPlayer, setSelectedPlayer] = useState({});
// the modalContent state and show state are doing the same thing so one of them is unneccessary
const [show, setShowModal] = useState(false);
const [clickedComponent, setClickedComponent] = useState("");
const showModal = () => {
setShowModal(true);
};
const hideModal = () => {
setShowModal(false);
};
const handleModalContent = (clicked) => {
setClickedComponent(clicked);
};
return (
<div className="App" style={{ justifyContent: "space-evenly" }}>
<div
style={{
justifyContent: "center",
width: "100%",
display: "flex",
flexWrap: "wrap",
margin: "40px 0px 0px 0px",
}}
>
<div
className="table-cell"
onClick={() => {
handleModalContent(componentType.team);
showModal();
}}
>
<div className="table-cell-text">Click to access Team component</div>
</div>
<div
className="table-cell"
onClick={() => {
handleModalContent(componentType.player);
showModal();
}}
>
<div className="table-cell-text">
Click to access Player component
</div>
</div>
</div>
<h3 style={{ marginTop: "30px" }}>
The last selected team was: {selectedTeam.Name}
<br />
The last selected player was: {selectedPlayer.Name}
</h3>
<Modal show={show} modalClosed={hideModal}>
{clickedComponent === componentType.player ? (
<Player
players={players}
selectedPlayer={selectedPlayer}
setSelectedPlayer={setSelectedPlayer}
/>
) : clickedComponent === componentType.team ? (
<Team
teams={teams}
selectedTeam={selectedTeam}
setSelectedTeam={setSelectedTeam}
/>
) : null}
</Modal>
</div>
);
}
The way I know how is to just use Hooks useState and useEffect and just update that state on select change.
Hope the below example for Player helps (worked in your code sandbox unless I am not answering your question):
import React, { useState, useEffect } from "react";
import "./styles.css";
const Player = (props) => {
const [test, setTest] = useState("");
useEffect(() => {
console.log("props:", props);
setTest(props.selectedPlayer);
}, [props]);
return (
<div style={{ position: "relative", margin: "0 auto", width: "10em" }}>
<h3>Select a player</h3>
<div className="input-group col">
<select
value={props.selectedPlayer}
onChange={(e) => {
if (e === "") props.setSelectedPlayer({});
else {
let foundPlayer = props.players.find(
(player) => player.Name === e.target.value
);
props.setSelectedPlayer(foundPlayer);
setTest(foundPlayer);
}
}}
>
<option value="">Select a player...</option>
{props.players.map((player) => (
<option key={player.Name} value={player.Name}>
{player.Name}
</option>
))}
</select>
</div>
<h3>{test.Name} MVPs: {test.MVPs}</h3>
{/* {Object.keys(props.selectedPlayer).length > 0 ? (
<div>
<h3>
{props.selectedPlayer.Name} MVPs: {props.selectedPlayer.MVPs}
</h3>
</div>
) : null} */}
</div>
);
};
export default Player;
EDIT - I'm now getting TypeError: this.state.historyIn.map is not a function from the below code.
I wonder if someone can help. I've challenged myself to create a sign in application to teach myself React.
I'm looking for a way to append the 'In' & 'Out' History each time the Sign-In or Out events are triggered. I've got as far as replacing the state each time the button is clicked but I'm a bit stuck now.
My thoughts were to create an array or object and append to this each time the button is clicked & then display this by mapping over it, but I'm not sure how I would handle this for EACH person.
I hope this makes sense.
The full project is here if you need to check any other components - https://github.com/samakers/digi-sign-in/tree/master/client/src/components
import React, { Component } from "react";
import { Button, Collapse } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import "../style/row.css";
class Row extends Component {
constructor() {
super();
this.state = {
signIn: "Sign-in",
signOut: "Sign-out",
disabledIn: false,
disabledOut: true,
online: "",
offline: "",
open: false,
historyIn: [],
historyOut: []
};
}
signIn() {
let today = new Date();
let time =
today.getHours() + ":" + today.getMinutes();
this.setState({ signIn: time, historyIn: time });
this.state.historyIn.push(time);
return time;
}
signOut() {
let today = new Date();
let time =
today.getHours() + ":" + today.getMinutes();
this.setState({ signOut: time, historyOut: time});
return time;
}
setStatusOnline() {
this.setState({
online: "animated",
offline: "green",
disabledOut: false,
signOut: "Sign-Out"
});
}
setStatusOffline() {
this.setState({
offline: "red",
disabledIn: false,
signIn: "Sign-In"
});
}
showHistory() {
this.setState(prevState => ({
open: !prevState.open
}));
}
render() {
const historyIn = this.state.historyIn.map(timeIn => {
return (
<div>
In: {timeIn}
</div>
)
});
return (
<React.Fragment>
<tr>
<td>{this.props.person.StaffID}</td>
<td>
<span
style={{ backgroundColor: this.state.offline }}
className={this.state.online}
></span>
{this.props.person.StaffName}
<Button
size="sm"
style={{ marginLeft: "20px" }}
onClick={() => this.showHistory()}
variant="info"
>
History
</Button>
<Collapse in={this.state.open}>
<div>
{historyIn}
<br />
Out: {this.state.historyOut}
</div>
</Collapse>
</td>
<td>
<Button
disabled={this.state.disabledIn}
onClick={() => {
this.signIn();
this.setState({ disabledIn: true });
this.setStatusOnline();
}}
variant="success"
>
{this.state.signIn}
</Button>
</td>
<td>
<Button
disabled={this.state.disabledOut}
variant="danger"
onClick={() => {
this.signOut();
this.setState({ disabledOut: true });
this.setStatusOffline();
}}
>
{this.state.signOut}
</Button>
</td>
</tr>
</React.Fragment>
);
}
}
export default Row;
Hi update historyIn using setState inSignIn()
this.setState({ historyIn: this.state.historyIn });
see code below if this what you want
https://codesandbox.io/s/silly-jepsen-xr9ql
I have created a header component in REACT and the Header uses flexbox for layout. Now I need to make the Header stick. I tried using position: fixed but that messes up the flexbox styling. Does anyone have any ideas on how I can solve this?
The code is shown below. The height of the header varies depending on whether mobile menu is displayed or not.
Thanks.
import React, { Component } from 'react';
import Logo from './Logo'
import logo from '../images/transparent.png';
import MenuItem from "./MenuItem";
import MenuItemBurger from "./MenuItemBurger";
class Header extends Component {
constructor(props) {
super(props);
this.headerStyle = {
height: 'auto',
padding: 10,
display: 'flex',
justifyContent: 'space-between',
zIndex: 10,
backgroundColor: 'white'
};
this.burgerMenuIconStyle = {
color: '#757c8b',
};
this.mobileMenuStyle = {
zIndex: 20,
justifyContent: 'center',
alignItems: 'center'
};
this.state = {
windowWidth: window.innerWidth,
mobileNavVisible: false,
navItems : [
{text: 'Home', selected: true, id:'home'},
{text: 'Our Services', selected: false, id: 'services'},
{text: 'Contact Us', selected: false, id: 'contact'}
]
};
}
handleResize() {
this.setState({windowWidth: window.innerWidth});
}
componentDidMount() {
window.addEventListener('resize', this.handleResize.bind(this));
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize.bind(this));
}
toggleMenuOnClick() {
if (this.state.mobileNavVisible) {
this.setState({...this.state, mobileNavVisible: false});
}
else {
this.setState({...this.state, mobileNavVisible: true});
}
}
renderMobileHeader() {
const navItemsMappedBurger = this.state.navItems.map(item => <MenuItemBurger text={item.text} id={item.id} onClick={() => this.toggleMenuOnClick()}/>);
if (this.state.mobileNavVisible) {
return (
<div className="mobileHeader">
<div style={this.headerStyle}>
<Logo logo={logo}/>
<i className="fa fa-bars fa-2x" onClick={() => this.toggleMenuOnClick()} style={this.burgerMenuIconStyle}></i>
</div>
<hr></hr>
<div className="navItems" style={this.mobileMenuStyle}> {navItemsMappedBurger} </div>
</div>
)
}
return (
<div className="mobileHeader">
<div style={this.headerStyle}>
<Logo logo={logo}/>
<i className="fa fa-bars fa-2x" onClick={() => this.toggleMenuOnClick()} style={this.burgerMenuIconStyle}></i>
</div>
</div>
)
}
renderWideHeader() {
const navItemsMapped = this.state.navItems.map(item => <MenuItem text={item.text} id={item.id}/>);
return (
<div className="wideHeader" style={this.headerStyle}>
<Logo logo={logo}/>
<div className="navItems">{navItemsMapped}</div>
</div>
)
}
render() {
if (this.state.windowWidth < 1000) {
return (
this.renderMobileHeader()
)
}
return (
this.renderWideHeader()
);
}
};
export default Header;
try to add a container to deal with the fixed position.
Something like this:
this.fixedHeader = {
position: fixed;
width: 100%;
top: 0;
left: 0;
z-index: 1000;
}
const FixedHeader = (children) => {
return <div style={this.fixedHeader}>{children}</div>
}
render() {
if (this.state.windowWidth < 1000) {
return <FixedHeader>{ this.renderMobileHeader() }</FixedHeader>
}
return <FixedHeader>{ this.renderWideHeader() }</FixedHeader>
}
I have asked this question many times here but didn't get the answer properly or any complete result. i need to validate my input or i can do that when my input tag is empty or when there is no number in the input, the alert should pop up. I tried for an alert but don't know why it is not working. Please help i'm stuck in this since a week.
Code:
<script type="text/jsx">
var styles = {
margin: '2em auto',
width: '300px',
height: '300px',
backgroundColor: '#DD4814',
color: '#ffffff',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-around'
};
var inputs = {
position: 'relative',
bottom: '17%',
left: '20%'
}
var btns = {
position: 'relative',
bottom: '7%'
}
var btn = {
backgroundColor: '#ffffff',
color: '#000000',
borderColor: '#DEB887',
borderRadius: '0.4em',
cursor: 'pointer',
margin: '0 1em',
padding: '0.5em',
display: 'inline-block'
}
var required = true;
class Timer extends React.Component {
constructor (props) {
super(props)
this.state = {count: 0, customNumber: 0}
}
handleChange (e) {
this.setState({ customNumber: e.target.value});
}
componentWillUnmount () {
clearInterval(this.timer)
}
tick () {
if (this.state.customNumber) {
this.setState({
count: (this.state.customNumber--)
})
if (this.state.customNumber <= 0) {
this.setState({ count: 0})
clearInterval(this.timer)
this.setState({ disabled: false })
}
} else {
this.setState({count: (this.state.count - 1)})
}
}
display () {
return ('0' + this.state.count % 100).slice(-2)
}
startTimer () {
if ((this.state.inputValue == " ") && isNaN(this.state.inputValue))
{
alert("Please give some value in number");
}
clearInterval(this.timer)
this.timer = setInterval(this.tick.bind(this), 1000)
this.setState({ disabled: true })
}
stopTimer () {
clearInterval(this.timer)
}
resetTimer () {
clearInterval(this.timer)
this.setState({count: 0})
this.setState({ disabled: false })
}
render () {
return (
<div style={styles} className='timer'>
<h1 style={{fontSize: '4em'}}>{this.display()}</h1>
<div className="input_text" style={inputs}>
<label htmlFor="custom_number">Enter number to start timer</label>
<input type="text" name="custom_number" id="custom_number" required={required} value={this.state.inputValue} onChange={this.handleChange.bind(this)} disabled={this.state.disabled} placeholder="Enter b/w 1-100" />
</div>
<div style={btns} className="buttons">
<button style={btn} type="button" name="start_btn" id="start_btn" onClick={this.startTimer.bind(this)}>Start</button>
<button style={btn} type="button" name="stop_btn" id="stop_btn" onClick={this.stopTimer.bind(this)}>Pause</button>
<button style={btn} type="button" name="reset_btn" id="reset_btn" onClick={this.resetTimer.bind(this)}>Stop</button>
</div>
</div>
)
}
}
ReactDOM.render(
<Timer />,
document.getElementById('root')
)
</script>
<div id="root"></div>
First thing, using alerts is not the best way to get the user's attention. console.log() is much more effective if all you are doing is debugging your code
Second thing, you are asking for alerts in a timer loop - you could end up with hundreds of alerts this way (refer to my first point)
Third thing, you are checking the value of this.state.inputValue and comparing it to a string containing one space (" ") which doesn't seem right
Fourth thing, you are setting the value of your input field with this:
value={this.state.inputValue}
This basically means the value of the field is set and can't be changed. You probably want to use defaultValue instead
Five, and I'm stopping here, your handleChange method doesn't even set the state, so you will never get what you want anyway.