This is a spelling game. The user drags the correct letter over the dropzone. On drop, the function checkIfLetterIsCorrect is invoked and runs the checking process. When all dropzones are filled with the correct letter the app moves on to the next word and props and state or in sync.
This all works fine on a hard refresh. However, this component is inside a Semantic UI React modal, and when the modal is closed and reopened, and I set the game back up this.props.currentWordLetters is the correct value, but when checkIfLetterIsCorrect is invoked, the value of this.props.currentWordLetters inside this method is of the value before the modal was closed. I checked with a console.log inside ComponentWillUnmount and the component is indeed unmounting when the modal is closed, so I don't see why the values inside checkIfLetterIsCorrect would be outdated as everything should be rendered like new. If somebody could point me in the right direction it would be a big help. Thank you.
class DropzoneContainer extends React.Component {
constructor() {
super();
this.state = {
correctCounter: 0,
};
}
componentWillUnmount(){
console.log('unMounted')
}
checkIfLetterIsCorrect(e) {
for (let i = 0; i < this.props.currentWordLetters.length; i++) {
console.log(this.props.currentWordLetters, this.state.letters);
if (
this.props.currentWordLetters[i] ===
e.relatedTarget.innerText &&
e.relatedTarget.innerText === e.target.innerText
) {
e.relatedTarget.classList.remove('draggable');
this.props.onCorrectLetter();
this.setState({
correctCounter: this.state.correctCounter + 1,
});
if (this.state.correctCounter === this.props.currentWordLetters.length) {
this.setState({ correctCounter: 0 })
clearInterval(this.props.timer.current);
this.props.onTimerStop();
this.props.onRoundComplete();
return;
}
}
}
}
//...
Here is the entire component:
import React from 'react';
import { connect } from 'react-redux';
import interact from 'interactjs';
import { Grid } from 'semantic-ui-react';
import Dropzone from '../dropzone/dropzone.component'
import {
onCorrectLetter,
onRoundComplete,
onTimerStop,
} from '../../../../actions/phonicsGameActions';
class DropzoneContainer extends React.Component {
constructor() {
super();
this.state = {
correctCounter: 0,
};
}
componentWillUnmount(){
console.log('unMounted')
}
checkIfLetterIsCorrect(e) {
for (let i = 0; i < this.props.currentWordLetters.length; i++) {
console.log(this.props.currentWordLetters, this.state.letters);
if (
this.props.currentWordLetters[i] ===
e.relatedTarget.innerText &&
e.relatedTarget.innerText === e.target.innerText
) {
e.relatedTarget.classList.remove('draggable');
this.props.onCorrectLetter();
this.setState({
correctCounter: this.state.correctCounter + 1,
});
if (this.state.correctCounter === this.props.currentWordLetters.length) {
this.setState({ correctCounter: 0 })
clearInterval(this.props.timer.current);
this.props.onTimerStop();
this.props.onRoundComplete();
return;
}
}
}
}
componentDidMount() {
console.log("component mountedw " + this.props.currentWordLetters)
// enable draggables to be dropped into this
interact(".inner-dropzone").dropzone({
// only accept elements matching this CSS selector
accept: ".draggable",
// Require a 75% element overlap for a drop to be possible
overlap: 0.75,
// listen for drop related events:
ondropactivate: function (event) {
// add active dropzone feedback
event.target.classList.add("drop-active");
},
ondragenter: function (event) {
var draggableElement = event.relatedTarget;
var dropzoneElement = event.target;
// feedback the possibility of a drop
dropzoneElement.classList.add("drop-target");
draggableElement.classList.add("can-drop");
//draggableElement.textContent = 'Dragged in'
},
ondragleave: function (event) {
// remove the drop feedback style
event.target.classList.remove("correct");
event.target.classList.remove("incorrect");
event.target.classList.remove("drop-target");
event.relatedTarget.classList.remove("can-drop");
event.target.classList.remove("test");
//event.relatedTarget.textContent = 'Dragged out'
},
ondrop: (event)=> {
event.stopImmediatePropagation();
this.checkIfLetterIsCorrect(event)
},
ondropdeactivate: function (event) {
// remove active dropzone feedback
event.target.classList.remove("drop-active");
event.target.classList.remove("drop-target");
event.target.classList.remove("drop-target");
},
})
}
render() {
return (
<Grid>
<Grid.Row style={{ justifyContent: 'space-evenly' }}>
{this.props.currentWordLetters.length > 1
? this.props.currentWordLetters.map((zone) => (
<Dropzone
key={zone}
letter={zone}
style={{
color: 'rgba(0,0,0,0)',
width: '55px',
height: '55px',
border: 'dotted white 2px',
}}
className="inner-dropzone"
/>
))
: 'Loading Dropzone...'}
</Grid.Row>
</Grid>
);
}
}
const mapDispatchToProps = (dispatch) => ({
onRoundComplete: () => dispatch(onRoundComplete()),
onCorrectLetter: () => dispatch(onCorrectLetter()),
onTimerStop: () => dispatch(onTimerStop()),
});
const mapStateToProps = (state) => ({
currentWordLetters: state.phonicsGameReducer.currentWordLetters,
});
export default connect(mapStateToProps, mapDispatchToProps)(DropzoneContainer);
Related
While building a react application I want to enable a feature in the application to allow the user to add a new value in the drop-down. Is it possible to add this feature in the application?
Adding the following example
import React from "react";
import { MDBDropdown, MDBDropdownToggle, MDBDropdownMenu, MDBDropdownItem } from "mdbreact";
const DropdownPage = () => {
return (
<MDBDropdown>
<MDBDropdownToggle caret color="primary">
MDBDropdown
</MDBDropdownToggle>
<MDBDropdownMenu basic>
<MDBDropdownItem>Action</MDBDropdownItem>
<MDBDropdownItem>Another Action</MDBDropdownItem>
<MDBDropdownItem>Something else here</MDBDropdownItem>
<MDBDropdownItem divider />
<MDBDropdownItem>Separated link</MDBDropdownItem>
</MDBDropdownMenu>
</MDBDropdown>
);
}
export default DropdownPage;
After the divider can we add a '+' symbol and if the user clicks on it then he can type in a new value and that gets added to the existing dropdown. Can anyone please help with the following.
Regards
import React from 'react';
import { MDBDropdown, MDBDropdownToggle, MDBDropdownMenu, MDBDropdownItem } from "mdbreact";
export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
isDrop: false,
isNewItem: false,
items: ['Action', 'Another Action', 'Something else here'],
newItem: ''
}
}
render (){
return (
<MDBDropdown>
<MDBDropdownToggle caret color="primary">
MDBDropdown
</MDBDropdownToggle>
<MDBDropdownMenu>
{
this.state.items.map((item, index) => {
return(
<MDBDropdownItem key={index}>{item}</MDBDropdownItem>
)
})
}
<MDBDropdownItem divider />
<MDBDropdownItem>Separated link</MDBDropdownItem>
<MDBDropdownItem toggle={false} onClick={this.updateIsNewItem}>{this.state.isNewItem ? 'x' : '+'}</MDBDropdownItem>
{
this.state.isNewItem &&
<MDBDropdownItem toggle={false}>
<input name='newItem' type='text' onChange={this.handleChange} value={this.state.newItem}/>
<button onClick={this.addNewItem}>Add</button>
</MDBDropdownItem>
}
</MDBDropdownMenu>
</MDBDropdown>
)
}
addNewItem = () => {
if(this.state.newItem !== ''){
this.setState({
items: [...this.state.items, this.state.newItem],
newItem: '',
isNewItem: false
});
}
}
handleChange = (event) => {
const name = event.target.name;
const value = event.target.value;
this.setState({[name]: value});
}
updateIsNewItem = () => {
this.setState(prevState => ({isNewItem: !prevState.isNewItem}));
}
}
I am building a searchDropdown component in React. I want to render dropdown only when search field is active. So I put a condition to render dropdown.
But when the dropdown is rendered conditionally, onClick events inside dropdown are not triggering.
My component is below.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {List, ListItem} from 'material-ui/List';
import {Card} from 'material-ui/Card';
import Divider from 'material-ui/Divider';
import TextField from 'material-ui/TextField';
import resources from '../../resources/resources';
import cx from 'classnames';
import './SearchDropdown.scss';
class SearchDropdown extends Component {
constructor(props) {
super(props);
this.cardContainerStyle = {
'maxHeight': '300px',
'overflow': 'scroll',
'border': '1px solid rgb(158, 158,158)'
};
this.searchFieldUnderlineStyle = {
'borderBottom': '1px solid #ccc'
}
this.state = {
dropdownStyle: {}
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.isActive && this.props.isActive !== nextProps.isActive) {
this.shouldSetBounds = true;
} else {
this.shouldSetBounds = false;
}
}
componentDidUpdate() {
if(this.shouldSetBounds) {
this._setDropdownBounds();
this.shouldSetBounds = false;
}
}
componentDidMount() {
window.addEventListener('scroll', this._handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this._handleScroll);
}
_handleScroll = () => {
if (this.props.isActive) {
this._setDropdownBounds();
}
}
_setDropdownBounds() {
const dropdownContainerOffset = this.dropdownContainer.getBoundingClientRect();
const containerTop = dropdownContainerOffset.top;
const containerLeft = dropdownContainerOffset.left;
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const dropdownStyle = {
top: dropdownContainerOffset.top + dropdownContainerOffset.height,
left: dropdownContainerOffset.left
};
if (containerTop > viewportHeight/2) {
dropdownStyle.top = 'auto';
dropdownStyle.bottom = viewportHeight - containerTop;
}
this.setState({ dropdownStyle });
}
_renderDropdown() {
const {onSelect, datalist = []} = this.props;
return (
<div className="search-dropdown-wrapper" style={this.state.dropdownStyle} onClick={(event) => {alert("Outer wrapper")}}>
<Card containerStyle={this.cardContainerStyle}>
<div>
{"Sample Dropdown"}
</div>
</Card>
</div>
);
}
_renderSearchField() {
const {value, handleSearch} = this.props;
return (
<TextField
value={value}
onChange={handleSearch}
fullWidth={true}
hintText={resources.BRAND_SEARCH}
underlineStyle={this.searchFieldUnderlineStyle}
/>
);
}
render() {
const {isActive, onBlur} = this.props;
return (
<div className="search-dropdown-container field-wrapper"
ref={dropdownContainer => this.dropdownContainer = dropdownContainer}
onBlur={onBlur}
>
{this._renderSearchField()}
{isActive && this._renderDropdown()}
</div>
);
}
}
SearchDropdown.propTypes = {
isActive: React.PropTypes.bool.isRequired,
value: React.PropTypes.string,
datalist: React.PropTypes.array,
handleSearch: React.PropTypes.func.isRequired,
onSelect: React.PropTypes.func
}
export default SearchDropdown;
In above code, _renderDropdown will only execute when isActive is true. The compoenent is getting rendered perfectly with all styles when the search field is active. But when this component is rendered, the onClick event on the div with class search-dropdown-wrapper is not working.
I'm not sure where I'm doing mistake. Please let me know if there is any proper way to do this. Thanks.
I finally made it work.
There is nothing wrong with the arrow function _renderDropdown used. The problem is with the onBlur event attached to the parent div with class search-dropdown-container of _renderDropdown. I have removed that onBlur event from there and added to search-dropdown-wrapper div. Then it started working fine.
What I think is, The search-dropdown-wrapper position is fixed and so it will not be as part of search-dropdown-container technically. So click event happening on the search-dropdown-wrapper triggers onBlur event of search-dropdown-container first. So, the click event is not firing on search-dropdown-wrapper
Please bind this to all methods in your class in the constructor like for example -
constructor(props) {
this._handleScroll = this._handleScroll.bind(this)
this._setDropdownBounds = this._setDropdownBounds.bind(this)
}
FYI
https://reactjs.org/docs/handling-events.html
http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-component-classes/
https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56
I am working on a simple version of ReactDND before I implement this code into my image uploader.
Each time an image is added, it is added to state and passed through to ReactDND so that it is draggable and also droppable (so users can rearrange their images).
Everything works great, except for one thing. The problem I am having is after adding multiple images, is that once I drag and drop and image (works), the State no longer updates for ReactDND and I cannot add new images.
Here is my code below (note I am just using a button to add extra items to state):
Main Component:
import React from 'react';
// Drag and drop stuff
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Container from './Container';
class ImageUploader extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
listCount: 1
};
this.onAddItem = this.onAddItem.bind(this);
}
onAddItem(e) {
e.preventDefault();
var listArray = this.state.list;
var buildObject = {
text: 'Jeremy' + this.state.listCount.toString(),
age: '25',
id: this.state.listCount
};
listArray.push(buildObject);
let newListCount = this.state.listCount + 1;
this.setState({
list: listArray,
listCount: newListCount
});
console.log(this.state.list);
}
render() {
return (
<div>
<h1>Add to List</h1>
<button onClick={this.onAddItem}>Add Item</button>
<h1>The List</h1>
<Container id={1} list={this.state.list} />
</div>
)
}
}
export default DragDropContext(HTML5Backend)(ImageUploader);
Container:
import React, { Component } from 'react';
import update from 'react/lib/update';
import Card from './Card';
import { DropTarget } from 'react-dnd';
class Container extends Component {
constructor(props) {
super(props);
this.state = { cards: props.list };
}
pushCard(card) {
this.setState(update(this.state, {
cards: {
$push: [ card ]
}
}));
}
removeCard(index) {
this.setState(update(this.state, {
cards: {
$splice: [
[index, 1]
]
}
}));
}
moveCard(dragIndex, hoverIndex) {
const { cards } = this.state;
const dragCard = cards[dragIndex];
this.setState(update(this.state, {
cards: {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard]
]
}
}));
}
render() {
const { cards } = this.state;
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
const style = {
width: "200px",
height: "404px",
border: '1px dashed gray'
};
const backgroundColor = isActive ? 'lightgreen' : '#FFF';
return connectDropTarget(
<div className="houzes-dropbox">
{cards.map((card, i) => {
return (
<Card
key={card.id}
index={i}
listId={this.props.id}
card={card}
removeCard={this.removeCard.bind(this)}
moveCard={this.moveCard.bind(this)} />
);
})}
</div>
);
}
}
const cardTarget = {
drop(props, monitor, component ) {
const { id } = props;
const sourceObj = monitor.getItem();
if ( id !== sourceObj.listId ) component.pushCard(sourceObj.card);
return {
listId: id
};
}
}
export default DropTarget("CARD", cardTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
}))(Container);
Card:
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import { DragSource, DropTarget } from 'react-dnd';
import flow from 'lodash/flow';
const style = {
border: '1px dashed grey',
padding: '0.5rem 1rem',
margin: '.5rem',
backgroundColor: 'white',
cursor: 'move'
};
class Card extends Component {
render() {
const { card, isDragging, connectDragSource, connectDropTarget } = this.props;
const opacity = isDragging ? 0 : 1;
// Background URL
let backgroundUrl = {
backgroundImage: "url(" + "http://localhost:4000/uploads/2017/8/a3ff91dc-2f80-42f7-951a-e9a74bf954d7-1200x800.jpeg" + ")"
};
console.log(card);
return connectDragSource(connectDropTarget(
<div className={`uploadedImageWrapper col-md-6 col-sm-12`}>
<div className="uploadedImage">
<span style={backgroundUrl} />
{card.text}
{card.age}
</div>
</div>
));
}
}
const cardSource = {
beginDrag(props) {
return {
index: props.index,
listId: props.listId,
card: props.card
};
},
endDrag(props, monitor) {
const item = monitor.getItem();
const dropResult = monitor.getDropResult();
if ( dropResult && dropResult.listId !== item.listId ) {
props.removeCard(item.index);
}
}
};
const cardTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
const sourceListId = monitor.getItem().listId;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
// Determine rectangle on screen
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// Determine mouse position
const clientOffset = monitor.getClientOffset();
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
// Time to actually perform the action
if ( props.listId === sourceListId ) {
props.moveCard(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex;
}
}
};
export default flow(
DropTarget("CARD", cardTarget, connect => ({
connectDropTarget: connect.dropTarget()
})),
DragSource("CARD", cardSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}))
)(Card);
So just to recap, I can add items to state, and they become draggable and droppable. But after having dragged and dropped an element, I can no longer add anymore items to state.
Any ideas as to what the solution would be? What am I doing wrong?
Thank-you for looking through this, and any answers. Cheers.
#Notorious.
I have checked your code in my side and solved the issue.
When you drag and drop an element that changes the state of Container but not the state of ImageUploader.
So I made a function to inform the state of Container has changed.
Also I inserted componentWillReceiveProps() function to Container and updated the state of Container in that function.
Finally the problem solved.
Here's the changed code.
Main Component:
import React from 'react';
// Drag and drop stuff
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Container from './Container';
class ImageUploader extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
listCount: 1
};
this.onAddItem = this
.onAddItem
.bind(this);
this.listChanged = this.listChanged.bind(this);
}
onAddItem(e) {
e.preventDefault();
var listArray = this.state.list;
var buildObject = {
text: 'Jeremy' + this
.state
.listCount
.toString(),
age: '25',
id: this.state.listCount
};
listArray.push(buildObject);
let newListCount = this.state.listCount + 1;
this.setState({list: listArray, listCount: newListCount});
}
listChanged(newList) {
this.setState({
list: newList
})
}
render() {
return (
<div>
<h1>Add to List</h1>
<button onClick={this.onAddItem}>Add Item</button>
<h1>The List</h1>
<Container id={1} list={this.state.list} listChanged={this.listChanged}/>
</div>
)
}
}
export default DragDropContext(HTML5Backend)(ImageUploader);
Container:
import React, { Component } from 'react';
import update from 'react/lib/update';
import Card from './Card';
import { DropTarget } from 'react-dnd';
class Container extends Component {
constructor(props) {
super(props);
this.state = { cards: this.props.list };
}
pushCard(card) {
this.setState(update(this.state, {
cards: {
$push: [ card ]
}
}));
}
removeCard(index) {
this.setState(update(this.state, {
cards: {
$splice: [
[index, 1]
]
}
}));
}
moveCard(dragIndex, hoverIndex) {
const { cards } = this.state;
const dragCard = cards[dragIndex];
this.setState(update(this.state, {
cards: {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard]
]
}
}));
}
componentWillReceiveProps(nextProps) {
// You don't have to do this check first, but it can help prevent an unneeded render
if (nextProps.list !== this.state.cards) {
this.props.listChanged(this.state.cards);
}
}
render() {
const { cards } = this.state;
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
const style = {
width: "200px",
height: "404px",
border: '1px dashed gray'
};
const backgroundColor = isActive ? 'lightgreen' : '#FFF';
return connectDropTarget(
<div className="houzes-dropbox">
{cards.map((card, i) => {
return (
<Card
key={card.id}
index={i}
listId={this.props.id}
card={card}
removeCard={this.removeCard.bind(this)}
moveCard={this.moveCard.bind(this)} />
);
})}
</div>
);
}
}
const cardTarget = {
drop(props, monitor, component ) {
const { id } = props;
const sourceObj = monitor.getItem();
if ( id !== sourceObj.listId ) component.pushCard(sourceObj.card);
return {
listId: id
};
}
}
export default DropTarget("CARD", cardTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
}))(Container);
I am really happy if this helped you.
Thanks for reading my post.
Vladimir
I have a notification component that looks like this:
When I click the bell button, a notification container pops up, like so:
I have the functionality set up so that when a user clicks or touches outside of the notification container, the box closes. However, I do not want to add events to the DOM directly. I'd much rather take advantage of React's SyntheticEvent wrapper. Does anyone know how I could use SyntheticEvent, instead of the DOM events I currently have?
Here is my code so far:
import React from 'react';
import { PropTypes } from 'prop-types';
import './Notification.scss';
export default class Notification extends React.Component {
constructor(props) {
super(props);
this.setNotificationWrapper = this.setNotificationWrapper.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
this.state = {
visible: false,
hasNotifications: false,
expanded: false,
};
this.toggleNotificationContainer = this.toggleNotificationContainer
.bind(this);
}
componentDidMount() {
document.addEventListener('touchend', this.handleClickOutside);
document.addEventListener('mousedown', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('touchend', this.handleClickOutside);
document.removeEventListener('mousedown', this.handleClickOutside);
}
setNotificationWrapper(node) {
this.notificationWrapper = node;
}
handleClickOutside(event) {
if (this.notificationWrapper &&
!this.notificationWrapper.contains(event.target)) {
this.toggleNotificationContainer();
event.preventDefault();
}
}
toggleNotificationContainer() {
this.setState({
visible: !this.state.visible,
hasNotifications: this.state.hasNotifications,
expanded: !this.state.expanded,
});
}
createNotificationItems(option, index) {
this.state.hasNotifications = true;
return (
<li
key={index}
value={index}
>
{option.label}
</li>
);
}
render() {
// set up the details to show in the list.
let notificationOptions = null;
let optionCount = 0;
let toggleDivDetails = null;
let buttonStyleName = '';
let buttonText = null;
if (this.props.notificationButtonIcon) {
buttonStyleName = 'notificationButton';
} else {
buttonText = this.props.notificationButtonText;
buttonStyleName = 'notificationButtonWithText';
}
toggleDivDetails = (
<button
onClick={this.toggleNotificationContainer}
styleName={this.state.expanded ? 'expanded' : buttonStyleName}
disabled={this.state.expanded}
>
{buttonText}
</button>
);
if (this.props.options) {
optionCount = this.props.options.length;
notificationOptions = this.props.options.map((option, index) =>
this.createNotificationItems(option, index));
}
if (optionCount === 0) {
this.state.hasNotifications = false;
}
return (
<div styleName="notificationBar">
{toggleDivDetails}
<span
styleName={
this.state.hasNotifications ? 'notificationCount' : 'hidden'
}
>
{optionCount}
</span>
{
this.state.visible &&
<div
styleName="notificationContainer"
ref={this.setNotificationWrapper}
>
<h3 styleName="notificationHeading">Notifications</h3>
<h4 styleName="notificationSubHeading">
{optionCount} notifications
</h4>
<ul styleName="notificationList">
{notificationOptions}
</ul>
</div>
}
</div>
);
}
}
Notification.propTypes = {
options: PropTypes.arrayOf(PropTypes.object),
notificationButtonIcon: PropTypes.bool.isRequired,
notificationButtonText: PropTypes.string.isRequired,
};
Notification.defaultProps = {
options: [],
NotificationButtonIcon: true,
notificationButtonText: 'Toggle Notifications',
};
I'm new to react and programming in general, I have searched and only found solutions for js not react specific.
Having trouble displaying next or previous item in an array passed via props. When Next button is clicked I only see the same item in the array being returned not the next item, I understand previous will return null as displaying first item on load.
import React, { Component } from 'react'
import VideoPlayer from './Video'
import axios from 'axios'
export default class App extends Component {
constructor(props) {
super(props);
this._TogglePrev = this._TogglePrev.bind(this);
this._ToggleNext = this._ToggleNext.bind(this);
// app state
this.state = {
videos: [],
selectedVideo: null
}
}
componentDidMount() {
axios.get('http://localhost:5000/v1/video?id=287948764917205')
.then((result)=> {
var videos = result.data.payload
this.setState({
videos: videos,
selectedVideo: videos[0]
});
})
}
componentWillUnmount() {
this.serverRequest.abort()
}
// State transitions
_ToggleNext() {
console.log("something worked");
// take a copy of our state
const selectedVideo = this.state.selectedVideo;
// next video
var i = 0,
max = selectedVideo.length;
for (i; i < max; i += 1) {
if (selectedVideo[i]) {
return selectedVideo[i + 1];
}
}
//set our state
this.setState( selectedVideo );
console.log(selectedVideo)
}
_TogglePrev() {
console.log("something worked");
var current = this.state.selectedVideo;
var prev = current - 1;
if (prev < 0) {
prev = this.state.videos.length - 1;
}
// update our state
this.setState({ prev });
}
render() {
return (
<div className="App" style={{width: '100%', height: '100%'}}>
<div className="controls">
<button className="toggle toggle--prev" onClick={this._TogglePrev}>Prev</button>
<button className="toggle toggle--next" onClick={this._ToggleNext}>Next</button>
</div>
<VideoPlayer video={this.state.selectedVideo} />
</div>
)
}
}
The returned data
[
{ eventId: "287948764917205"
userName: "Jon Doe"
videoLink: "https://"https:s3.amazonaws.com/...""
userPhotoLink: "https://"https:s3.amazonaws.com/...""
},
{ eventId: "287948764917205"
userName: "Jane Thompson"
videoLink: "https://"https:s3.amazonaws.com/...""
userPhotoLink: "https://"https:s3.amazonaws.com/...""
}
]
Mistakes:
1. If you use return keyword inside for loop it will not only break the loop, it will return from that function also, so in these lines:
for (i; i < max; i += 1) {
if (selectedVideo[i]) {
return selectedVideo[i + 1];
}
}
this.setState( selectedVideo );
....
If if(selectedVideo[i]) will return true then it will break the loop and return from the function, so the lines after this for loop will never executes because of that return statement.
2. setState is a function and we need to pass an object (key-value pair, key will be the state variable names) in this, so you need to write it like this:
this.setState({ selectedVideo }); or this.setState({ selectedVideo: selectedVideo }); //both are same
Another way of writing the code by maintaining index:
1. Instead of maintaining selectedVideo in state variable maintain the index only, index of item of the array.
2. On click of next and prev button, increase or decrease the value of index and use that index to pass specific object of the state videos array to child component.
Like this:
import React, { Component } from 'react'
import VideoPlayer from './Video'
import axios from 'axios'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
videos: [],
selectedIndex: 0
}
this._TogglePrev = this._TogglePrev.bind(this);
this._ToggleNext = this._ToggleNext.bind(this);
}
componentDidMount() {
axios.get('http://localhost:5000/v1/video?id=287948764917205')
.then((result)=> {
var videos = result.data.payload
this.setState({
videos: videos,
selectedIndex: 0
});
})
}
componentWillUnmount() {
this.serverRequest.abort()
}
_ToggleNext() {
if(this.state.selectedIndex == this.state.videos.length - 1)
return;
this.setState(prevState => ({
selectedIndex: prevState.selectedIndex + 1
}))
}
_TogglePrev() {
if(this.state.selectedIndex == 0)
return;
this.setState(prevState => ({
selectedIndex: prevState.selectedIndex - 1
}))
}
render() {
let {selectedIndex, videos} = this.state;
return (
<div className="App" style={{width: '100%', height: '100%'}}>
<div className="controls">
<button className="toggle toggle--prev" onClick={this._TogglePrev}>Prev</button>
<button className="toggle toggle--next" onClick={this._ToggleNext}>Next</button>
</div>
<VideoPlayer video={videos[selectedIndex]} />
</div>
)
}
}
Use document.activeElement in order to get the currently focused element. Then, use nextElementSibling on order to get the next element then focus() just like thisdocument.activeElement.nextElementSibling.focus()
Full example:
export default function TextField() {
return (
<div
onKeyDown={(e:any)=>{
if (e.keyCode==13){
const active:any = document.activeElement
active.nextElementSibling.focus()
}
}}
>
<input/>
<input/>
<input/>
</div>
);
};
It's better to write in the constructor:
constructor(props) {
super(props);
this._TogglePrev.bind(this);
this._ToggleNext.bind(this);
// app state
this.state = {
videos: [],
selectedVideo: null,
selectedVideoIndex:0
}
}
and also change
_ToggleNext() {
console.log("something worked");
// take a copy of our state
const selectedVideo = this.state.selectedVideo;
// next video
var selectedVideoIndex = this.state.selectedVideoIndex; //i always starts with zero ????? you need also to save the index
max = selectedVideo.length;
for (selectedVideoIndex; selectedVideoIndex < max; selectedVideoIndex++) {
if (selectedVideo[selectedVideoIndex]) {
const retval = selectedVideo[selectedVideoIndex + 1];
this.setState( selectedVideoIndex+1 );
this.setState(retval );
return retval;
}
}
console.log(selectedVideo)
}