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
Related
In typescript code I tried to disable the "-" button in the Shopping Cart if amount of item = 1.
Currently, when clicking the "-" button we can reach amount 0, -1, -2 etc. I tried to disable it using jQuery and JS code, but in result received only mistake.
Could you please help with answer how to make it work properly?
import {
CardContent,
CardActions,
Button,
Card,
TextField,
} from '#mui/material'
import { Component } from 'react'
import './ProductListItem.scss'
type Props = {
title: string
description: string
type: string
capacity: string
price: number
image: string
}
type State = {
count: number
}
class ProductListItem extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
count: 1,
}
this.onIncrementClick = this.onIncrementClick.bind(this)
this.onDecrementClick = this.onDecrementClick.bind(this)
}
onIncrementClick() {
this.setState((prevState) => ({
count: prevState.count + 1,
}))
}
onDecrementClick() {
this.setState((prevState) => ({
count: prevState.count - 1,
}))
}
render() {
const { title, description, type, capacity, price, image } = this.props
return (
<Card variant="outlined" className="product">
<CardContent>
<div className="product-image">
<img src={image} alt="" />
</div>
<div className="product-title">{title}</div>
<div className="product-description">{description}</div>
<div className="product-features">Type:{type}</div>
<div className="product-features">Capacity:{capacity}</div>
<div className="product-price">Price:{price}</div>
<div className="product-quantity">
<Button
variant="outlined"
onClick={this.onDecrementClick}
>
-
</Button>
<TextField size="small" value={this.state.count} />
<Button
variant="outlined"
onClick={this.onIncrementClick}
>
+
</Button>
</div>
</CardContent>
<CardActions className="btns-wrap">
<Button variant="outlined">Add to cart</Button>
</CardActions>
</Card>
)
}
}
export default ProductListItem
Expacting to receive result like code below, but in TS
let input = document.querySelector(".input");
let button = document.querySelector(".button");
button.disabled = true;
input.addEventListener("change", stateHandle);
function stateHandle() {
if (document.querySelector(".input").value === "") {
button.disabled = true;
} else {
button.disabled = false;
}
}
No need to add event listeners and such. You can do this by using the disabled prop as written on the MUI Button props documentation and passing the count state as a check.
<Button
variant="outlined"
onClick={this.onDecrementClick}
disabled={this.state.count === 1}
>
-
</Button>
I use react js to create a staycation website, when I want to display the InputNumber and InputDate components I experience an error like the title above, in the componentDidUpdate section, I have tried tweaking the code but it hasn't worked, but when I omit the componentDidUpdate part, the inputdate and inputnumber components run.
this is the input component code Number I have tried the input component works well,:
import React from "react";
import propTypes from "prop-types";
import "./index.scss";
export default function Number(props) {
const {
value,
placeholder,
name,
min,
max,
prefix,
suffix,
isSuffixPlural,
} = props;
const onChange = (e) => {
let value = String(e.target.value);
if (+value <= max && +value >= min) {
props.onChange({
target: {
name: name,
value: +value,
},
});
}
};
const minus = () => {
value > min &&
onChange({
target: {
name: name,
value: +value - 1,
},
});
};
const plus = () => {
value < max &&
onChange({
target: {
name: name,
value: +value + 1,
},
});
};
return (
<div className={["input-number mb-3", props.outerClassName].join(" ")}>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text minus" onClick={minus}>
-
</span>
</div>
<input
min={min}
max={max}
name={name}
pattern="[0-9]*"
className="form-control"
placeholder={placeholder ? placeholder : "0"}
value={`${prefix}${value}${suffix}${
isSuffixPlural && value > 1 ? "s" : ""
}`}
onChange={onChange}
/>
<div className="input-group-append">
<span className="input-group-text plus" onClick={plus}>
+
</span>
</div>
</div>
</div>
);
}
Number.defaultProps = {
min: 1,
max: 1,
prefix: "",
suffix: "",
};
Number.propTypes = {
value: propTypes.oneOfType([propTypes.string, propTypes.number]),
onChange: propTypes.func,
placeholder: propTypes.string,
isSuffixPlural: propTypes.bool,
outerClassName: propTypes.string,
};
and this is my input date component code I have tried the input component works well, :
import React, { useState, useRef, useEffect } from "react";
import propTypes from "prop-types";
import { DateRange } from "react-date-range";
import "./index.scss";
import "react-date-range/dist/styles.css"; // main css file
import "react-date-range/dist/theme/default.css"; // theme css file
import formatDate from "utils/formatDate";
import iconCalendar from "assets/images/icon/icon-calendar.svg";
export default function Date(props) {
const { value, placeholder, name } = props;
const [isShowed, setIsShowed] = useState(false);
const datePickerChange = (value) => {
const target = {
target: {
value: value.selection,
name: name,
},
};
props.onChange(target);
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
});
const refDate = useRef(null);
const handleClickOutside = (event) => {
if (refDate && !refDate.current.contains(event.target)) {
setIsShowed(false);
}
};
const check = (focus) => {
focus.indexOf(1) < 0 && setIsShowed(false);
};
const displayDate = `${value.startDate ? formatDate(value.startDate) : ""}${
value.endDate ? " - " + formatDate(value.endDate) : ""
}`;
return (
<div
ref={refDate}
className={["input-date mb-3", props.outerClassName].join(" ")}
>
<div className="input-group">
<div className="input-group-prepend bg-gray-900">
<span className="input-group-text">
<img src={iconCalendar} alt="icon calendar" />
</span>
</div>
<input
readOnly
type="text"
className="form-control"
value={displayDate}
placeholder={placeholder}
onClick={() => setIsShowed(!isShowed)}
/>
{isShowed && (
<div className="date-range-wrapper">
<DateRange
editableDateInputs={true}
onChange={datePickerChange}
moveRangeOnFirstSelection={false}
onRangeFocusChange={check}
ranges={[value]}
/>
</div>
)}
</div>
</div>
);
}
Date.propTypes = {
value: propTypes.object,
onChange: propTypes.func,
placeholder: propTypes.string,
outerClassName: propTypes.string,
};
I have tried the inpudate component to run well, as well as the input number, but if I combine these components I have an error did i miss something, and I tried to combine these components on the bookingform page but when I tried on the browser I experienced the above error.
My code is in the Booking Form:
import React, { Component } from "react";
import propTypes from "prop-types";
import Button from "elements/Button";
import { InputNumber, InputDate } from "elements/Form";
export default class BookingForm extends Component {
constructor(props) {
super(props);
this.state = {
data: {
duration: 1,
date: {
startDate: new Date(),
endDate: new Date(),
key: "selection",
},
},
};
}
updateData = (e) => {
this.setState({
...this.state,
data: {
...this.state.data,
[e.target.name]: e.target.value,
},
});
};
componentDidUpdate(prevProps, prevState) {
const { data } = this.state;
if (prevState.data.date !== data.date) {
const startDate = new Date(data.date.startDate);
const endDate = new Date(data.date.endDate);
const countDuration = new Date(endDate - startDate).getDate();
this.setState({
data: {
...this.state.data,
duration: countDuration,
},
});
}
if (prevState.data.duration !== data.duration) {
const startDate = new Date(data.date.startDate);
const endDate = new Date(
startDate.setDate(startDate.getDate() + +data.duration - 1)
);
this.setState({
...this.state,
data: {
...this.state.data,
date: {
...this.state.data.date,
endDate: endDate,
},
},
});
}
}
startBooking = () => {
const { data } = this.state;
this.props.startBooking({
_id: this.props.itemDetails._id,
duration: data.duration,
date: {
startDate: data.date.startDate,
endDate: data.date.endDate,
},
});
this.props.history.push("/checkout");
};
render() {
const { data } = this.state;
const { itemDetails } = this.props;
console.log(this.state);
return (
<div className="card bordered" style={{ padding: "60px 80px" }}>
<h4 className="mb-3">Start Booking</h4>
<h5 className="h2 text-teal mb-4">
${itemDetails.price}{" "}
<span className="text-gray-500 font-weight-light">
per {itemDetails.unit}
</span>
</h5>
<label htmlFor="duration">How long you will stay?</label>
<InputNumber
max={30}
suffix={" night"}
isSuffixPlural
onChange={this.updateData}
name="duration"
value={data.duration}
/>
<label htmlFor="date">Pick a date</label>
<InputDate onChange={this.updateData} name="date" value={data.date} />
<h6
className="text-gray-500 font-weight-light"
style={{ marginBottom: 40 }}
>
You will pay{" "}
<span className="text-gray-900">
${itemDetails.price * data.duration} USD
</span>{" "}
per{" "}
<span className="text-gray-900">
{data.duration} {itemDetails.unit}
</span>
</h6>
<Button
className="btn"
hasShadow
isPrimary
isBlock
onClick={this.startBooking}
>
Continue to Book
</Button>
</div>
);
}
}
BookingForm.propTypes = {
itemDetails: propTypes.object,
startBooking: propTypes.func,
};
I encountered this error and tried to fix it, but couldn't find a solution to the problem
I use react js to create a staycation website, when I want to display the InputNumber and InputDate components I experience an error like the title above, in the componentDidUpdate section, I have tried tweaking the code but it hasn't worked, but when I omit the componentDidUpdate part, the inputdate and inputnumber components run.
I encountered this error and tried to fix it, but couldn't find a solution to the problem
I use react js to create a staycation website, when I want to display the InputNumber and InputDate components I experience an error like the title above, in the componentDidUpdate section, I have tried tweaking the code but it hasn't worked, but when I omit the componentDidUpdate part, the inputdate and inputnumber components run.
I encountered this error and tried to fix it, but couldn't find a solution to the problem
I use react js to create a staycation website, when I want to display the InputNumber and InputDate components I experience an error like the title above, in the componentDidUpdate section, I have tried tweaking the code but it hasn't worked, but when I omit the componentDidUpdate part, the inputdate and inputnumber components run.
import quackSound from "./music/Duck-quack.mp3";
class MallardDuck extends Duck {
constructor(props) {
super();
this.state = {
canFly: false,
canQuack: false,
quackSound: new Audio(quackSound),
};
}
quack = () => {
const quack = new QuackSound();
return quack.quack();
};
fly = () => {
const fly = new FlyWings();
return fly.fly();
};
render() {
return (
<div className="duck">
<img
className={`image ${this.state.canFly ? "canFly" : ""}`}
src={mallardDuck}
alt="mallardDuck"
onAnimationEnd={() => {
this.setState({ canFly: false });
}}
/>
<Button
className="eventButton"
onClick={(event) => {
event.preventDefault();
this.setState({ canFly: true });
}}
>
Fly
</Button>
<Button
className="eventButton"
onClick={(event) => {
event.preventDefault();
this.setState({ canQuack: true });
this.state.quackSound.play(); // Here !!!!!!
}}
>
Quack
</Button>
{this.state.canQuack ? this.quack() : null}
{this.state.canFly ? this.fly() : null}
</div>
);
}
}
My audio mp3 file is 18 seconds long. I want to play like first 3 or 4 seconds. Is there any way to do that in react js ! The above mentioned code play the whole 18 seconds, I just want to play the first few seconds. Can I do that in react js ? Also, Can I choose from where does my sound starts and ends for example If I want to play the sound from 0.03 to 0.07 seconds of quack sound !
I would do it this way :
import quackSound from "./music/Duck-quack.mp3";
class MallardDuck extends Duck {
constructor(props) {
super();
this.state = {
canFly: false,
canQuack: false,
round:0,
quackSound: new Audio(quackSound),
};
this.intervalRef=null;
}
quack = () => {
const quack = new QuackSound();
return quack.quack();
};
fly = () => {
const fly = new FlyWings();
return fly.fly();
};
render() {
return (
<div className="duck">
<img
className={`image ${this.state.canFly ? "canFly" : ""}`}
src={mallardDuck}
alt="mallardDuck"
onAnimationEnd={() => {
this.setState({ canFly: false });
}}
/>
<Button
className="eventButton"
onClick={(event) => {
event.preventDefault();
this.setState({ canFly: true });
}}
>
Fly
</Button>
<Button
className="eventButton"
onClick={(event) => {
event.preventDefault();
//you can tweek interval duration for your liking , for now its set to pay every .3 seconds (300 ms)
const myInterval=setInterval(()=>{
if(this.state.round > 3) clearInterval(myInterval);
this.setState({ canQuack: true });
this.setState({ round: this.state.round+1 });
this.state.quackSound.play(); // Here !!!!!!},300)
}}
>
Quack
</Button>
{this.state.canQuack ? this.quack() : null}
{this.state.canFly ? this.fly() : null}
</div>
);
}
}
I have this "trash" button:
<button
type='reset'
className='c-btn--ghost no-border'
onClick={(e) => this.props.handleProjectDelete(e, project.id)}>
<i className='fa fa-trash u-margin-right-tiny'/>
</button>
This is the page with the button.
And when I click it I want a component called CustomModal to render with this props:
<CustomModal
alternateModalClass='c-group-stickies-modal'
onClosePress={this.handleCloseClick}
alternateCloseButtonClass='c-group-stickies-modal__close-button'
text={'are you sure you want to delete it?'}
/>
So a modal like this can appear:
But I don't know how to do that.
This is the component that has the trash button:
https://jsfiddle.net/10u6pfjp/
And this is the CustomModal component: https://jsfiddle.net/cp29ms8g/
As others have mentioned, you should be approaching this with a conditional statement as to whether or not your modal should appear by having a variable in this.state. Change it in your button onClick. Since you now have 2 functions to run, I made a new function called handleProjectDelete in your component to handle both at once.
At the bottom of your render, you'll see that I added the conditional where you should place a modal. I used <Modal/> as a placeholder. Use CSS to force it into a position that's appropriate for a modal.
const MAX_PROJECTS_PER_PAGE = 10
export class ProjectsTable extends Component {
constructor() {
super()
this.state = {
showModal: false
}
}
componentWillReceiveProps(nextProps) {
const { history, organizations, hasFetched } = nextProps
if (!deepEqual(this.props, nextProps) && hasFetched) {
if (!organizations || organizations.isEmpty()) {
history.push('/beta-code')
}
}
}
handleProjectDelete(e, project.id) {
this.setState({showModal: true})
this.props.handleProjectDelete(e, project.id)
}
renderProjectsTable() {
const { projects } = this.props
const projectsWithoutDefault = projects.filter(proj => proj.name !== 'default')
const projectsTable = projectsWithoutDefault.map((project) => {
return ({
name: <NavLink to={`/projects/${project.id}`}> {project.name} </NavLink>,
team: <UsersList users={fromJS(project.users)} showBadge showMax={5} type='list' />,
retro:
(project.lastRetro)
? <NavLink className='c-nav-link'
exact to={`/retrospectives/${project.lastRetro.id}`}>
{moment.utc(project.lastRetro.date)
.format('dddd, DD MMMM YYYY').toString()}
</NavLink> : <div>No retros found</div>,
delete:
<div className='delete-align'>
<button
type='reset'
className='c-btn--ghost no-border'
onClick={(e) => this.handleProjectDelete(e, project.id)}>
<i className='fa fa-trash u-margin-right-tiny'/>
</button>
</div>
})
})
return projectsTable
}
render () {
return (
<div className='o-wrapper u-margin-top'>
<TablePagination
title='Projects'
data={ this.renderProjectsTable()}
headers={['Name', 'Team', 'Last Retrospective', ' ']}
columns='name.team.retro.delete'
nextPageText='>'
prePageText='<'
perPageItemCount={ MAX_PROJECTS_PER_PAGE }
totalCount={ this.renderProjectsTable().length }
arrayOption={ [['size', 'all', ' ']] }
/>
{ this.state.showModal ? <div className='delete-modal'><Modal/><div/> : null }
</div>
)
}
}
const mapStateToProps = ({
projects
}) => ({
hasFetched: projects.get('hasFetched'),
projects: projects.get('projects')
})
ProjectsTable.defaultProps = {
projects: []
}
export default connect(mapStateToProps)(ProjectsTable)
I hope you can do this as below
<button
type='reset'
className='c-btn--ghost no-border'
onClick={(e) => {
this.props.handleProjectDelete(e, project.id);
this.renderModal;
}}>
<i className='fa fa-trash u-margin-right-tiny'/>
</button>
CODE:
var React = require('react');
var Recipe = require('./Recipe.jsx');
var AddRecipe = require('./AddRecipe.jsx');
var EditRecipe = require('./EditRecipe.jsx');
var RecipeBox = React.createClass({
getInitialState: function () {
return {
recipesArray: [],
adding: false,
editing: false,
currentIndex: 0
};
},
handleClick: function () {
this.setState({
adding: true
});
},
handleEditClick: function(index) {
this.setState({
editing: true,
currentIndex: index
});
},
handleDeleteClick: function(index) {
var newRecipesArray = this.state.recipesArray;
newRecipesArray.splice(index-1,1);
this.setState({
recipesArray: newRecipesArray
});
},
handleClose: function() {
this.setState({
adding: false,
editing: false
});
},
handleAdd: function(newRecipe) {
this.setState({
recipesArray: this.state.recipesArray.concat(newRecipe)
});
},
handleEdit: function(newRecipe, index) {
var newRecipesArray = this.state.recipesArray;
newRecipesArray[index-1] = newRecipe;
this.setState({
recipesArray: newRecipesArray
});
},
render: function() {
var i = 0;
var that = this;
var recipes = this.state.recipesArray.map(function(item) {
i++
return (
<div key={"div"+i} className="table">
<Recipe key={i} name={item.name} ingredients={item.ingredients} />
<button key ={"edit"+i} onClick={() => { that.handleEditClick(i)}} className="btn edit btn-primary">Edit</button>
<button key ={"delete"+i} onClick={() => { that.handleDeleteClick(i)}} className="btn delete btn-danger">Delete</button>
</div>
);
});
return (
<div>
<h1>React.js Recipe Box</h1>
<button className="btn btn-primary" onClick={this.handleClick}>Add Recipe</button>
<table>
<tbody>
<tr>
<th>RECIPES</th>
</tr>
</tbody>
</table>
{recipes}
{ this.state.adding ? <AddRecipe handleClose={this.handleClose} handleAdd={this.handleAdd} /> : null }
{ this.state.editing ? <EditRecipe currentIndex = {this.state.currentIndex} handleClose={this.handleClose} handleEdit={this.handleEdit}/> : null }
</div>
);
},
});
module.exports = RecipeBox;
QUESTION:
How should I implement saving state to localStorage ?
What would be the most elegant implementation ?
Currently learning React and looking to write clean and elegant code.
Whenever an update to state is fired, it will trigger the lifecycle method of componentDidUpdate. You can hook into that method in order to save the state of the component.
componentDidUpdate() {
window.localStorage.setItem('state', JSON.stringify(this.state));
}
Depending on your use case, you should be able to load it back up on componentDidMount.
componentDidMount() {
// there is a chance the item does not exist
// or the json fails to parse
try {
const state = window.localStorage.getItem('state');
this.setState({ ...JSON.parse(state) });
} catch (e) {}
}
I would warn you, you probably want a solution more like redux with a localStorage adapter for a "full-fledged" solution. This one is pretty frail in a few different ways.
I would take a look at plugins that make localstorage easier (not browser specific). An example would be this:
https://github.com/artberri/jquery-html5storage
The page above has all the information you need to get started. If that one doesn't work then I would continue to search. There are plenty out there. There may be newer ones that use React as well. The jQuery plugins have worked for me when I was learning/doing Angular.