Implementing an online game in react - reactjs

I wrote a small game in React which consists of three components,
App <= MainInfo <= Game. The essence of the game is to press the START GAME button and quickly click on all the blue cubes that disappear after clicking. At the end, an alert window pops up with the time and number of cubes pressed, but there are bugs:
After loading, you can click on the blue cubes and they will disappear without pressing START GAME. (So you can even win the game with a zero time counter).
If you press START GAME a couple of times in a row, the counter is accelerated, and after winning, if you press the button, the time continues.
How to make a REFRESH button to restart the game after winning and not reload the entire page?
Link to game - https://quintis1212.github.io/react-game/build/index.html
import React from 'react';
import './App.css';
import MainInfo from './GameContent/MainInfo';
function App() {
return (
<div className="App">
<MainInfo />
</div>
);
}
export default App;
import React, {Component} from 'react';
import Game from './GameTable/Game';
class MainInfo extends Component {
state = {
count:0,
timer:0,
initGame: false
}
shouldComponentUpdate(){
return this.state.initGame;
}
logToConsole = (e)=> {
if (e.target.className === 'Element') {
this.setState((state)=>{
return {count: state.count + 1}
});
e.target.className = 'Element-empty';
console.log(e.target.className)
}
}
timerUpdated(){ this.setState((state)=>{
return {timer: state.timer + 1}
}); }
initGameHandler =()=>{
this.setState({initGame: true})
console.log('refreshHandler')
this.timerID=setInterval(() => { this.timerUpdated() }, 1000);
}
finishGameHandler = (score)=>{
console.log(score)
if(this.state.count === score-1) {
alert('-----GAME OVER-----'+
'YOUR TIME: '+this.state.timer+'seconds'+
' YOUR COUNT: '+this.state.count+'points');
clearInterval(this.timerID)
this.setState({initGame: false})
}
}
render(){
return(
<div>
<p>Timer : <strong>{this.state.timer} seconds </strong></p>
<p>Counter :<strong>{this.state.count}</strong></p>
<button onClick={this.initGameHandler}>START GAME</button>
<Game click={this.logToConsole} updateData={this.finishGameHandler} />
</div>
)
}
}
export default MainInfo;
import React,{Component} from 'react';
class Game extends Component {
shouldComponentUpdate(){
return false
}
render() {
let item;
let count = 0;
let arr = [];
for (let i = 0; i < 70; i++) {
arr.push(Math.floor(Math.random() * 10));
}
item = arr.map((el,i,arr) => {
count++
return el < 2 ? arr[i-1] < 4?<div key={count} className='Element-empty'></div>:<div onClick={(e)=>{this.props.click(e)}} key={count} className='Element'></div> : <div key={count} className='Element-empty'></div>
})
// console.log(item.filter(el => el.props.className == 'Element'))
let score = item.filter(el => el.props.className === 'Element')
let scoreLenhgth=score.length
return(
<div onClick={() => { this.props.updateData(scoreLenhgth)}} >
{item}
</div>
)
}
}
export default Game;

I made quite some changes but got it all to work, didn't put any comments on the changes so please comment if you have any questions.
function App() {
return (
<div className="App">
<MainInfo />
</div>
);
}
class MainInfo extends React.Component {
state = {
count: 0,
timer: 0,
initGame: false,
};
timerUpdated() {
this.setState(state => {
return { timer: state.timer + 1 };
});
}
initGameHandler = () => {
this.setState({ initGame: true });
this.timerID = setInterval(() => {
this.timerUpdated();
}, 1000);
};
gameClickHandler = (correct, itemsLeft) => {
this.setState(
{
count: this.state.count + (correct ? 1 : 0),
},
() => {
if (itemsLeft === 0 || !correct) {
alert(
'-----GAME OVER-----' +
'YOUR TIME: ' +
this.state.timer +
' seconds ' +
' YOUR COUNT: ' +
this.state.count +
' points'
);
clearInterval(this.timerID);
this.setState({
initGame: false,
count: 0,
timer: 0,
});
}
}
);
};
render() {
return (
<div>
<p>
Timer :{' '}
<strong>{this.state.timer} seconds </strong>
</p>
<p>
Counter :<strong>{this.state.count}</strong>
</p>
{this.state.initGame || (
<button onClick={this.initGameHandler}>
START GAME
</button>
)}
{this.state.initGame && (
<Game
click={this.logToConsole}
updateData={this.gameClickHandler}
blocks={2}
/>
)}
</div>
);
}
}
const shuffle = array => {
const copy = [...array];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
};
class Game extends React.PureComponent {
constructor(props) {
super(props);
this.state = this.setGame();
}
setGame = () => {
const arr = shuffle(
[...new Array(70)].map((_, i) => i)
);
const selected = arr.slice(0, this.props.blocks || 5);
return { arr, selected };
};
render() {
return (
<div
onClick={e => {
const itemClicked = Number(
e.target.getAttribute('data-id')
);
const correct = this.state.selected.includes(
itemClicked
);
this.props.updateData(
correct,
this.state.selected.length - (correct ? 1 : 0)
);
this.setState(state => ({
selected: state.selected.filter(
v => v !== itemClicked
),
}));
}}
>
{this.state.arr.map((_, i) => (
<div
key={i}
data-id={i}
className={
!this.state.selected.includes(i)
? 'Element-empty'
: 'Element'
}
></div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.App {
text-align: center;
}
.Element {
display: inline-block;
width: 40px;
height: 40px;
background-color: cornflowerblue;
border: 2px solid rgb(68, 209, 179);
}
.Element-empty {
display: inline-block;
width: 40px;
height: 40px;
background-color: rgba(158, 147, 100, 0.394);
border: 2px solid rgb(68, 209, 179);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

Implement Timer using React

import React from 'react';
import { nanoid } from 'nanoid';
import Confetti from 'react-confetti';
import { useStopwatch } from 'react-timer-hook';
import Die from './components/Die';
import './style.css';
import Timer from './components/Timer';
import Rolls from './components/rolls';
function App() {
const { seconds, minutes, hours, isRunning, start, pause, reset } =
useStopwatch({ autoStart: true });
const [dice, setDice] = React.useState(allNewDice());
const [tenzies, setTenzies] = React.useState(false);
const [time, setTime] = React.useState({});
const [hasStarted, setHasStarted] = React.useState(() => false);
const [bestTime, setBestTime] = React.useState(() =>
localStorage.getItem('best-time')
? JSON.parse(localStorage.getItem('best-time'))
: ''
);
React.useEffect(() => {
setTime({ seconds: seconds, minutes: minutes, hours: hours });
}, [seconds, minutes, hours]);
React.useEffect(() => {
const firstValue = dice[0].value;
const allHeld = dice.every((die) => die.isHeld === true);
const allEqual = dice.every((die) => die.value === firstValue);
if (allEqual && allHeld) {
setTenzies(true);
//if (time < bestTime || !bestTime) {
// setBestTime(time);
// localStorage.setItem('best-time', time);
//}
}
}, [dice]);
function generateNewDie() {
return { value: Math.ceil(Math.random() * 6), isHeld: false, id: nanoid() };
}
function allNewDice() {
const newDice = [];
for (let i = 0; i < 10; i++) {
newDice.push(generateNewDie());
}
return newDice;
}
function rollDice() {
if (!tenzies) {
setDice((oldDice) =>
oldDice.map((die) => {
return die.isHeld === true ? die : generateNewDie();
})
);
} else {
setTenzies(false);
setDice(allNewDice());
}
}
function holdDice(id) {
setDice((oldDice) =>
oldDice.map((die) => {
return die.id === id ? { ...die, isHeld: !die.isHeld } : die;
})
);
}
const diceElements = dice.map((die) => (
<Die
key={die.id}
value={die.value}
isHeld={die.isHeld}
holdDice={() => holdDice(die.id)}
/>
));
return (
<main>
{tenzies && <Confetti />}
<h1 className="title">Tenzies</h1>
<p className="instructions">
Roll until all dice are the same. Click each die to freeze it at its
current value between rolls.
</p>
<div className="dice-container">{diceElements}</div>
<div className="row">
<div className="total-timer">
<Timer best={true} timer={time} />
<Timer timer={time} />
</div>
<button type="button" className="roll-dice" onClick={rollDice}>
{tenzies ? 'New Game' : 'Roll'}
</button>
<div className="total-rolls">
<Rolls best={true} />
<Rolls />
</div>
</div>
</main>
);
}
export default App;
Is it a good practice having Effect Hook render my page every second of the timer??
Timer
export default function Timer(props) {
const styles = {
color: props.best ? '#59e391' : 'white',
};
return (
<div className="timer" style={styles}>
{props.best ? <span>Best Time : </span> : <span>Your Time : </span>}
<span className="digits">
{props.timer.hours ? props.timer.hours : '00'}
</span>
<span>:</span>
<span className="digits">
{props.timer.minutes ? props.timer.minutes : '00'}
</span>
<span>:</span>
<span className="digits">
{props.timer.seconds ? props.timer.seconds : '00'}
</span>
</div>
);
}

How to create new divs inside for loop and show the result in React

I'm working on a burger project.
When add buttons are clicked, the ingredient should be added. When remove buttons are clicked, the ingredient should be removed.
The problem I am having right now is when the buttons are clicked, nothing has happened.
Here's how state is setup:
constructor(props) {
super(props)
this.state = {
lettuce: 0,
tomato: 0,
cheese: 0,
meat: 0
}
}
The button:
let ingredients = ['lettuce', 'tomato', 'cheese', 'meat']
{
ingredients.map((ingredient, index) => {
return(
<div key={index}>
<p>{ingredient}</p>
<div className="ingredientButtons">
<button onClick={()=>this.addRemoveIngredient('add', ingredient)} className="ingrBtn">Add</button>
<button onClick={()=>this.addRemoveIngredient('remove', ingredient)} className="ingrBtn">Remove</button>
</div>
</div>
)
})
}
Button Handler:
addRemoveIngredient = (action, ingredient) => {
let stateValue = this.state[ingredient];
console.log(`before: ${stateValue}`)
action === 'add' ? stateValue = stateValue + 1 : stateValue = stateValue - 1
console.log(`after: ${stateValue}`)
this.setState({
[ingredient]: stateValue < 0 ? 0 : stateValue
})
console.log(this.state)
}
When Add under lettuce is clicked.
Methods that show the ingredients
showIngredient = (ingredient) => {
let burger = []
for(let i = 0; i < this.state[ingredient]; i++){
burger.push(<div key = {i} className={ingredient}></div>)
}
return burger
}
burger = () => {
let ingredients = ['lettuce', 'tomato', 'cheese', 'meat']
ingredients.map((ingredient, index) => {
return (
<div key ={index}>
{
this.showIngredient(ingredient)
}
</div>
)
})
The css that gets the image
.lettuce{
background-image: url("../assets/lettuse.jpg");
background-size: 300px;
height: 27px;
width: 100%;
display: table;
background-repeat: no-repeat;
background-position: center;
Show ingredients in render function
<div className="burgerIngredients">
<div className="topSide"></div>
{this.burger()}
<div className="bottomSide"></div>
</div>
}
I'm not sure why nothing is showing.
Thank you for your time.
I have tried to replicate the functionality but looks like fine for me. Can you update here what is wrong. Also check console its updating correct:
Code:
import React from "react";
import "./style.css";
class App extends React.Component{
constructor(props) {
super(props)
this.state = {
lettuce: 0,
tomato: 0,
cheese: 0,
meat: 0
}
}
addRemoveIngredient = (action, ingredient) => {
let stateValue = this.state[ingredient];
action === 'add' ? stateValue = stateValue + 1 : stateValue = stateValue - 1
this.setState({
[ingredient]: stateValue >= 0 ? stateValue : 0
},() => {
console.log(this.state)
})
}
showIngredient = (ingredient) => {
let burger = []
for(let i = 0; i < this.state[ingredient]; i++){
burger.push(<div key = {i} className={ingredient}>{this.state[ingredient]}</div>)
}
return burger
}
burger = () => {
let ingredients = ['lettuce', 'tomato', 'cheese', 'meat']
return ingredients.map((ingredient, index) => {
return (
<div key ={index}>
{
this.showIngredient(ingredient)
}
</div>
)
})
}
renderButton = () => {
let ingredients = ['lettuce', 'tomato', 'cheese', 'meat']
return ingredients.map((ingredient, index) => {
return(
<div key={index}>
<p>{ingredient}</p>
<div className="ingredientButtons">
<button onClick={()=>this.addRemoveIngredient('add', ingredient)} className="ingrBtn">Add</button>
<button onClick={()=>this.addRemoveIngredient('remove', ingredient)} className="ingrBtn">Remove</button>
</div>
</div>
)
})
}
render(){
return(<div className="burgerIngredients">
<div className="topSide"></div>
{this.burger()}
<div className="bottomSide"></div>
{this.renderButton()}
</div>)
}
}
export default App;
Demo: https://stackblitz.com/edit/react-u2uzjb

Changing styles when scrolling React

I want to add the scrolling effect. At the start, the elements have the opacity: 0.2 property. When element is reached in the browser window, it is to replace the property with opacity: 1. At this moment, when I scroll, all elements change the property to opacity: 1. How to make this value when element is reached in the browser, the rest of the elements have the property opacity: 0.2
class QuestionListItem extends Component {
constructor() {
super();
this.state = {
opacity: 0.2,
};
}
componentDidMount = () => {
window.addEventListener('scroll', () => {
this.setState({
opacity: 1,
});
});
};
render() {
const { question } = this.props;
const { opacity } = this.state;
return (
<div>
<li
key={question.id}
className="Test__questions-item"
style={{ opacity: `${opacity}` }}
ref={
(listener) => { this.listener = listener; }
}
>
<p>
{question.question}
</p>
<QuestionAnswerForm />
</li>
</div>
);
}
}
I want effect like this https://anemone.typeform.com/to/jgsLNG
A proper solution could look like this. Of course, this is just a concept. You can fine-tune the activation/deactivation logic using props from getBoundingClientRect other than top (e.g. height, bottom etc).
Important that you should not set the component's state on every single scroll event.
const activeFromPx = 20;
const activeToPx = 100;
class ScrollItem extends React.Component {
state = {
isActive: false
}
componentDidMount = () => {
window.addEventListener('scroll', this.handleScroll);
this.handleScroll();
};
handleScroll = () => {
const { top } = this.wrapRef.getBoundingClientRect();
if (top > activeFromPx && top < activeToPx && !this.state.isActive) {
this.setState({ isActive: true });
}
if ((top <= activeFromPx || top >= activeToPx) && this.state.isActive) {
this.setState({ isActive: false });
}
}
setWrapRef = ref => {
this.wrapRef = ref;
}
render() {
const { isActive } = this.state;
return (
<div
className={`scroll-item ${isActive && 'scroll-item--active'}`}
ref={this.setWrapRef}
>
{this.props.children}
</div>
)
}
}
class ScrollExample extends React.Component {
render() {
return (
<div className="scroll-wrap">
<ScrollItem>foo</ScrollItem>
<ScrollItem>bar</ScrollItem>
<ScrollItem>eh</ScrollItem>
</div>);
}
}
ReactDOM.render(<ScrollExample />, document.getElementById('root'))
.scroll-wrap {
height: 300vh;
background: lightgray;
padding-top: 55px;
}
.scroll-item {
height: 60vh;
background: lightcyan;
margin: 10px;
opacity: 0.2;
}
.scroll-item--active {
opacity: 1;
}
<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="root"></div>
You can include an isInViewport-like implementation as this one: https://gist.github.com/davidtheclark/5515733 then use it on your component.
componentDidMount = () => {
window.addEventListener('scroll', (event) => {
if (isElementInViewport(event.target) {
this.setState({
opacity: 1,
});
}
});
};
There's also read-to-use react-addons for this: https://github.com/roderickhsiao/react-in-viewport

How do you reset a loading bar in react JS?

Just a quick problem here. Was finishing a project that I had asked before about here (How to pass values between components in React JS?) and had another question. In the machine component here I need a loading bar, and the teacher showed us this as an example: https://codepen.io/AaronCoding/pen/GjVaGp. I have modified it so it works when values are pressed rather than off a button press but I need it to reset so when the user presses another button it "loads" again. I have tried resting the progress variable to 0 but that just seems to cause the loading bar to get stuck in a loop.
import React, { Component } from 'react';
class CoffeeMachine extends Component {
state = {
brewing: "Now Brewing: Nothing",
progress: 0,
speed: 1,
color: "#ff0050",
frame: this.frame.bind(this),
green: this.green.bind(this),
red: this.red.bind(this)
}
frame() {
if (this.state.progress < 100){
this.setState((prevState, props) => ({
progress: prevState.progress + this.state.speed,
color: "#" + this.red() + this.green() + "50"
}));
console.log(this.state.color);
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
green() {
let progress = this.state.progress;
progress *= 2.55;
progress = Math.round(progress);
progress = progress.toString(16);
return progress;
}
red() {
let progress = this.state.progress;
progress *= 2.55;
progress = Math.round(progress);
progress = 255 - progress;
progress = progress.toString(16);
return progress;
}
brewCoffee() {
this.setState({brewing: "Now brewing: " + this.props.coffee})
setTimeout(() => {this.brewingComplete()}, 5000);
this.interval = setInterval(() => this.frame(), 50);
}
brewingComplete() {
this.setState({brewing: this.props.coffee + " is done"})
setTimeout(() => {this.brewingComplete()}, 5000);
}
componentWillUpdate(nextProps,nextState) {
if (this.props.coffee !== null && this.state === nextState) {
setTimeout(() => {this.brewCoffee()}, 3000);
}
}
render(){
return(
<div>
{ this.state.brewing}
<div id="myBar" style={{
width: this.state.progress + "%",
backgroundColor: this.state.color
}}>
<div id="label">Loaded {this.state.progress}%</div>
</div>
</div>
)
}
}
export default CoffeeMachine;
In your componentWillUpdate() make sure no brewing is running by checking if progress is equal to zero.
You should also check if the choice of coffee to brew has changed in there.
Then you may clear the interval and set the progress to zero.
Make sure you say brewing is done when it is really done because the timeout for that continues running.
Also, you can't bind your methods in the state. You may bind them directly by using fat arrows or bind them in the constructor (old fashioned).
I would also advise to store brewingDelay and brewingTime in a const as they are used several times for matters that must be "synchronized".
import React, { Component } from "react";
const brewingDelay = 2000;
const brewingTime = 5000;
const initialColor = "#ff0050";
class CoffeeMachine extends Component {
state = {
brewing: "Now Brewing: Nothing",
progress: 0,
speed: 1,
color: initialColor,
frame: null,
green: null,
red: null
};
frame = () => {
if (this.state.progress < 100) {
this.setState((prevState, props) => ({
progress: prevState.progress + this.state.speed,
color: "#" + this.red() + this.green() + "50"
}));
console.log(this.state.color);
}
};
componentWillUnmount() {
clearInterval(this.interval);
}
green = () => {
let progress = this.state.progress;
progress *= 2.55;
progress = Math.round(progress);
progress = progress.toString(16);
return progress;
};
red = () => {
let progress = this.state.progress;
progress *= 2.55;
progress = Math.round(progress);
progress = 255 - progress;
progress = progress.toString(16);
return progress;
};
brewCoffee = () => {
this.setState({ brewing: "Now brewing: " + this.props.coffee });
setTimeout(() => {
this.brewingComplete();
}, brewingTime);
this.interval = setInterval(() => this.frame(), 50);
};
brewingComplete = () => {
if (this.state.progress >= 99)
this.setState({ brewing: this.props.coffee + " is done" });
setTimeout(() => {
this.brewingComplete();
}, brewingTime);
};
componentWillUpdate(nextProps, nextState) {
if (
this.props.coffee !== undefined &&
nextProps.coffee !== this.props.coffee &&
this.state.progress !== 0
) {
clearInterval(this.interval);
setTimeout(() => {
this.setState({ progress: 0, color: initialColor }, () => {});
}, brewingDelay);
}
if (this.props.coffee !== null && this.state === nextState) {
setTimeout(() => {
this.brewCoffee();
}, brewingDelay);
}
}
render() {
return (
<div>
{this.state.brewing}
<div
id="myBar"
style={{
width: this.state.progress + "%",
backgroundColor: this.state.color
}}
>
<div id="label">Loaded {this.state.progress}%</div>
</div>
</div>
);
}
}
class ParentChoice extends React.PureComponent {
state = {};
render() {
return (
<React.Fragment>
<button onClick={() => this.setState({ coffee: "Arabica" })}>
Arabica
</button>
<button onClick={() => this.setState({ coffee: "Robusta" })}>
Robusta
</button>
<CoffeeMachine coffee={this.state.coffee} />
</React.Fragment>
);
}
}
export default ParentChoice;

React props of parent are not passed to child component

I'm passing props to a child component, from a parent component that receive this same prop from his own parent.
For some reason, when the parent props get updated, this update does not affect the child component.
The component in itself is very basic:
here's the parent :
import React, { Component } from 'react'
import styled from 'styled-components'
import { Icon } from 'antd'
import BaseProductSelector from '../BaseProductSelector'
import BaseProductPreview from '../BaseProductPreview'
import FullDesignSelector from '../FullDesignSelector'
import ColorPicker from '../ColorPicker'
import GeneratorProgress from '../GeneratorProgress'
import GeneratorError from '../GeneratorError'
import BPDetails from './BPDetails'
const GeneratorFlow = styled.div`
/*background-color: #eeeeee;*/
padding: 0 60px;
h3 {
color: #444;
}
.innerGenFlow {
padding: 15px;
border-radius: 5px;
box-shadow: 0 20px 40px -14px rgba(0, 0, 0, 0.35);
}
`
export default class ProductGeneratorFlow extends Component {
constructor(props) {
super()
let colors
if (props.product) {
colors = props.product.variations.filter(p => p.type === 'color')
} else {
colors = []
// colors.push(props.baseProduct.variations.filter(p => p.type === 'color')[0])
}
this.state = {
pickedColors: colors,
done: false,
error: false,
}
this.setStep = this.setStep.bind(this)
this.setKey = this.setKey.bind(this)
this.showBP = this.showBP.bind(this)
this.hideBP = this.hideBP.bind(this)
this.onChange = this.onChange.bind(this)
this.toggleColor = this.toggleColor.bind(this)
this.toggleAll = this.toggleAll.bind(this)
this.productCreated = this.productCreated.bind(this)
this.productPending = this.productPending.bind(this)
this.setFirstColor = this.setFirstColor.bind(this)
this.displayError = this.displayError.bind(this)
}
setStep(step) {
this.setState({ step })
}
showBP() {
this.setState({ BPDisplayed: true })
}
hideBP() {
this.setState({ BPDisplayed: false })
}
getBaseProd() {
const bpid = this.props.product.supplierBaseProductId
const result = this.props.base_products.filter(obj => obj._id === bpid)
return result[0]
}
setKey(activeKey) {
this.setState({
activeKey,
})
}
onChange(activeKey) {
this.setState({
activeKey,
})
}
productPending() {
this.setState({
done: false,
error: false,
})
this.props.showBP()
}
productCreated() {
this.props.displaySuccess()
this.setState({ done: true })
}
displayError() {
this.setState({ error: true })
}
toggleColor(color) {
let pickedColors = this.state.pickedColors
if (this.state.pickedColors.includes(color)) {
// console.log(pickedColors.filter(i => i != color).length)
pickedColors = pickedColors.filter(i => i != color)
} else {
pickedColors.push(color)
}
this.setState({
pickedColors,
})
}
test(id) {
this.setState({picked: true})
this.props.select(id)
}
toggleAll(value) {
if (value === true) {
this.setState({
pickedColors: this.props.baseProduct.variations.filter(p => p.type === 'color'),
})
} else {
this.setState({ pickedColors: [] })
}
}
setFirstColor() {
if (this.state.pickedColors.length > 0) {
this.props.setVariation(this.state.pickedColors[0])
}
}
render() {
if (this.state.error) {
return (
<GeneratorError
showBP={this.props.showBP}
reset={this.productPending}
/>
)
}
if (this.state.done) {
return (
<GeneratorProgress
active
showBP={this.props.showBP}
reset={this.productPending}
/>
)
}
if (this.props.product) {
return (
<GeneratorFlow>
<FullDesignSelector
designs={this.props.designs}
select={this.test}
addedDesigns={this.props.addedDesigns}
getImage={this.props.getImage}
removeDesign={this.props.removeDesign}
active
printingZone={this.props.printingZone}
setStep={this.setStep}
showBP={this.showBP}
showDS={this.props.showDS}
setKey={this.setKey}
/>
<ColorPicker
baseProduct={this.props.baseProduct}
product={this.props.product}
picked={this.state.pickedColors}
toggleColor={this.toggleColor}
variation={this.props.variation}
selectAll={this.toggleAll}
toggleFirstColor={this.props.toggleFirstColor}
setVariation={this.props.setVariation}
selectedColor={
this.props.variation ? this.props.variation.value : null
}
setPreviewColor={this.props.setVariation}
/>
<BaseProductPreview
addedDesigns={this.props.addedDesigns}
size={this.props.size}
shop={this.props.shop}
printingZone={this.props.printingZone}
picked={this.state.pickedColors}
previews={this.props.previews}
product={this.props.product}
setDone={this.productCreated}
baseProduct={this.getBaseProd()}
displaySuccess={this.props.displaySuccess}
generatorError={this.displayError}
status='edition'
setKey={this.setKey}
products={this.props.products}
productLoading={this.props.productLoading}
/>
</GeneratorFlow>
)
}
return (
<GeneratorFlow>
<ColorPicker
picked={this.state.pickedColors}
toggleColor={this.toggleColor}
baseProduct={this.props.baseProduct}
toggleFirstColor={this.setFirstColor}
variation={this.props.variation}
selectAll={this.toggleAll}
setVariation={this.props.setVariation}
selectedColor={
this.props.variation ? this.props.variation.value : null
}
setPreviewColor={this.props.setVariation}
/>
<FullDesignSelector
designs={this.props.designs}
select={this.props.select}
addedDesigns={this.props.addedDesigns}
getImage={this.props.getImage}
printingZone={this.props.printingZone}
removeDesign={this.props.removeDesign}
active
setStep={this.setStep}
showBP={this.showBP}
showDS={this.props.showDS}
setKey={this.setKey}
/>
<BaseProductPreview
addedDesigns={this.props.addedDesigns}
baseProduct={this.props.baseProduct}
generatorError={this.displayError}
size={this.props.size}
displaySuccess={this.props.displaySuccess}
shop={this.props.shop}
picked={this.state.pickedColors}
setDone={this.productCreated}
printingZone={this.props.printingZone}
previews={this.props.previews}
setPreview={this.props.setPreview}
status='creation'
setStep={this.setStep}
products={this.props.products}
productLoading={this.props.productLoading}
/>
</GeneratorFlow>
)
}
}
And here is the only part thst uses this prop in the child
import React, { Component } from 'react'
import styled from 'styled-components'
import toPx from 'unit-to-px'
import _ from 'lodash'
import { LocalForm, Control } from 'react-redux-form'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { toast } from 'react-toastify'
import { Input, Select, Icon, Tooltip } from 'antd'
import s3 from '../../../../../services/s3'
import theme from '../../../../../theme/theme'
import Alert from '../../../../../components/Alert'
import { createProduct, modifyProduct } from '../../../../../modules/products'
// import ProductImage from '../../../../../components/ProductImage'
class BaseProductPreview extends Component {
constructor(props) {
super(props)
let colors
if (props.product) {
colors = Object.assign([], props.product.variations.filter(v => v.type === 'color'))
} else {
colors = []
}
this.state = {
name: props.product ? props.product.name : '',
displayDescription: props.product ? Object.assign({}, props.product.displayDescription) : {},
collections: (props.product && props.product.collections) ? props.product.collections : [],
pricing: props.product ? Object.assign({}, props.product.pricing) : { margin: 0 },
elevdone: false,
i: 0,
colors,
}
this.createInnerProduct = this.createInnerProduct.bind(this)
this.getPrice = this.getPrice.bind(this)
}
oneColor() {
if (this.props.baseProduct.variations && this.state.colors) {
let colorAlert
if (this.state.colorAlertShown) {
colorAlert = <Alert message='Choisissez au moins une couleur' type='error' />
}
const colorsBp = this.props.baseProduct.variations.filter(v => v.type === 'color')
if (colorsBp.length <= 1) {
return ''
}
return (
<div>
<p>Choix de couleur :</p>
{ colorAlert }
<span
className='bullet-color'
>
{this.getColorsRef(this.props.baseProduct).map(value =>
(<div
onClick={() => { this.toggleColor(value) }}
className={this.colorIsInProduct(value)}
style={{ backgroundColor: value }}
/>))}
</span>
</div>)
}
return null
}
getColorsRef() {
return this.props.baseProduct.variations.filter(v => v.type === 'color').map(a => a.value)
}
colorIsInProduct(couleur) {
// true/false
let active
if (this.state.colors.find(v => v.value === couleur)) {
active = 'active-color'
}
return active
}
toggleColor(couleur) {
// const item = this.state.item
const colors = this.state.colors
// si on a deja la couleur dans le produit on l'enlève
if (colors.find(v => v.value === couleur) && colors.length > 1) {
// je retire cette couleur des varaitions de mon produits
const index = colors.indexOf(colors.find(v => v.value === couleur))
colors.splice(index, 1)
} else if (colors.find(v => v.value === couleur)) {
this.setState({ colorAlertShown: true })
} else {
// on va chercher la variation couleur corespondante
// dans le base product et on la copie dans le product
this.setState({ colorAlertShown: false })
colors.push(this.props.baseProduct.variations.find(v => v.value === couleur))
}
this.setState({ colors })
// TODO on change la couleur du mockup
}
getJsonsObjects(printingZones) {
// INITIATE EMPTY JSON OBJECT
const jsonsArray = {}
// GET CURRENT CANVAS
printingZones.map((item) => {
const y = document.getElementById(`${item}-canvas`).fabric
const helper = _.filter(y.getObjects(), { clipFor: 'layer' })[0]
if (helper) {
helper.set({ stroke: 'transparent' })
}
jsonsArray[item] = y.toJSON(['height'])
})
return jsonsArray
}
getCustomizationPrice() {
let customizationPrice = 0
Object.keys(this.props.baseProduct.printingZone).map((item) => {
const y = document.getElementById(`${item}-canvas`).fabric
const items = y.getObjects()
if (items.length > 1) {
customizationPrice = customizationPrice + 5
}
})
customizationPrice = customizationPrice - 5
if (customizationPrice < 0) {
customizationPrice = 0
}
return customizationPrice
}
getAction() {
return (<p>Créer mon produit</p>)
}
marginValidation(value) {
let returned_value = value
if (value == '') {
returned_value = 0
}
if (!value) {
returned_value = 0
} else if (value > 100) {
// TODO Show moreThan100Alert
returned_value = 100
}
const pricing = Object.assign({}, this.state.pricing, { margin: returned_value })
this.setState({ pricing })
}
validForm() {
if (this.state.name && this.props.picked.length > 0 && this.state.pricing.margin >= 0 && this.props.addedDesigns.length > 0) {
return false
}
return true
}
getPrice() {
const position_print = this.props.addedDesigns.map(d => {
return d.position
})
// uniq(position_print)
const count = []
position_print.map((position) => {
if (count.indexOf(position) === -1) {
count.push(position)
}
})
if (count.length <= 1) {
return (
<div>
<p className='price'>Cout de production <span>{this.props.baseProduct.unitPrice} €</span></p>
<div className='price-marge'>Vos bénéfices <span className='requiredField2'>*</span>
<Control.text
component={Input}
className='inputMarge'
model='.margin'
value={this.state.pricing.margin}
onChange={(e) => {
this.marginValidation(e.target.value.replace(',', '.'))
}}
/>
</div>
<hr />
<div className='price-total'>
{`
${parseFloat(this.props.baseProduct.unitPrice)
+
parseFloat(this.state.pricing.margin)} €`}
</div>
</div>
)
}
if (count.length > 1) {
return (
<div>
<p className='price'>Cout de production <span>{this.props.baseProduct.unitPrice} €</span></p>
<p className='price'>Impression supplémentaire <span>5 €</span></p>
<div className='price-marge'>Vos bénéfices <span className='requiredField2'>*</span>
<Control.text
component={Input}
className='inputMarge'
model='.margin'
value={this.state.pricing.margin}
onChange={(e) => {
this.marginValidation(e.target.value.replace(',', '.'))
}}
/>
</div>
<hr />
<div className='price-total'>
{`
${parseFloat(this.props.baseProduct.unitPrice)
+
parseFloat(this.state.pricing.margin) + parseFloat(5)} €`}
</div>
</div>
)
}
return null
}
getCategory() {
if (this.props.baseProduct.category.fr[0] === 'Homme' && this.props.baseProduct.category.fr[1] === 'Femme') {
return (<span>Unisex</span>)
}
if (this.props.baseProduct.category.fr[0] === 'Homme') {
return (<span>Homme</span>)
}
if (this.props.baseProduct.category.fr[0] === 'Femme') {
return (<span>Femme</span>)
}
return null
}
showElevio() {
if (this.state.i < 5) {
this.state.i = this.state.i + 1
setTimeout(() => {
if (this.props.productLoading.loading === true || this.props.products.length === 0) {
if (this.props.products.length === 0 && this.state.elevdone === false) {
return (
window._elev.openArticle(263),
this.setState({ elevdone: true })
)
} return null
}
if (this.props.productLoading.loading === false) {
this.showElevio()
}
return null
}, 500)
} return null
}
render() {
const { Option } = Select
const children = []
if (this.props.shop.settings.collections) {
this.props.shop.settings.collections.map((collec, i) => {
children.push(<Option key={collec.name ? collec.name : i}>{collec.name}</Option>)
return null
})
}
this.showElevio()
return (
<StyledBaseProductPreview>
<h2>Description</h2>
<LocalForm
onSubmit={() => this.createInnerProduct()}
>
<div className='form-step'>
<p className='advice-name'>
<Tooltip title='Figurera sur la fiche produit'>
<span>{this.props.baseProduct.subCategory.fr} {this.getCategory()}</span>
</Tooltip>
</p>
<p>Nom <span className='requiredField'>*</span></p>
<Control.text
component={Input}
model='.name'
placeholder='Nom du produit'
value={this.state.name}
onChange={(e) => {
this.setState({ name: e.target.value })
}}
/>
</div>
<div className='form-step'>
<p>Description</p>
<Control.textarea
className='productDescription'
model='.displayDescription'
placeholder='Description du produit'
value={this.state.displayDescription.fr}
onChange={(e) => {
const new_item = Object.assign({}, this.state.displayDescription)
new_item.fr = e.target.value
this.setState({ displayDescription: new_item })
}}
/>
</div>
<div className='form-step'>
<p>Collection(s)</p>
<Select
mode='multiple'
className='styledSelect'
placeholder='Pas de collection'
notFoundContent='Pas de collection'
value={this.state.collections}
style={{ width: '100%' }}
onSearch={(e) => {
this.setState({ toCreate: e })
}}
onChange={(e) => {
this.setState({ collections: e })
}}
>
{children}
</Select>
</div>
<div className='form-step pricingForm'>
<h2>Prix </h2>
<hr />
{this.getPrice()}
</div>
<Crumpet type='submit' className='superCrumpet' disabled={this.validForm()}>
{this.getAction()}
</Crumpet>
</LocalForm>
</StyledBaseProductPreview>
)
}
}
const mapStateToProps = (state, ownProps) => {
console.log(state); // state
console.log(ownProps); // undefined
return({
user: state.user,
addedDesigns: ownProps.addedDesigns,
})
}
const mapDispatchToProps = dispatch => bindActionCreators({
createProduct,
modifyProduct,
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps,
)(BaseProductPreview)
When inspecting these elements, I can see the parent getting updated :
And now, the funny part: when parent props get updated, the first two child component props are updated as well, but BaseProductPreview doesn't !
However, this gets updated as soon as I change the state of the child component.
How comes so ? How can the state update the component props ?
React only act on state not on props. whenever you change state UI gets update with latest changes. bind your props to state in child component and use componentWillReceiveProps(props) to change child state when props get updated in parent component.
Well the only solution I found was to connect a higher component with redux, so that none of the children were redux-related anymore, and then pass the reducer function through props to that child.
Without redux, my component gets updated properly.

Resources