Related
In the following code, I have two columns. One column has all the tasks. The other column has a group of selected tasks. So, my expectation is that, when a user drags and drops a task from the total tasks column to the selected tasks column, the task should still be there in total tasks column. When the tasks are there in the selected tasks column, there will be a button visible to delete the tasks from selected tasks column. When I click this button, the task gets removed from the selected tasks column. But I am not able to drag it again from the total tasks column. I am getting the following error:
Unable to find draggable with id: task-2
Request you guys to take a look and tell me what's the mistake I am doing
Please find the link for the codesandbox:
https://codesandbox.io/s/unruffled-waterfall-wdyc5?file=/src/task.jsx
Also this is my code:
App.jsx:
import React from 'react';
import '#atlaskit/css-reset';
import styled from 'styled-components';
import { DragDropContext } from 'react-beautiful-dnd';
import initialData from './initial-data';
import Column from './column';
const Container = styled.div`
display: flex;
`;
class App extends React.Component {
state = initialData;
onDragStart = start => {
const homeIndex = this.state.columnOrder.indexOf(start.source.droppableId);
this.setState({
homeIndex,
});
};
onDragEnd = result => {
this.setState({
homeIndex: null,
});
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const home = this.state.columns[source.droppableId];
const foreign = this.state.columns[destination.droppableId];
if (home === foreign) {
const newTaskIds = Array.from(home.taskIds);
newTaskIds.splice(source.index, 1);
newTaskIds.splice(destination.index, 0, draggableId);
const newHome = {
...home,
taskIds: newTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newHome.id]: newHome,
},
};
this.setState(newState);
return;
}
const foreignTaskIds = Array.from(foreign.taskIds);
foreignTaskIds.splice(destination.index, 0, draggableId);
const newForeign = {
...foreign,
taskIds: foreignTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newForeign.id]: newForeign,
},
};
this.setState(newState);
};
deleteHandler = (taskId) => {
console.warn("I am going to delete: " + taskId);
var columnId = 'column-2';
const column = this.state.columns[columnId];
const columnTaskIds = Array.from(column.taskIds);
columnTaskIds.splice(columnTaskIds.indexOf(taskId), 1);
const newcolumn = {
...column,
taskIds: columnTaskIds,
};
var newState = null;
newState = {
...this.state,
columns: {
...this.state.columns,
[newcolumn.id]: newcolumn
}
};
this.setState(newState);
console.log(newState);
}
render() {
return (
<DragDropContext
onDragStart={this.onDragStart}
onDragEnd={this.onDragEnd}
>
<Container>
{this.state.columnOrder.map((columnId, index) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(
taskId => this.state.tasks[taskId],
);
const isDropDisabled = index < this.state.homeIndex;
return (
<Column
key={column.id}
column={column}
tasks={tasks}
isDropDisabled={isDropDisabled}
deleteHandler={this.deleteHandler}
/>
);
})}
</Container>
</DragDropContext>
);
}
}
export default App
column.jsx
import React from 'react';
import styled from 'styled-components';
import { Droppable } from 'react-beautiful-dnd';
import Task from './task';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
width: 220px;
display: flex;
flex-direction: column;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
transition: background-color 0.2s ease;
background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')};
flex-grow: 1;
min-height: 100px;
`;
export default class Column extends React.Component {
isSelectedTasksColumn = this.props.column.id === 'column-2';
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<Droppable
droppableId={this.props.column.id}
isDropDisabled={this.props.isDropDisabled}
>
{(provided, snapshot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapshot.isDraggingOver}
>
{this.props.tasks.map((task, index) => (
<Task key={task.id} task={task} index={index} isSelectedTasksColumn={this.isSelectedTasksColumn} deleteHandler={this.props.deleteHandler}/>
))}
{provided.placeholder}
</TaskList>
)}
</Droppable>
</Container>
);
}
}
task.jsx
import React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;
background-color: ${props =>
props.isDragDisabled
? 'lightgrey'
: props.isDragging
? 'lightgreen'
: 'white'};
`;
const DeleteButton = styled.button`
justify-self: center;
align-self: center;
border: 0;
background-color: black;
color: white;
padding: 7px;
cursor: pointer;
display: ${props => props.isSelectedTasksColumn ? "inline" : "none"};
float: right;
`;
const DisplayValue = styled.p`
align-self: center;
font-size: 20px;
padding: 0;
margin: 0;
display: inline;
`;
export default class Task extends React.Component {
render() {
const isDragDisabled = false;
return (
<Draggable
draggableId={this.props.task.id}
index={this.props.index}
isDragDisabled={isDragDisabled}
>
{(provided, snapshot) => (
<Container
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
isDragging={snapshot.isDragging}
isDragDisabled={isDragDisabled}
>
<DisplayValue>{this.props.task.content}</DisplayValue>
<DeleteButton isSelectedTasksColumn = {this.props.isSelectedTasksColumn}
onClick={() => this.props.deleteHandler(this.props.task.id)}>
✖
</DeleteButton>
</Container>
)}
</Draggable>
);
}
}
initial-data.js:
const initialData = {
tasks: {
'task-1': { id: 'task-1', content: 'Task 1' },
'task-2': { id: 'task-2', content: 'Task 2' },
'task-3': { id: 'task-3', content: 'Task 3' },
'task-4': { id: 'task-4', content: 'Task 4' },
},
columns: {
'column-1': {
id: 'column-1',
title: 'All tasks',
taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
},
'column-2': {
id: 'column-2',
title: 'Selected tasks',
taskIds: [],
}
},
// Facilitate reordering of the columns
columnOrder: ['column-1', 'column-2'],
};
export default initialData;
Removing React.StrictMode could help
https://reactjs.org/docs/strict-mode.html
If I click on a certain product I get the following 404 error:
If I click on one of the first 6 products which guide me to the link listed within the screenshot above, i get an error regarding my qty being undefined as follows:
and when i close out of the error I am left with this:
AGAIN - I have included my gatsby-node.js as well as my QtyButton.js code below... I also have linked to my github repo here with the most updated code...
gatsby-node.js:
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
// You can delete this file if you're not using it
exports.createPages = async({
graphql,
actions
}) => {
const {
createPage
} = actions
const result = await graphql(
`
{
products: allStrapiProduct {
edges {
node {
name
strapiId
description
category {
name
}
variants {
id
color
size
style
price
images {
localFile {
childImageSharp {
gatsbyImageData
}
}
}
}
}
}
}
categories: allStrapiCategory {
edges {
node {
strapiId
name
description
filterOptions {
Size {
checked
label
}
Style {
checked
label
}
Color {
checked
label
}
}
}
}
}
}
`
)
if (result.errors) {
throw result.errors
}
const products = result.data.products.edges
const categories = result.data.categories.edges
products.forEach(product => {
createPage({
path: `/${product.node.category.name.toLowerCase()}/${
product.node.name.split(' ')[0]
}`,
component: require.resolve('./src/templates/ProductDetail.js'),
context: {
name: product.node.name,
id: product.node.strapiId,
category: product.node.category.name,
description: product.node.description,
variants: product.node.variants,
product,
},
})
})
categories.forEach(category => {
createPage({
path: `/${category.node.name.toLowerCase()}`,
component: require.resolve('./src/templates/ProductList.js'),
context: {
name: category.node.name,
description: category.node.description,
id: category.node.strapiId,
filterOptions: category.node.filterOptions,
},
})
})
}
exports.onCreateWebpackConfig = ({
stage,
loaders,
actions
}) => {
if (stage === 'build-html') {
actions.setWebpackConfig({
module: {
rules: [{
test: /react-spring-3d-carousel/,
use: loaders.null()
}],
},
})
}
}
QtyButton.js
import React, {
useState,
useEffect,
useContext
} from 'react'
import clsx from 'clsx'
import Grid from '#material-ui/core/Grid'
import Typography from '#material-ui/core/Typography'
import Button from '#material-ui/core/Button'
import ButtonGroup from '#material-ui/core/ButtonGroup'
import Badge from '#material-ui/core/Badge'
import {
makeStyles
} from '#material-ui/core/styles'
import {
CartContext
} from '../../contexts'
import {
addToCart,
removeFromCart
} from '../../contexts/actions'
import Cart from '../../images/Cart'
const useStyles = makeStyles(theme => ({
qtyText: {
color: ({
white
}) => (white ? theme.palette.secondary.main : '#fff'),
},
mainGroup: {
height: '3rem',
},
editButtons: {
height: '1.525rem',
borderRadius: 0,
backgroundColor: ({
white
}) =>
white ? '#fff' : theme.palette.secondary.main,
borderLeft: ({
white
}) =>
`2px solid ${white ? theme.palette.secondary.main : '#fff'}`,
borderRight: ({
round
}) => (round ? 0 : '2px solid #fff'),
borderBottom: 'none',
borderTop: 'none',
borderRadius: ({
round
}) => (round ? '0px 50px 50px 0px' : 0),
'&:hover': {
backgroundColor: ({
white
}) =>
white ? '#fff' : theme.palette.secondary.light,
},
},
endButtons: {
backgroundColor: ({
white
}) =>
white ? '#fff' : theme.palette.secondary.main,
borderRadius: 50,
border: 'none',
},
cartButton: {
marginLeft: '0 !important',
transition: 'background-color 1s ease',
},
minus: {
marginTop: '-0.25rem',
},
minusButton: {
borderTop: ({
white
}) =>
`2px solid ${white ? theme.palette.secondary.main : '#fff'}`,
},
qtyButton: {
'&:hover': {
backgroundColor: ({
white
}) =>
white ? '#fff' : theme.palette.secondary.main,
},
},
badge: {
color: '#fff',
fontSize: '1.5rem',
backgroundColor: theme.palette.secondary.main,
padding: 0,
},
success: {
backgroundColor: theme.palette.success.main,
'&:hover': {
backgroundColor: theme.palette.success.main,
},
},
}))
export default function QtyButton({
stock,
variants,
selectedVariant,
name,
isCart,
white,
hideCartButton,
round,
override,
}) {
const {
cart,
dispatchCart
} = useContext(CartContext)
const existingItem = isCart ?
cart.find(item => item.variant === variants[selectedVariant]) :
null
const classes = useStyles({
white,
round
})
const [qty, setQtyState] = useState(isCart ? existingItem.qty : 1)
const [success, setSuccess] = useState(false)
let setQty
if (override) {
setQty = val => {
override.setValue(val)
setQtyState(val)
}
} else {
setQty = setQtyState
}
const handleChange = direction => {
if (qty === stock[selectedVariant].qty && direction === 'up') {
return null
}
if (qty === 1 && direction === 'down') {
return null
}
const newQty = direction === 'up' ? qty + 1 : qty - 1
setQty(newQty)
if (isCart) {
if (direction === 'up') {
dispatchCart(addToCart(variants[selectedVariant], 1, name))
} else if (direction === 'down') {
dispatchCart(removeFromCart(variants[selectedVariant], 1))
}
}
}
const handleCart = () => {
setSuccess(true)
dispatchCart(
addToCart(
variants[selectedVariant],
qty,
name,
stock[selectedVariant].qty
)
)
}
useEffect(() => {
if (stock === null || stock === -1) {
return undefined
}
if (qty === 0 && stock[selectedVariant].qty !== 0) {
setQty(1)
} else if (qty > stock[selectedVariant].qty) {
setQty(stock[selectedVariant].qty)
}
}, [stock, selectedVariant])
useEffect(() => {
let timer
if (success) {
timer = setTimeout(() => setSuccess(false), 1500)
}
return () => clearTimeout(timer)
}, [success])
return ( <
Grid item >
<
ButtonGroup classes = {
{
root: classes.mainGroup
}
} >
<
Button classes = {
{
root: clsx(classes.endButtons, classes.qtyButton)
}
} >
<
Typography variant = "h3"
classes = {
{
root: classes.qtyText
}
} > {
qty
} <
/Typography> <
/Button> <
ButtonGroup orientation = "vertical" >
<
Button onClick = {
() => handleChange('up')
}
classes = {
{
root: classes.editButtons
}
} >
<
Typography variant = "h3"
classes = {
{
root: classes.qtyText
}
} >
+
<
/Typography> <
/Button> <
Button onClick = {
() => handleChange('down')
}
classes = {
{
root: clsx(classes.editButtons, classes.minusButton)
}
} >
<
Typography variant = "h3"
classes = {
{
root: clsx(classes.qtyText, classes.minus)
}
} >
-
<
/Typography> <
/Button> <
/ButtonGroup> {
hideCartButton ? null : ( <
Button onClick = {
handleCart
}
disabled = {
stock ? stock[selectedVariant].qty === 0 : true
}
classes = {
{
root: clsx(classes.endButtons, classes.cartButton, {
[classes.success]: success,
}),
}
} >
{
success ? ( <
Typography variant = "h3"
classes = {
{
root: classes.qtyText
}
} > ✓
<
/Typography>
) : ( <
Badge overlap = "circle"
badgeContent = "+"
classes = {
{
badge: classes.badge
}
} >
<
Cart color = "#fff" / >
<
/Badge>
)
} <
/Button>
)
} <
/ButtonGroup> <
/Grid>
)
}
It's impossible to follow this code, you've pasted images rather than code blocks so it's extremely hard to know where each part belongs (even more if they are parts repeated between snapshots). Try to debug and add the interesting parts, not the whole file...
and when i close out of the error I am left with this
Well, you should never close the issue. It needs to be fixed, not ignored.
Your gatsby-node.js, among querying a bunch of fields you are not using (image, price, etc) looks fair good. Remember that the gatsby-node.js is where you create pages and send data to your templates, just as is, don't query unnecessary fields to avoid long build times. You should query for those fields in your template query.
Said that regarding the "first issue", if your product page is not rendering, your problem is there. Run gatsby clean in each trial.
Regarding your blocking issue, the one is breaking your code, as I said, it's impossible to follow the trace given the details you've provided but I would try something like:
<Button
onClick={handleCart}
disabled={stock[selectedVariant] ? stock[selectedVariant].qty === 0 : true}
classes={{
root: clsx(classes.endButtons, classes.cartButton, {
[classes.success]: success,
}),
}}
>
Clearly, in some of your products you don't have a qty property inside the stock object, that's why your code is breaking. You should pull the thread to know if that product should or shouldn't have qty and adapt the code logic to that business logic, not otherwise. The snippet above should fix your code-breaking issue but you need to know if your Button should or shouldn't have qty at that point.
If you are using optional chaining plugin you can simplify it as:
disabled={stock?.selectedVariant?.qty === 0 : true}
I made a tic tac toe game. It works fine but the player's name are static.
I have a form with two player's name as an text field which sets state values when typed something.
I am having a problem to render the game when button is clicked.
classNames :
Board-> contains the tic tac toe game
Game -> contains Board class with extra divs for "turns" "player names"
StartGame -> contains the form
I have written the following code to get the tic tac toe game div when button is clicked.
<label> Player 1: </label>
<input
type="text"
onChange={() => this.setState({ p1: event.target.value })}
defaultValue={this.state.p1}
placeholder="Player 1 Name"
/>
<br /> <br />
<label> Player 2: </label>
<input
type="text"
onChange={() => this.setState({ p2: event.target.value })}
defaultValue={this.state.p2}
placeholder="Player 2 Name"
/>
<br /> <br />
<input
type="button"
value="Start New Game"
onClick={() => {
this.renderGame(this.state.p1, this.state.p2);
}}
/>
Code for whole project is:
https://codepen.io/damodar-bhattarai/pen/YzKWREN?editors=0010
I want to display the tic tac toe game only when form is filled and button is clicked.
update:
Code for renderGame function
renderGame(p11, p22){
if (p11 && p22) {
return <Game p1={p11} p2={p22} />;
} else {
return "error";
}
};
Final Update
Game Working link with restart new game:
https://codepen.io/damodar-bhattarai/pen/zYOBexp?editors=0010
The simplest solution will be to use a flag to render the component and toggle the flag when the user clicks on the 'Start new Game' button.
Hope the below solution helps. Please reach out to me if you need any clarification.
function Square(props) {
return ( <
button className = "square"
onClick = {
props.onClick
} > {
props.value
} <
/button>
);
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
clicks: 0,
p1: props.p1,
p2: props.p2,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
let count = this.state.clicks;
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
clicks: ++count
});
}
renderSquare(i) {
return <Square
value = {
this.state.squares[i]
}
onClick = {
() => this.handleClick(i)
}
/>;
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X(' + this.state.p1 + ')' : 'O(' + this.state.p2 + ')');
}
let countclick;
countclick = this.state.clicks;
return ( <
div >
<
p > No.of Clicks: {
countclick
} < /p> <
div className = "status" > {
status
} < /div> <
div className = "board-row" > {
this.renderSquare(0)
} {
this.renderSquare(1)
} {
this.renderSquare(2)
} <
/div> <
div className = "board-row" > {
this.renderSquare(3)
} {
this.renderSquare(4)
} {
this.renderSquare(5)
} <
/div> <
div className = "board-row" > {
this.renderSquare(6)
} {
this.renderSquare(7)
} {
this.renderSquare(8)
} <
/div> < /
div >
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: props.p1,
p2: props.p2,
};
}
render() {
return ( <
div className = "game" >
<
div className = "game-board" >
<
Board p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/> < /
div >
<
/div>
);
}
}
class StartGame extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: '',
p2: '',
showGame: false
};
}
renderGame(p11, p22) {
debugger;
if (p11 && p22) {
this.setState({
showGame: true
});
}
}
render() {
return ( <
div className = "game-info" >
<
label > Player 1: < /label> <
input type = "text"
onChange = {
() => this.setState({
p1: event.target.value
})
}
defaultValue = {
this.state.p1
}
placeholder = "Player 1 Name" / >
<
br / > < br / >
<
label > Player 2: < /label> <
input type = "text"
onChange = {
() => this.setState({
p2: event.target.value
})
}
defaultValue = {
this.state.p2
}
placeholder = "Player 2 Name" / >
<
br / > < br / >
<
input type = "button"
value = "Start New Game"
onClick = {
() => {
this.renderGame(this.state.p1, this.state.p2);
}
}
/>
{
this.state.showGame && < Game
p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/>} < /
div >
);
}
}
// ========================================
ReactDOM.render( <
StartGame / > ,
document.getElementById('root')
);
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol,
ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="errors" style="
background: #c00;
color: #fff;
display: none;
margin: -20px -20px 20px;
padding: 20px;
white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
document.body.classList.add('mouse-navigation');
document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
if (e.keyCode === 9) {
document.body.classList.add('kbd-navigation');
document.body.classList.remove('mouse-navigation');
}
});
window.addEventListener('click', function(e) {
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
e.preventDefault();
}
});
window.onerror = function(message, source, line, col, error) {
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
errors.textContent += text + '\n';
errors.style.display = '';
};
console.error = (function(old) {
return function error() {
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
errors.style.display = '';
old.apply(this, arguments);
}
})(console.error);
</script>
it's easier to do this using a boolean to determine if you should render a component. in render game just set a variable to true on the state if both names are filled out.
class StartGame extends React.Component {
constructor(props){
super(props);
this.state={
p1: '',
p2:'',
};
// so this will refer to the component and not the function
this.renderGame = this.renderGame.bind(this);
}
renderGame(){
// if p1 and p2 then renderGame should be true and clear any error messages.
if (this.state.p1 && this.state.p2) {
this.setState({ renderGame: true, error: '' })
// else tell the user they need to enter player names.
} else {
this.setState({ error: 'Please enter player names.' })
}
}
render() {
return (
<div className="game-info">
{this.state.error}
<br/>
<label> Player 1: </label>
<input type="text"
onChange={() => this.setState({ p1: event.target.value })}
defaultValue={this.state.p1}
placeholder="Player 1 Name" />
<br /> <br />
<label> Player 2: </label>
<input type="text"
onChange={() => this.setState({p2:event.target.value})}
defaultValue={this.state.p2}
placeholder="Player 2 Name" />
<br /> <br />
<input type="button" value="Start New Game" onClick={this.renderGame}/>
// if the boolean is true then go ahead and render the game component
{this.state.renderGame && <Game p1={this.state.p1} p2={this.state.p2}/>}
</div>
);
}
}
function Square(props) {
return ( <
button className = "square"
onClick = {
props.onClick
} > {
props.value
} <
/button>
);
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
clicks: 0,
p1: props.p1,
p2: props.p2,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
let count = this.state.clicks;
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
clicks: ++count
});
}
renderSquare(i) {
return <Square
value = {
this.state.squares[i]
}
onClick = {
() => this.handleClick(i)
}
/>;
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X(' + this.state.p1 + ')' : 'O(' + this.state.p2 + ')');
}
let countclick;
countclick = this.state.clicks;
return ( <
div >
<
p > No.of Clicks: {
countclick
} < /p> <
div className = "status" > {
status
} < /div> <
div className = "board-row" > {
this.renderSquare(0)
} {
this.renderSquare(1)
} {
this.renderSquare(2)
} <
/div> <
div className = "board-row" > {
this.renderSquare(3)
} {
this.renderSquare(4)
} {
this.renderSquare(5)
} <
/div> <
div className = "board-row" > {
this.renderSquare(6)
} {
this.renderSquare(7)
} {
this.renderSquare(8)
} <
/div> < /
div >
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: props.p1,
p2: props.p2,
};
}
render() {
return ( <
div className = "game" >
<
div className = "game-board" >
<
Board p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/> < /
div >
<
/div>
);
}
}
class StartGame extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: '',
p2: '',
};
// so this will refer to the component and not the function
this.renderGame = this.renderGame.bind(this);
}
renderGame() {
// if p1 and p2 then renderGame should be true and clear any error messages.
if (this.state.p1 && this.state.p2) {
this.setState({
renderGame: true,
error: ''
})
// else tell the user they need to enter player names.
} else {
this.setState({
error: 'Please enter player names.'
})
}
}
render() {
return ( <
div className = "game-info" > {
this.state.error
} <
br / >
<
label > Player 1: < /label> <
input type = "text"
onChange = {
() => this.setState({
p1: event.target.value
})
}
defaultValue = {
this.state.p1
}
placeholder = "Player 1 Name" / >
<
br / > < br / >
<
label > Player 2: < /label> <
input type = "text"
onChange = {
() => this.setState({
p2: event.target.value
})
}
defaultValue = {
this.state.p2
}
placeholder = "Player 2 Name" / >
<
br / > < br / >
<
input type = "button"
value = "Start New Game"
onClick = {
this.renderGame
}
/>
{
this.state.renderGame && < Game p1 = {
this.state.p1
}
p2 = {
this.state.p2
}
/>} < /
div >
);
}
}
// ========================================
ReactDOM.render( <
StartGame / > ,
document.getElementById('root')
);
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol,
ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="errors" style="
background: #c00;
color: #fff;
display: none;
margin: -20px -20px 20px;
padding: 20px;
white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
document.body.classList.add('mouse-navigation');
document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
if (e.keyCode === 9) {
document.body.classList.add('kbd-navigation');
document.body.classList.remove('mouse-navigation');
}
});
window.addEventListener('click', function(e) {
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
e.preventDefault();
}
});
window.onerror = function(message, source, line, col, error) {
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
errors.textContent += text + '\n';
errors.style.display = '';
};
console.error = (function(old) {
return function error() {
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
errors.style.display = '';
old.apply(this, arguments);
}
})(console.error);
</script>
I forked your codepen and made the changes.
https://codepen.io/therj/pen/yLBJZJb
constructor(props){
super(props);
this.state={
p1: '',
p2:'',
player_set: false
};
Introduced a new state player_set.
You were handling onChange inline, I cloned it locally and React threw global-event error. Fixed by creating a handleChange method.
const handleChange = (event) => {
this.setState(
{...this.state,[event.target.name]: event.target.value,
}, ()=>{
this.setState({...this.state, 'player_set': this.state.p1 && this.state.p2})
})
}
I added name = "p1" and name = "p2", that's how single onChange will set state for both. I changed player_set state in callback tp setState, setting player_set and p1/p2 together might cause issues (I can't confirm, someone can comment on it, maybe?!).
Finally, in the StartGame:
{this.state.player_set &&
<Game
p1={this.state.p1}
p2={this.state.p2}
/>
}
This will render Game if player_set state is true!
You can use those states for player names as well.
When I move the page using the library react-native-tab-navigator of these navigators to move the componentDidMount page it can only be done once and after that, the lifecycle component does not work, ask for the solution: following my code.
I have opened an issue on Github, I have tried sending the state to the parent. I have used
react-native-tab-navigator version 0.3.4
class MainTab extends Component {
state = {
selectedTab: 'home'
};
render() {
return (
<
TabNavigator.Item selected = {
this.state.selectedTab === 'home'
}
title = "Home"
selectedTitleStyle = {
{
color: "#FF7158",
}
}
tabStyle = {
{
borderTopWidth: this.state.selectedTab === 'home' ? 3 : 0,
borderTopColor: '#FF7158',
backgroundColor: this.state.selectedTab === 'home' ? '#fff8f6' : '#FFFFFF'
}
}
renderIcon = {
() => < Image source = {
require('allComponents/images/ic_beranda.png')
}
style = {
{
width: 18,
height: 18
}
}
/>}
renderSelectedIcon = {
() => < Image source = {
require('allComponents/images/ic_beranda-actives.png')
}
style = {
{
width: 18,
height: 18
}
}
/>}
// renderBadge={() => <View style={{width: 20, height:20, backgroundColor:'#FF7158', borderRadius:50}}><Text style={{fontSize:12, textAlign:'center', color:'white', fontWeight:'600'}}>2}
onPress = {
() => this.setState({
selectedTab: 'home'
})
} >
<
/TabNavigator.Item> <
TabNavigator.Item
selected = {
this.state.selectedTab === 'profile'
}
title = "Pemesanan"
selectedTitleStyle = {
{
color: "#FF7158",
}
}
tabStyle = {
{
borderTopWidth: this.state.selectedTab === 'profile' ? 3 : 0,
borderTopColor: '#FF7158',
backgroundColor: this.state.selectedTab === 'profile' ? '#fff8f6' : '#FFFFFF'
}
}
renderIcon = {
() => < Image source = {
require('allComponents/images/ic_pemesanan-inactive.png')
}
resizeMode = 'stretch'
style = {
{
width: 18,
height: 18
}
}
/>}
renderSelectedIcon = {
() => < Image source = {
require('allComponents/images/ic_pemesanan-active.png')
}
resizeMode = 'stretch'
style = {
{
width: 18,
height: 18
}
}
/>}
onPress = {
() => this.setState({
selectedTab: 'profile'
})
} >
<
/TabNavigator.Item> <
TabNavigator.Item
selected = {
this.state.selectedTab === 'riwayat'
}
title = "Akun"
selectedTitleStyle = {
{
color: "#FF7158",
}
}
tabStyle = {
{
borderTopWidth: this.state.selectedTab === 'riwayat' ? 3 : 0,
borderTopColor: '#FF7158',
backgroundColor: this.state.selectedTab === 'riwayat' ? '#fff8f6' : '#FFFFFF'
}
}
renderIcon = {
() => < Image source = {
require('allComponents/images/ic_akun-inactive.png')
}
resizeMode = 'stretch'
style = {
{
width: 18,
height: 18
}
}
/>}
renderSelectedIcon = {
() => < Image source = {
require('allComponents/images/ic_akun-active.png')
}
resizeMode = 'stretch'
style = {
{
width: 18,
height: 18
}
}
/>}
onPress = {
() => this.setState({
selectedTab: 'riwayat'
})
} >
<
/TabNavigator.Item>
);
}
}
I expect that componentDidMount can function on TabBar.
componentDidMount won't trigger when it is inside a tabNavigator (expect for the first time the component gets mounted).
The reason is that when switching from a tab to another, every tab gets rendered a first time, leaving all the tabs rendered without unmounting them.
I don't know which navigator you are using, but usually you have a way to know when a screen gets 'focussed' and when it gets 'blurred'. Using those you can trigger a function when you have the screen passing from blurred to focussed.
I have following UI element on the top of my page:
|[Static1] [Dynamic1] [Dynamic2] [Dynamic3] [Static2]|
So Static1 is some logo component that sticks to the left, Static2 is some user menu component that sticks to the right.
Now inside of it I have a collection component that displays several dynamic elements loaded from the DB.
All is good, if there are not too much of those components, but if there are more, I don't wan't any linebreaks, only some fort of "More" menu, something like:
|[Static1] [Dynamic1] [Dynamic2] [Dynamic3] [Dynamic4][...][Static2]|
and when I click the [...] button I wan't a vertical list of the dynamic components.
The list of dynamic items is stored in an ElementList component, with following code:
React.createClass({
render() {
return (
<div ref="listparent">
{ this.props.elements.map((element) => {
return <Element
ref={"element-"+element.name}
key={element.name}
})}
</div>
)
}
});
this.props.elements is a collection passed as a prop. I tried something allong those lines, but it either didn't work or worked but not on each page refresh:
export default React.createClass({
getInitialState(){
return {
visibleElements: this.props.elements,
hiddenElements: []
}
},
componentDidMount() {
this.rearrange();
},
componentDidUpdate(){
this.rearrange();
},
rearrange(){
var element = ReactDOM.findDOMNode(this.refs.listparent);
let visibleElements = [];
let hiddenElements = [];
for(var i=0; i< this.props.elements.length; i++)
{
var currentElement = this.props.elements[i];
var domElement = ReactDOM.findDOMNode(this.refs["element-"+element.name]);
if(domElement) {
if (domElement.offsetTop + domElement.offsetHeight >
element.offsetTop + element.offsetHeight ||
domElement.offsetLeft + domElement.offsetWidth >
element.offsetLeft + element.offsetWidth - 200) {
hiddenElements.push(currentElement);
}
else {
visibleElements.push(currentElement);
}
}
}
if(this.state.visibleElements.length != visibleElements.length) {
this.setState({
visibleElements: visibleElements,
hiddenElements: hiddenElements
})
}
},
render() {
return (
<div ref="listparent">
{ this.state.visibleElements.map((element) => {
return <Element
ref={"element-"+element.name}
key={element.name} />
})}
{ this.state.hiddenElements.length >0 &&
<DropdownMenu
Header="..."
>
{ this.state.hiddenElements.map((element) => {
return <Element
ref={"element-"+element.name}
key={element.name} />
})}
</DropdownMenu>
}
</div>
)
}
});
Here is a rough jsFiddle with what I want to do: https://jsfiddle.net/3uf9r8ne/
Got it working, I don't know if that's the best solution, or how robust it is, but works for me at least for now.
JsFiddle: https://jsfiddle.net/1w6m1n6h/
var Dropdown = React.createClass({
getInitialState(){
return {
isOpen: false
}
},
componentWillMount() {
document.addEventListener('click', this.handleClick, false);
},
componentWillUnmount() {
document.removeEventListener('click', this.handleClick, false);
},
handleClick: function (e) {
var component = ReactDOM.findDOMNode(this.refs.component);
if (e.target == component || $(component).has(e.target).length) {
// Inside the component
}
else{
// Outide
this.setState({ isOpen: false});
}
},
render()
{
return (
<div ref="component" className="dropdown">
<div className="dropdown-button" onClick={() => this.setState({ isOpen : !this.state.isOpen})}>{this.props.Header}</div>
{
this.state.isOpen && (
<div className="dropdown-menu" onClick={() => this.setState({ isOpen : false})}>
{React.Children.map(this.props.children, (item) => item)}
</div>
)
}
</div>
);
}
});
var Card = React.createClass({
render() {
let className = "card";
if(this.props.isHidden)
className += " is-hidden";
return (
<div className={className}>{this.props.name}</div>
)
}
});
var Cards = React.createClass({
getInitialState() {
return {
vCards: [],
hCards: [],
lastSetCards: [],
preMounted: false,
laidOut: false
};
},
rearrange() {
_.throttle(this.setState({laidOut: false, preMounted: false}), 100);
},
componentDidMount() {
window.addEventListener('resize', this.rearrange);
},
componentWillUnmount() {
window.removeEventListener('resize', this.rearrange);
},
componentDidUpdate() {
if(this.props.cards.length != this.state.lastSetCards || !this.state.preMounted) {
this.setState({
lastSetCards: this.props.cards.length,
vCards: this.props.cards,
preMounted: true,
laidOut: false
});
}
if(this.state.preMounted && !this.state.laidOut) {
var element = ReactDOM.findDOMNode(this.refs.listparent);
let visibleCards = [];
let hiddenCards = [];
for(var i=0; i< this.props.cards.length; i++)
{
var card = this.props.cards[i];
var cardElement = ReactDOM.findDOMNode(this.refs["card-"+card]);
if(cardElement) {
if (cardElement.offsetTop + cardElement.offsetHeight >
element.offsetTop + element.offsetHeight ||
cardElement.offsetLeft + cardElement.offsetWidth >
element.offsetLeft + element.offsetWidth - 160) {
hiddenCards.push(card);
}
else {
visibleCards.push(card);
}
}
}
this.setState({
vCards: visibleCards,
hCards: hiddenCards,
laidOut: true
});
}
},
render() {
return (<div className="cards-top" ref="listparent">
<div className="cards" >
{this.state.vCards.map((c)=> <Card ref={"card-"+c} key={c} name={c} />)}
</div>
<Dropdown Header="MORE">
{this.state.hCards.map((c)=> <Card isHidden={true} key={c} name={c} />)}
</Dropdown>
</div>
)
}
});
var Hello = React.createClass({
getInitialState() {
return {
cards: ["one", "two" ]
};
},
componentDidMount() {
this.setState({
cards: ["one", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen"]
});
},
render: function() {
let addNew = () => {
this.state.cards.push("additional_"+this.state.cards.length);
this.setState({
cards: this.state.cards
})
};
return (
<div>
<div className="header">
<div className="logo">Logo</div>
<div className="user">User</div>
<Cards cards={this.state.cards} />
<div className="clear"></div>
</div>
<br/><br/>
<button onClick={addNew}>Add</button>
</div>);
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
.logo
{
float: left;
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
.user
{
float: right;
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
.header{
position: relative;
max-height: 10px;
height: 10px;
width: 100%;
}
.cards
{
position: relative;
display: inline-block;
vertical-align: top;
white-space: nowrap;
}
.clear
{
clear: both;
}
.card
{
display: inline-block;
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
.cards-top
{
display: block;
white-space: nowrap;
vertical-align: top;
width: 100%;
border: green 1px solid;
}
.dropdown
{
display: inline-block;
}
.is-hidden
{
display: block;
}
.dropdown-button
{
margin: 5px;
padding: 5px;
border: solid 1px blue;
}
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>