I have a series of data in which there is a form in every item. I try to use index in onSubmit event and when I check index of for loop in console, it shows the correct index, but when I check index of
handleSubmitR=(e, DetailsRoom, index)=>{console.log(index)}
it is different from the index in for loop.
For example, if I have 3 forms in one item the result of the index in for loop is
'01','02' ,'03'
but in onsubmit event the result of index is **
'03','03','03'
Any suggestions?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
resultEdit: {},
};
$.ajax({
url:"/json.bc",
type:"post",
success: (result) => {
this.setState({data: eval(result)});
}
})
}
renderHotel(){
return this.state.data.sort((a, b) => a.total - b.total).map((item,i)=>{
return (
<div class="items">
{this.renderDetailsRoom(item,i)}
</div>
)
})
}
renderDetailsRoom(DetailsRoom,i){
let lenfamilies = DetailsRoom.families.length
var indents =[];
for(var j = 0 ;j <lenfamilies;j++){
var numF = i;
var numS = j;
var stingF = numF.toString();
var stingS = numS.toString();
var index= stingF+stingS
**////
<!-----e.g. For two forms the result of consol.log(index) = '00' and '01'----->
/////**
indents.push(<form method="post" key={index} action={this.renderAction(DetailsRoom)} onSubmit={e => this.handleSubmitR(e, DetailsRoom, index)}><div class="Result">{this.state.resultEdit[index]}</div></form>)
}
return(
indents
)
}
handleSubmitR=(e, DetailsRoom, index)=>{
////
<!-----but the result of consol.log(index) in this part for both form are '01'----->
/////
console.log(index)
e.preventDefault();
return this.setState( prevState => ({
resultEdit: { ...prevState.resultEdit, [index]:'submitted'},
})) }
render() {
return (
<div>{this.renderHotel()}</div>);
}
}
ReactDOM.render(<App/>, document.getElementById('Result'));
Related
environment:react 17.x、umi 3.5.21
In this code below, I send a request and build an array of components, using console.log(hosCardArr) in then has content
let hosCardArr: any[] = [];
useEffect(() => {
getHosList({ _location: location, _level: level }).then(function (
response: returnHosInfo[],
) {
let arr: any[] = [];
for (let i = 0; i < response.length; i++) {
let e: returnHosInfo = response[i]
if (i % 2 == 0) {
arr.push(<HosCard name={e.name} level={e.level} openTime={e.openTime} domLocation='left' />)
} else {
arr.push(<HosCard name={e.name} level={e.level} openTime={e.openTime} domLocation='right' />)
}
}
hosCardArr = arr
});
});
In this code below, I render the array, using console.log(hosCardArr) in <div><div/> the result is []
<div>{hosCardArr}</div>
The end result is that the elements in the array are not displayed
The focus of this question is on data flow,If I don't use state, it will render the array empty and not updated
const [hosCardArr, setHosCardArr] = useState<any[]>([]);
useEffect(() => {
getHosList({ _location: location, _level: level }).then(function (
response: returnHosInfo[],
) {
let arr: any[] = [];
for (let i = 0; i < response.length; i++) {
let e: returnHosInfo = response[i]
if (i % 2 == 0) {
arr.push(<HosCard name={e.name} level={e.level} openTime={e.openTime} domLocation='left' />)
} else {
arr.push(<HosCard name={e.name} level={e.level} openTime={e.openTime} domLocation='right' />)
}
}
setHosCardArr(arr)
});
}, []);
you must use .map function to render each item of the array , like this :
return (
<>
{
hosCardArr.map((hos,index) =>
<div key={index}>{hos}</div>
);
}
</>
)
I cant understand why my renderMovies() function dont wanna update my component state.data and i cant render component on my screen ?!
Everithing goes ok until renderMovies function.. I think this.setState(newState) in my fetchPostData function is working incorrect... Do somebody know how to fix it? I tried different ways but i cant solve this issue.
class Movies extends React.Component {
constructor(props) {
super(props)
this.state = { data: {}}
this.fetchPostData = this.fetchPostData.bind(this)
this.renderMovies = this.renderMovies.bind(this)
this.populatePageAfterFetch = this.populatePageAfterFetch.bind(this)
}
componentDidMount() {
this.fetchPostData()
}
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
let objLength = Object.keys(myJSON).length
let newState = this.state;
for (let i = 0; i < objLength; i++) {
let objKey = Object.values(myJSON)[i].title.rendered;
// console.log(objKey)
let currentMovie = newState.data[objKey];
currentMovie = {};
currentMovie.name = Object.values(myJSON)[i].title.rendered;
currentMovie.description = Object.values(myJSON)[i].content.rendered;
currentMovie.featured_image = Object.values(myJSON)[i]['featured_image_url'];
currentMovie.genre = Object.values(myJSON)[i]['genre'];
}
this.setState(newState)
})
}
renderMovies() {
if(this.state.data) {
const moviesArray = Object.values(this.state.data)
console.log(moviesArray)
return Object.values(moviesArray).map((movie, index) => this.populatePageAfterFetch(movie, index))
}
}
populatePageAfterFetch(movie, index) {
if (this.state.data) {
return (
<div key={index} index={index}>
<h2>{movie.title}</h2>
<h3>{movie.genre}</h3>
<p>{movie.description}</p>
</div>
)
}
}
render() {
return (
<div>
<h1>Movies</h1>
<div>{this.renderMovies()}</div>
</div>
)
}
}
When i try to console.log(moviesArray) it show me:
Issue
You save current state into a variable named newState, never update it, and then save the same object reference back into state. React state never really updates.
let newState = this.state;
for (let i = 0; i < objLength; i++) {
...
}
this.setState(newState);
Additionally you mutate state
let currentMovie = newState.data[objKey];
currentMovie = {};
But this doesn't work either since initial state is an empty object so newState.data[objKey] is aways undefined. (so nothing is ever actually mutated)
Solution
It appears as though you intended to map the myJSON data/values into movie objects to update this.state.data. May I suggest this solution. The key is to always create new object references for any object you update.
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
this.setState(prevState => ({
// array::reduce over the JSON values
data: Object.values(myJSON).reduce((movies, movie) => {
// compute movie key
const name = movie.title.rendered;
return {
...movies,
[name]: {
...movies[name], // copy any existing movie properties
// merge in new/updated properties
name,
description: movie.content.rendered,
featured_image: movie.featured_image_url,
genre: movie.genre,
},
}
}, { ...prevState.data }) // use previous state as initial value for reduce
}))
})
}
My React state:
//...
this.state = {
mylist: [
{
"id": 0,
"trueorfalse": false
},
{
"id": 1,
"trueorfalse": false
}
]
}
//...
I am trying to update the trueorfalse value based on the id
Here is what I did so far but didn't work:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
var TorF = true
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { TorF }) : el))
})
}
I really want to make it dynamic so the trueorfase will be opposite of what it is now:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { /* if already true set to false or vice versa */ }) : el))
})
}
How can I update my code to have the dynamicity shown in the second example (if possible), otherwise the first example would do just fine
Another solution using map:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mylist: [
{
id: 0,
trueorfalse: false
},
{
id: 1,
trueorfalse: true
}
]
};
}
toggleBoolean = () => {
const ID = Number(this.state.selectedID);
this.setState(prevState => ({
mylist: prevState.mylist.map(item => {
if (item.id === ID) {
return { ...item, trueorfalse: !item.trueorfalse };
} else {
return item;
}
})
}));
};
render() {
return (
<div className="App">
<p>{`State values: ${JSON.stringify(this.state.mylist)}`}</p>
<button onClick={this.toggleBoolean}>Change true/false values</button>
<label>Insert ID:</label>
<input
type="number"
onChange={event => this.setState({ selectedID: event.target.value })}
/>
</div>
);
}
}
I think the following code would accomplish your second question.
var idnum = e.target.id.toString().split("_")[1]
let newList = Array.from(this.state.mylist) //create new array so we don't modify state directly
if (type === 1) {
let objToUpdate = newList.find((el) => el.id === idnum) // grab first element with matching id
objToUpdate.trueorfalse = !objToUpdate.trueorfalse
this.setState( { mylist: newList } )
}
For react, is it possible to change the cell color for a component? I know it is way super easy can be done in Jquery by looking at the id/class of the element and do an addClass, but how to do it on react? I'm still figuring it out the changes, what i can think of is that i set the value on a state, but how i get the affected cell and implement the state? Any idea? Or is there any better grid that I can achieve like what can be done in Jquery?
constructor(props, context) {
super(props, context);
this.state = {
cellUpdateCss: ""
};
}
handleGridRowsUpdated = ({ cellKey, fromRow, toRow, updated, action, originRow }) => {
let rows = this.state.rows.slice();
for (let i = fromRow; i <= toRow; i++) {
let rowToUpdate = rows[i];
let updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
this.postToServer(updatedRow.data, JSON.stringify(updated));
}
this.setState({ rows });
};
postToServer = (aX, vC) => {
var self = this;
axios.post(this.props.postApi, {
axNo: aX,
updated: vC
})
.then((response) => {
console.log(response);
self.setState({ cellUpdateCss: "green" });
})
.catch((error) => {
console.log(error);
self.setState({ cellUpdateCss: "red" });
});
}
render() {
return (
<ReactDataGrid
onGridRowsUpdated={this.handleGridRowsUpdated}
/>
)
}
third party grid that I use
https://github.com/adazzle/react-data-grid
You can accomplish that by applying the style to the row class name, first of all, in handleGridRowsUpdated function you need to set fromRow, toRow to the constructor state then we need to call the changeStyle function that I did with the callback of setState.
handleGridRowsUpdated = ({ cellKey, fromRow, toRow, updated, action, originRow }) => {
let rows = this.state.rows.slice();
for (let i = fromRow; i <= toRow; i++) {
let rowToUpdate = rows[i];
let updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
this.postToServer(updatedRow.data, JSON.stringify(updated));
}
this.setState({ rows, fromRow: fromRow, toRow: toRow },()=>{
this.changeStyle();
});
};
changeStyle function, it will change the color of the edited row.
changeStyle = () => {
var all = document.getElementsByClassName('react-grid-Row');
for (var i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = this.state.cellUpdateCss;
}
}
I did a full example on CodeSandbox
I am trying to come up a react exercise for the flip-match cards game: say 12 pairs of cards hide (face down) randomly in a 4x6 matrix, player click one-by-one to reveal the cards, when 2 cards clicked are match then the pair is found, other wise hide both again., gane over when all pairs are found.
let stored = Array(I * J).fill(null).map((e, i) => (i + 1) % (I * J));
/* and: randomize (I * J / 2) pairs position in stored */
class Board extends React.Component {
constructor() {
super();
this.state = {
cards: Array(I*J).fill(null),
nClicked: 0,
preClicked: null,
clicked: null,
};
}
handleClick(i) {
if (!this.state.cards[i]) {
this.setState((prevState) => {
const upCards = prevState.cards.slice();
upCards[i] = stored[i];
return {
cards: upCards,
nClicked: prevState.nClicked + 1,
preClicked: prevState.clicked,
clicked: i,
};
}, this.resetState);
}
}
resetState() {
const preClicked = this.state.preClicked;
const clicked = this.state.clicked;
const isEven = (this.state.nClicked-1) % 2;
const matched = (stored[preClicked] === stored[clicked]);
if (isEven && preClicked && clicked && matched) {
// this.forceUpdate(); /* no effects */
this.setState((prevState) => {
const upCards = prevState.cards.slice();
upCards[preClicked] = null;
upCards[clicked] = null;
return {
cards: upCards,
nClicked: prevState.nClicked,
preClicked: null,
clicked: null,
};
});
}
}
renderCard(i) {
return <Card key={i.toString()} value={this.state.cards[i]} onClick={() => this.handleClick(i)} />;
}
render() {
const status = 'Cards: '+ I + ' x ' + J +', # of clicked: ' + this.state.nClicked;
const cardArray = Array(I).fill(null).map(x => Array(J).fill(null));
return (
<div>
<div className="status">{status}</div>
{ cardArray.map((element_i, index_i) => (
<div key={'row'+index_i.toString()} className="board-row">
{ element_i.map((element_j, index_j) => this.renderCard(index_i*J+index_j))
}
</div>
))
}
</div>
);
}
}
Essentially, Board constructor initialize the state, and handleClick() calls setState() to update the state so it trigger the render of the clicked card's value; the callback function resetState() is that if the revealed two card did not match, then another setState() to hide both.
The problem is, the 2nd clicked card value did not show before it goes to hide. Is this due to React combine the 2 setState renderings in one, or is it rendering so fast that we can not see the first rendering effects before the card goes hide? How to solve this problem?
You're passing resetState as the callback to setState, so I would expect after the initial click your state will be reset.
You might want to simplify a bit and do something like this:
const CARDS = [
{ index: 0, name: 'Card One', matchId: 'match1' },
{ index: 1, name: 'Card Two', matchId: 'match2' },
{ index: 2, name: 'Card Three', matchId: 'match1', },
{ index: 3, name: 'Card Four', 'matchId': 'match2' },
];
class BoardSim extends React.Component {
constructor(props) {
super(props);
this.state = {
cardsInPlay: CARDS,
selectedCards: [],
checkMatch: false,
updateCards: false
};
...
}
...
componentDidUpdate(prevProps, prevState) {
if (!prevState.checkMatch && this.state.checkMatch) {
this.checkMatch();
}
if (!prevState.updateCards && this.state.updateCards) {
setTimeout(() => {
this.mounted && this.updateCards();
}, 1000);
}
}
handleCardClick(card) {
if (this.state.checkMatch) {
return;
}
if (this.state.selectedCards.length === 1) {
this.setState({ checkMatch: true });
}
this.setState({
selectedCards: this.state.selectedCards.concat([card])
});
}
checkMatch() {
if (this.selectedCardsMatch()) {
...
}
else {
...
}
setTimeout(() => {
this.mounted && this.setState({ updateCards: true });
}, 2000);
}
selectedCardsMatch() {
return this.state.selectedCards[0].matchId ===
this.state.selectedCards[1].matchId;
}
updateCards() {
let cardsInPlay = this.state.cardsInPlay;
let [ card1, card2 ] = this.state.selectedCards;
if (this.selectedCardsMatch()) {
cardsInPlay = cardsInPlay.filter((card) => {
return card.id !== card1.id && card.id !== card2.id;
});
}
this.setState({
selectedCards: [],
cardsInPlay,
updateCards: false,
checkMatch: false
});
}
render() {
return (
<div>
{this.renderCards()}
</div>
);
}
renderCards() {
return this.state.cardsInPlay.map((card) => {
return (
<div key={card.name} onClick={() => this.handleCardClick(card)}>
{card.name}
</div>
);
});
}
...
}
I've created a fiddle for this you can check out here: https://jsfiddle.net/andrewgrewell/69z2wepo/82425/