Related
In the TableAndInfo Component, each row's parent element is rendered by the renderEls function and the content is returned by the renderSection function. This passes in the content in the sections property as a parameter, as well as the class of the parent container. Passing the props value of date, time, and current in the sections array results in a successful initial render, but they do not continue updating when the state changes. I have inspected the code with the React Developer Tools and I see that the state is being updated by the functions defined in the App function and other components. How do I ensure that the grandchildren elements are re-rendered when the state is updated? Sorry if this doesn't make sense, I was having trouble trying to explain the problem. Thanks in advance!
function App() {
var [component, updateView] = useState('ServerFunctions');
var updateDateAndTime = function(formatDate) {
setInterval(function() {
if (document.getElementsByClassName('date')[0] && document.getElementsByClassName('time')[0]) {
updateDate(formatDate('date'));
updateTime(formatDate('time'));
}
}, 1000);
};
useEffect(() => {
updateDateAndTime(formatDate);
});
var [date, updateDate] = useState(formatDate('date'));
var [time, updateTime] = useState(formatDate('time'));
switch(component) {
case 'Welcome':
return (<Welcome updateView={updateView} date={date} time={time} backspacePinpad={backspacePinpad} />);
case 'ServerFunctions':
return (<ServerFunctions updateView={updateView} date={date} time={time} />);
default:
return null;
}
}
class TablesAndInfo extends React.Component {
sections = [[['info-row pct-space-b', 'flex between full-h'], {
1: ['Menu', 'button', 'gray white-f clickable roundish quarter-w label clickable'],
2: [this.props.current, 'div', 'f-override white-b small left three-quarter-w no-txt-overflow'],
}], [['info-table full-h', 'flex full-h'], {
1: ['Another Round', 'button', 'info-button blue white-f clickable'],
2: ['Select All', 'button', 'info-button gray white-f clickable'],
3: ['Name Check', 'button', 'info-button yellow clickable'],
}], [['tables-section full-h', 'tables-section full-h white-b'], {
}], [['new-table-b full-h', 'new-table-b med single round label full-h full-w'], {
1: ['New Table', 'button', 'blue white-f med single clickable round label clickable full-h full-w']
}]];
renderEls(num, classes) {
return (
<div className={classes}>
{this.sections[num].map((child, key) => {
if (typeof child === 'object' && !(child instanceof Array)) {
return (
<div className={this.sections[num][0][0]}>{this.renderSection(child, this.sections[num][0][1], key)}</div>
)
} else {
return null;
}
})}
</div>
)
}
renderSection(obj, parentClass) {
return (
<div className={parentClass}>
{Object.keys(obj).map((key, i) => {
if (obj[key][1] === 'button') {
return (
<button key={i} className={"flex center " + obj[key][2]}>{obj[key][0]}</button>
)
} else {
return (
<div key={i} className={"flex center " + obj[key][2]}>{obj[key][0]}</div>
)
}
})}
</div>
)
}
render() {
return (
<div className="tables space-r">
<div className="info tables-section">
{this.renderEls(0, 'info-table info-r')}
{this.renderEls(1, 'info-table info-b')}
</div>
{this.renderEls(2, 'table-view tables-section full-h pct-space-b')}
{this.renderEls(3, 'new-table')}
</div>
)
}
class ServerFunctions extends React.Component {
return (
<div className="App ServerFunctions">
<Header signOff={this.signOff} renderBttnRow={this.renderBttnRow} />
<div className="container order-control flex space-b">
<SelectedItems />
<TablesAndInfo current={this.state.current} date={this.props.date} time={this.props.time} />
<Functions current={this.state.current} />
</div>
<Footer current={this.state.current} renderBttnRow={this.renderBttnRow} />
</div>
)
}
}
Ended up solving this! Here's what I added in the TablesAndInfo Component:
constructor(props) {
super(props);
this.state = {current: this.props.current, date: this.props.date, time: this.props.time}
}
shouldComponentUpdate(nextProps) {
if ((nextProps.current !== this.state.current) || (nextProps.date !== this.state.date) || (nextProps.time !== this.state.time)) {
this.setState({current: nextProps.current});
this.setState({date: nextProps.date});
this.setState({time: nextProps.time});
}
return true;
}
componentDidUpdate() {
this.sections[0][1][2][0] = this.state.current;
this.sections[0][2][1][0] = this.state.time;
this.sections[0][2][2][0] = this.state.date;
}
What I've tried and my issue
I started with creating an external function and running it through the onClick... this works partly as it sends the alerts on click. See the services page on test.ghostrez.net.
Click the small images to trigger the alerts that show which if statement, thestate.active:value, and the state.id:value.
So I know the correct statements are being triggered.
My problem is I keep having state[i].setState is not a function returned rather than the state being set as intended.
I have placed the function internally and externally to the class Player and it returned the same issue.
I converted the function to an internal arrow function as suggested HERE.
I converted it to a const changeActiveField = () => {stuff in here}
I attempted to bind it const changeActive = changeActiveField.bind(this) *as suggested HERE and HERE
Each attempt returning the same Error
this is what the debug console returns
Here is my current function its process > 1. if the active object in state has the same id as image clicked - do nothing, 2. if the active object has a different id to the image clicked setState active:value to false then come back and find the object with the id === id of the image clicked and setState active:true from false.
function changeActiveField(im, state) {
console.log(state);
for (var i = 0; i < state.length; i++) {
if (state[i].active === true && state[i].id === im) {
return alert("if " + state[i].active + " " + state[i].id);
} else if (state[i].active === true && state[i].id !== im) {
alert(" elseif set false " + state[i].active + " " + state[i].id);
state[i].setState(false);
} else if (state[i].id === im) {
alert("elseif make true " + state[i].active + " " + state[i].id);
state[i].setState({ active: true });
return;
} else {
return alert("Nope");
}
}
}
changeActiveField is called here
<div className="thumbs">
{this.state.ids.map((i) => (
<>
<Image
className="carouselitem"
rounded
onClick={() => changeActiveField(i.id, this.state.ids)}
src={"http://img.youtube.com/vi/" + i.id + "/hqdefault.jpg"}
size="small"
/>
<h2>
{i.id} {i.active ? "true" : "false"}
</h2>
</>
))}
</div>
No joke I've been trying to resolve this for 4 days now. I'm stumped.
It appears that you are trying to setState on an individual id, but what you are actually doing is trying to call id.setState
From the code you supplied, each id looks basically like this:
{active: //true/false, id: //some Int}
but in reality your code is looking for this...
{active: //true/false, id: //some Int, setState: () => //do something here}
You'll need to handle how to find your specific id object in that array of ids, and then update your full state with the current state AND the modification you are making.
EDIT://my fault, wasn't thinking.
I would recommend making a copy of your state array in a new variable, then mapping through that new array variable making your mutations. Then set your state based on that new array objects...
let newIdArr = this.state.ids
newIdArr.map(id => //do your stuff here...)
this.setState({...this.state, ids: newIdArr})
Lastly, when you setState(false) you are overwriting ALL your state to where it will be just false, losing all your ids along the way.
This is the end product of too many days pulling my hair out... but it works now and hopefully, it helps someone else. (full component code last)
I used an anonymous function in the Image that is being rendered. This finds and updates the object in the this.state array, first, it finds the ids that don't match the value passed in from the "carouselitem" and updates their active values to false, then it finds the id that matches the value passed in and updates it to true.
The old function changeActiveField is now
onClick={() => {
this.setState((prevState) => ({
ids: prevState.ids.map((ob) =>
ob.id !== i.id
? { ...ob, active: false }
: { ...ob, active: true }
),
}));
}}
I have also moved my firstActiveId into the class. This finds the array object with active: true and returns the id value which is placed in the activevid to display and play the appropriate video.
firstActiveId = () => {
for (var i = 0; i < this.state.ids.length; i++) {
if (this.state.ids[i].active) {
return this.state.ids[i].id;
}
}
};
The firstActiveId is used like this to provide playback.
<div className="activevid">
<Embed
active
autoplay={true}
color="white"
hd={false}
id={this.firstActiveId(this.state.ids)}
iframe={{
allowFullScreen: true,
style: {
padding: 0,
},
}}
placeholder={
"http://img.youtube.com/vi/" +
this.firstActiveId(this.state.ids) +
"/hqdefault.jpg"
}
source="youtube"
/>
</div>
TIP: don't over-complicate things like I do
Full Component
import React, { Component } from "react";
import { Embed, Image } from "semantic-ui-react";
import "./Player.css";
export default class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
ids: [
{
id: "iCBvfW08jlo",
active: false,
},
{
id: "qvOcCQXZVg0",
active: true,
},
{
id: "YXNC3GKmjgk",
active: false,
},
],
};
}
firstActiveId = () => {
for (var i = 0; i < this.state.ids.length; i++) {
if (this.state.ids[i].active) {
return this.state.ids[i].id;
}
}
};
render() {
return (
<div className="carouselwrap">
<div className="activevid">
<Embed
active
autoplay={true}
color="white"
hd={false}
id={this.firstActiveId(this.state.ids)}
iframe={{
allowFullScreen: true,
style: {
padding: 0,
},
}}
placeholder={
"http://img.youtube.com/vi/" +
this.firstActiveId(this.state.ids) +
"/hqdefault.jpg"
}
source="youtube"
/>
</div>
<div className="thumbs">
{this.state.ids.map((i) => (
<>
<Image
className="carouselitem"
rounded
onClick={() => {
this.setState((prevState) => ({
ids: prevState.ids.map((ob) =>
ob.id !== i.id
? { ...ob, active: false }
: { ...ob, active: true }
),
}));
}}
src={"http://img.youtube.com/vi/" + i.id + "/hqdefault.jpg"}
size="small"
/>
</>
))}
</div>
</div>
);
}
}
I need to change the name of every avatar every X seconds, I have follow this solution and it works fine but, right now its changing all the names to the same name from RandomAcidName array.
I imagine that I need to iterate through this list also, so each name is passing just once to every avatar.
Here is my code
nameJuggler is passing the name to each avatar
import React, { Component } from "react";
import "./Avatar.scss";
import AcidData from "../Acidsdata/Acids-data.json";
import RandomAcidName from "../../../src/randomAcidName.json";
const getDate = new Date();
const getDay = getDate.getDate();
const getMonth = getDate.getMonth() + 1;
const fullTodayDate = getDay + "/" + getMonth;
const AvatarDisplay = props => {
return (
<div key={props.id} className="container__position-relative">
<img src={props.photo} alt={props.name} className="avatar__img" />
<div className="container__position-absolute avatar__name">
<span>{props.name}</span>
</div>
{props.cumple}
</div>
);
};
class AcidPoblation extends Component {
constructor(props) {
super(props);
this.state = {
sudoNameId: 0,
acidos: AcidData,
randomName: RandomAcidName
};
}
componentDidMount() {
this.changeName = setInterval(() => {
let currentSudoNameId = this.state.sudoNameId;
this.setState({
sudoNameId: currentSudoNameId + 1
});
}, 1500);
}
componentDidUnmount() {
clearInterval(this.changeName);
}
render() {
let nameJuggler =
RandomAcidName[this.state.sudoNameId % RandomAcidName.length];
return (
<main className="container__grid">
{this.state.acidos.map(item => (
<AvatarDisplay
photo={item.acidphoto}
name={nameJuggler}
birth={item.birthdate}
id={item.acidid}
cumple={
item.birthdate === fullTodayDate ? (
<img
src="https://via.placeholder.com/40x40"
alt="feliz cumpleaƱos"
className="container__position-absolute avatar__birth-date"
/>
) : null
}
/>
))}
</main>
);
}
}
export default AcidPoblation;
Here is the Json with the names
[
"Anderson",
"Ashwoon",
"Aikin",
"Bateman",
"Bongard",
"Bowers",
"Boyd",
"Cannon",
"Cast",
"Deitz",
"Dewalt",
"Ebner",
"Frick",
"Hancock",
"Haworth",
"Hesch",
"Hoffman",
"Kassing",
"Knutson",
"Lawless",
"Lawicki",
"Mccord",
"McCormack",
"Miller",
"Myers",
"Nugent",
"Ortiz",
"Orwig",
"Ory",
"Paiser",
"Pak",
"Pettigrew",
"Quinn",
"Quizoz",
"Ramachandran",
"Resnick",
"Sagar",
"Schickowski",
"Schiebel",
"Sellon",
"Severson",
"Shaffer",
"Solberg",
"Soloman",
"Sonderling",
"Soukup",
"Soulis",
"Stahl",
"Sweeney",
"Tandy",
"Trebil",
"Trusela",
"Trussel",
"Turco",
"Uddin",
"Uflan",
"Ulrich",
"Upson",
"Vader",
"Vail",
"Valente",
"Van Zandt",
"Vanderpoel",
"Ventotla",
"Vogal",
"Wagle",
"Wagner",
"Wakefield",
"Weinstein",
"Weiss",
"Woo",
"Yang",
"Yates",
"Yocum",
"Zeaser",
"Zeller",
"Ziegler",
"Bauer",
"Baxster",
"Casal",
"Cataldi",
"Caswell",
"Celedon",
"Chambers",
"Chapman",
"Christensen",
"Darnell",
"Davidson",
"Davis",
"DeLorenzo",
"Dinkins",
"Doran",
"Dugelman",
"Dugan",
"Duffman",
"Eastman",
"Ferro",
"Ferry",
"Fletcher",
"Fietzer",
"Hylan",
"Hydinger",
"Illingsworth",
"Ingram",
"Irwin",
"Jagtap",
"Jenson",
"Johnson",
"Johnsen",
"Jones",
"Jurgenson",
"Kalleg",
"Kaskel",
"Keller",
"Leisinger",
"LePage",
"Lewis",
"Linde",
"Lulloff",
"Maki",
"Martin",
"McGinnis",
"Mills",
"Moody",
"Moore",
"Napier",
"Nelson",
"Norquist",
"Nuttle",
"Olson",
"Ostrander",
"Reamer",
"Reardon",
"Reyes",
"Rice",
"Ripka",
"Roberts",
"Rogers",
"Root",
"Sandstrom",
"Sawyer",
"Schlicht",
"Schmitt",
"Schwager",
"Schutz",
"Schuster",
"Tapia",
"Thompson",
"Tiernan",
"Tisler"
]
I need that every name is assing just once.
In this link is a replica of my project...
The problem is
let nameJuggler = [this.state.sudoNameId % RandomAcidName.length];
this.state.sudoNameId won't be unique because it's just a number set in the parent component and will be provided to all of the AvatarDisplays not dynamically changing.
An easy fix would just make the .map also have an index so you can add the index to the this.state.sudoNameId which will make each number unique.
See code below
return (
<main className="container__grid">
{this.state.acidos.map((item, index) => {
let nameJuggler =
RandomAcidName[(this.state.sudoNameId + index) % RandomAcidName.length];
return (
<AvatarDisplay
photo={item.acidphoto}
name={nameJuggler}
birth={item.birthdate}
id={item.acidid}
cumple={
item.birthdate === fullTodayDate ? (
<img
src="https://via.placeholder.com/40x40"
alt="feliz cumpleaƱos"
className="container__position-absolute avatar__birth-date"
/>
) : null
}
/>
)
})}
</main>
I'm building a shopping cart application and I ran into a problem where all my inputs have the same state value. Everything works fine but when I type in one input box, it's the same throughout all my other inputs.
I tried adding a name field to the input and setting my initial state to undefined and that works fine but the numbers don't go through.
How do we handle inputs to be different when they have the same state value? Or is this not possible / dumb to do?
class App extends Component {
state = {
items: {
1: {
id: 1, name: 'Yeezys', price: 300, remaining: 5
},
2: {
id: 2, name: 'Github Sweater', price: 50, remaining: 5
},
3: {
id: 3, name: 'Protein Powder', price: 30, remaining: 5
}
},
itemQuantity: 0
},
render() {
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>$ {item.price}</h2>
{item.remaining === 0 ? (
<p style={{ 'color': 'red' }}>Sold Out</p>
) : (
<div>
<p>Remaining: {item.remaining}</p>
<input
type="number"
value={ itemQuantity }
onChange={e => this.setState({ itemQuantity: e.target.value})}
placeholder="quantity"
min={1}
max={5}
/>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
</div>
)
}
}
If you are using same state key for all input, All input take value from one place and update to one place. To avoid this you have to use separate state. I suppose you are trying to show input for a list of item.
To achive you can create a component for list item and keep state in list item component. As each component have their own state, state value will not conflict.
Here is an example
class CardItem extends Component {
state = {
number: 0
}
render() {
render (
<input type="text" value={this.state.number} onChange={e => this.setState({ number: e.target.value })} />
)
}
}
class Main extends Component {
render () {
const list = [0,1,2,3,4]
return (
list.map(item => <CardItem data={item} />)
)
}
}
This is a solution which the problem is loosely interpreted, but it does work without having to create another component. As you know, you needed to separate the state of each items in the cart. I did this by dynamically initializing and setting the quantity states of each item. You can see the state changes with this example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { quantities: {} }
}
componentDidMount() {
let itemIDs = ['1', '2', '3', 'XX']; //use your own list of items
itemIDs.forEach(id => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: 0})});
})
}
render() {
let list = Object.keys(this.state.quantities).map(id => {
return (
<div>
<label for={id}>Item {id}</label>
<input
id={id}
key={id}
type="number"
value={this.state.quantities[id]}
onChange={e => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: e.target.value})})
}}
/>
</div>
);
})
return (
<div>
{list}
<div>STATE: {JSON.stringify(this.state)}</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root'></div>
You can modify the state structure to your liking.
Here is how I usually handle this scenario. You say that you get an array of items? Each item object should contain a key to store the value (count in my example). You can use a generic onChange handler to update an individual item in the array. So now, your state is managing the list of items instead of each individual input value. This makes your component much more flexible and it will be able to handle any amount of items with no code changes:
const itemData = [
{ id: 0, count: 0, label: 'Number 1' },
{ id: 1, count: 0, label: 'Number 2' },
{ id: 2, count: 0, label: 'Number 3' },
{ id: 3, count: 0, label: 'Number 4' }
];
class App extends React.Component {
state = {
items: itemData
}
handleCountChange = (itemId, e) => {
// Get value from input
const count = e.target.value;
this.setState( prevState => ({
items: prevState.items.map( item => {
// Find matching item by id
if(item.id === itemId) {
// Update item count based on input value
item.count = count;
}
return item;
})
}))
};
renderItems = () => {
// Map through all items and render inputs
return this.state.items.map( item => (
<label key={item.label}>
{item.label}:
<input
type="number"
value={item.count}
onChange={this.handleCountChange.bind(this, item.id)}
/>
</label>
));
};
render() {
return (
<div>
{this.renderItems()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
label {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can't use the same state for the both inputs. Try to use a different state for each one like that:
class App extends Component {
state = {
number: ""
}
render() {
return (
<div>
<input
type="number"
value={this.state.number}
onChange={e => this.setState({ number: e.target.value })}
/>
<input
type="number"
value={this.state.number2}
onChange={e => this.setState({ number2: e.target.value })}
/>
</div>
)
}
}
I have the following: (https://www.npmjs.com/package/react-sound)
return (
<div className = { "wrapper " + currentState.bgColor}>
<div className="wrapper-inner">
<CustomSound soundOn = { this.state.soundOn} />
<CorrectSound soundOn = { this.state.correct} />
<IncorrectSound soundOn = { this.state.incorrect} />
<h1 className = { isH1Visible}><span>the big</span> reveal</h1>
{form}
{cat}
{grid}
{timeUp}
{correct}
{incorrect}
</div>
</div>
)
the soundOn props for correct and incorrect is set inside "getAnswer" function:
import React, { Component } from 'react';
class Action1 extends Component {
constructor(props) {
super(props);
this.state = {
answer1: "",
answer2: "",
answer3: "",
answer4: "",
answerDisabled: false
}
}
updateStateProperty(el, val) {
var s = {};
s[el] = val;
this.setState(s);
}
getAnswer = (e) => {
let answer = e.target.getAttribute('data-id');
this.props.checkAnswer(e);
if(e.target.getAttribute('data-question') == ("option_" + this.props.question.correct_option)){
this.updateStateProperty(answer, 'correct');
this.props.setCorrectOn(true);
}
else {
this.updateStateProperty(answer, 'wrong');
this.props.setIncorrectOn(true);
}
this.setState({
answerDisabled: true
})
setTimeout(() => {
this.props.setIncorrectOn(false);
this.props.setCorrectOn(false);
this.updateStateProperty(answer, '');
}, 1000);
setTimeout(() => {
this.setState({
answerDisabled: false
})
}, 1500)
}
render() {
return(
<div className="action">
<div className={"action-question " + this.props.catBgColor}>
<h3>{this.props.question.question}</h3>
</div>
<div className={"action-answers " + this.props.catBgColor}>
<p className={this.state.answer1 + " " + (this.state.answerDisabled ? 'disable-link' : "")} data-id="answer1" data-question="option_1" onClick={this.getAnswer.bind(this)}>{this.props.question.option_1}</p>
<p className={this.state.answer2 + " " + (this.state.answerDisabled ? 'disable-link' : "")} data-id="answer2" data-question="option_2" onClick={this.getAnswer.bind(this)}>{this.props.question.option_2}</p>
<p className={this.state.answer3 + " " + (this.state.answerDisabled ? 'disable-link' : "")} data-id="answer3" data-question="option_3" onClick={this.getAnswer.bind(this)}>{this.props.question.option_3}</p>
<p className={this.state.answer4 + " " + (this.state.answerDisabled ? 'disable-link' : "")} data-id="answer4" data-question="option_4" onClick={this.getAnswer.bind(this)}>{this.props.question.option_4}</p>
</div>
</div>
);
}
}
export default Action1;
each sound component has the following structure (with different sound files):
import React from 'react';
import Sound from 'react-sound';
class CustomSound extends React.Component {
render() {
return (
<Sound
url="./assets/sound.mp3"
playStatus={this.props.soundOn ? Sound.status.PLAYING : Sound.status.STOPPED}
playFromPosition={0 /* in milliseconds */}
/>
);
}
}
export default CustomSound;
when either of the 2 is triggered:
this.props.setCorrectOn(true); or this.props.setIncorrectOn(true);
the sound coming from:
<CustomSound soundOn = { this.state.soundOn} />
stops and starts again. Ideally that is a soundtrack that I would like to carryon playing and stop it at a later stage of the game.
The problem seems to be happening on iPad only. On chrome on desktop is absolutely fine.
Solution found at: http://www.schillmania.com/projects/soundmanager2/doc/
soundManager.setup({ ignoreMobileRestrictions: true });