ComponentDidMount is not firing in my case - javascript

It is main class where I am calling the child component (DeviceReplacementRequestsList.js). And child component's componentDidMount is not working. I don't know why this is happening. I saw some other answers but I think that were not related to my case.
Main Component.js
class DevicesTypes extends React.Component {
module_name = "Device Types";
create_function = "create_device_type";
get_function = "get_device_type";
render() {
return (
<WidgetGrid>
{
this.props.modules && this.props.modules[this.module_name] && this.props.modules[this.module_name].members && this.props.modules[this.module_name].members.find((x) => { return x.id == this.create_function }) ?
<article className="col-sm-5">
<NewDeviceType />
</article> : null
}
{
this.props.modules && this.props.modules[this.module_name] && this.props.modules[this.module_name].members && this.props.modules[this.module_name].members.find((x) => { return x.id == this.get_function }) ?
<article className="col-sm-7">
<DeviceTypeList permissions={this.props.modules && this.props.modules[this.module_name] && this.props.modules[this.module_name].members ? this.props.modules[this.module_name].members : []} />
</article> : null}
{
this.props.modules && this.props.modules[this.module_name] ?
<article className="col-sm-7">
<Text2Speech/>
</article> : null
}
{
this.props.modules && this.props.modules[this.module_name] ?
<article className="col-sm-5">
<NewDeviceReplacement/>
</article>: null
}
{
this.props.modules && this.props.modules[this.module_name] ?
<article className="col-sm-7">
<DeviceReplacementRequestsList/>
</article> : null
}
</WidgetGrid>
)
}
}
In last component DeviceReplacementRequestsList.js my componentDidMount is not triggering
DeviceReplacementRequestsList.js
export default class DeviceReplacementList extends React.Component {
constructor() {
super();
this.state = {
deviceData : [],
spinner : true
}
}
componentDidMount() {
console.log("hello")
this.getDeviceReplacementRequests();
}
getDeviceReplacementRequests() {
const queryPrams = "";
ds.getDeviceReplacementRequests(queryPrams,data => {
console.log(data);
this.setState({
deviceData : data
})
}, err => {
})
}
handleChanges(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<div>
<JarvisWidget editbutton={false} deletebutton={false} color="blueDark">
<header>
<span className="widget-icon"> <i className="fa fa-edit" /></span>
<h2>All Device Types</h2>
<button className="btn btn-trans float-right" type="button" onClick={this}>
<i className="fa fa-refresh"></i>
</button>
<div className="col-md-12">
<div className="loader"></div>
</div>
</header>
</JarvisWidget>
</div>
)}

Have you tried binding it first inside the constructor?
this.getDeviceReplacementRequests = this.getDeviceReplacementRequests.bind(this);

Related

Warning: Text content did not match in React 18

recently the project I am working on has been upgraded to React 18. By then, suddenly a lot of issues with hydration have started to appear as warnings/errors in the console. The one I'm struggling with is "Warning: Text content did not match":
Code of this component:
<div className="O75-product-faq__questions is-active accordion--initialized">
{
dataForSelect.length > 1 && <h4 className="O75-product-faq__questions__name js-category-name">{props.questionsByCategories[selectedCategory?.value].name}</h4>
}
{
props.questionsByCategories[selectedCategory?.value].questions.map((element, i) => (
<div key={i} className="O75-product-faq__questions__item">
{(element.question || props.showOnlyAnswer) && <div className={`O75-product-faq__questions__item__button${openedElement === i ? ' has-accordion-open' : ''}`} onClick={() => openElement(i)}>{element.question}</div>}
<AnimateHeight height={openedElement === i ? 'auto' : 0} duration={transitionDisabled ? 0 : 400}>
<div className="O75-product-faq__questions__item__content" dangerouslySetInnerHTML={{ __html: element.answer }} />
</AnimateHeight>
</div>))
}
</div>
I know that this issue results from the difference between client and server side rendering, but I don't know how to fix it and no other similar question contained solution that worked in my case.
Rest of the file, in case if the issue is not with aforementioned part:
import React, { useMemo, useState } from 'react';
import type { ReactElement } from 'react';
import AnimateHeight from 'react-animate-height';
import { BaseSelect, SelectOption } from '../molecules/base-select';
import type { FrequentlyAskedCategory } from './frequentlyAskedQuestion';
import { fromBase64 } from '../shared-services/base64Service';
interface FaqPanelProps {
mainTitle?: string;
menuTitle?: string;
showOnlyAnswer: boolean;
currentPageUrl: string;
questionsByCategories: Record<string, FrequentlyAskedCategory>,
faqStructuredDataBase64: string;
}
const FAQPanel = (props: FaqPanelProps): ReactElement => {
const categories = Object.keys(props.questionsByCategories);
const dataForSelect: Array<SelectOption> = categories.map(key => ({ label: props.questionsByCategories[key].name, value: key }));
const noOpenedElementIndex = -1;
const [openedElement, setOpenedElement] = useState<number>(-1);
const [selectedCategory, setSelectedCategory] = useState<SelectOption>(dataForSelect.length > 0 ? dataForSelect[0] : null);
const [transitionDisabled, setTransitionDisabled] = useState<boolean>(false);
const parsedStructuredData = useMemo(() => {
if(props.faqStructuredDataBase64 != null && props.faqStructuredDataBase64 !== ""){
return fromBase64(props.faqStructuredDataBase64);
}
return "";
}, [props.faqStructuredDataBase64]);
const selectNewCategory = (option: SelectOption): void => {
setTransitionDisabled(true);
setOpenedElement(noOpenedElementIndex);
setSelectedCategory(option);
}
const openElement = (index: number): void => {
if (transitionDisabled) {
setTransitionDisabled(false);
}
setOpenedElement(index === openedElement ? noOpenedElementIndex : index);
}
const speakableJson = JSON.stringify({
"#context": "https://schema.org/",
"#type": "WebPage",
"name": props.mainTitle,
"speakable":
[".O75-product-faq__headline",
".O75-product-faq__questions__item"],
"url": props.currentPageUrl
});
const hasFAQStructuredData = parsedStructuredData != null && parsedStructuredData !== "";
return (
<div className="container">
<section className="O75-product-faq" >
{
props.mainTitle
? <h2 className="O75-product-faq__headline">{props.mainTitle}</h2>
: <h4 className="O75-product-faq__categories-headline">{props.menuTitle}</h4>
}
<div className="flex">
{dataForSelect.length > 1 &&
<div className="O75-product-faq__categories">
<div className="filter__list is-hidden-sm filter">
{
dataForSelect.map((element, i) => (
<button className={`filter__btn js-filter__btn${element.value === selectedCategory?.value ? " is-active" : ""}`} key={i} onClick={() => selectNewCategory(element)}>
{element.label}
</button>))
}
</div>
<div className="filter__group is-hidden-md">
<BaseSelect selectedValue={selectedCategory}
handleChange={selectNewCategory}
options={dataForSelect} />
</div>
</div>
}
{categories.length > 0 &&
<div className="O75-product-faq__questions is-active accordion--initialized">
{
dataForSelect.length > 1 && <h4 className="O75-product-faq__questions__name js-category-name">{props.questionsByCategories[selectedCategory?.value].name}</h4>
}
{
props.questionsByCategories[selectedCategory?.value].questions.map((element, i) => (
<div key={i} className="O75-product-faq__questions__item">
{(element.question || props.showOnlyAnswer) && <div className={`O75-product-faq__questions__item__button${openedElement === i ? ' has-accordion-open' : ''}`} onClick={() => openElement(i)}>{element.question}</div>}
<AnimateHeight height={openedElement === i ? 'auto' : 0} duration={transitionDisabled ? 0 : 400}>
<div className="O75-product-faq__questions__item__content" dangerouslySetInnerHTML={{ __html: element.answer }} />
</AnimateHeight>
</div>))
}
</div>
}
</div>
{hasFAQStructuredData && <script suppressHydrationWarning type="application/ld+json" dangerouslySetInnerHTML={{__html:parsedStructuredData } }></script>}
<script suppressHydrationWarning type="application/ld+json" dangerouslySetInnerHTML={{__html: speakableJson}} ></script>
</section>
</div>
)
}
export { FAQPanel };
export type { FaqPanelProps }
Does anyone knows how to fix it?

Incrementing/decrementing score value in rock/paper/scissors React app

I am new to React and to this platform, and I am building a rock/scissors/paper app with React.js.
The app works well, but I cannot increment the score value of the game. If the player wins I would like to increment it of +1, if the CPU wins decrement it by -1. (draw nothing happens)
I can see the score in the screen, because I set the state of to the value, I just cannot update it.
I think the problem is mainly on my setScore() function. I think is correct that I pass it from as a prop to , but in I don't know exactly how to call the method and then eventually pass it back to .
I know is a lot of code but essentially I just would like to pass a setScore() method down to a child component, call it, and the pass the result back to the parent component.
Do you have any idea how I could fix this problem ?
You can also check Codesandbox
Thank you very much in advance!!
App.js
import React from "react"
import Header from "./components/Header"
import Main from "./components/Main"
import Footer from "./components/Footer"
import './App.css';
class App extends React.Component {
constructor(){
super()
this.state = {
score: 12
}
this.setScore = this.setScore.bind(this)
}
/*here i create the setScore method*/
setScore() {
this.setState((prevState) => {
return {
score: prevState.score + 1
}
})
}
render() {
return (
<div className="App">
<div className="container">
<Header
rock = "ROCK"
paper = "PAPER"
scissors = "SCISSORS"
score = {this.state.score}
/>
/*here I pass the setScore method as a prop to my Main component (should I also pass the score
value as a parameter?)*/
<Main setScore = {this.setScore}/>
<Footer />
</div>
</div>
)
}
}
export default App;
Main.js
import React from "react"
import Choice from "./Choice"
import TryAgain from "./TryAgain"
import paper from '../images/icon-paper.svg'
import rock from '../images/icon-rock.svg'
import scissors from '../images/icon-scissors.svg'
import './Main.css';
class Main extends React.Component {
constructor(props) {
super(props)
this.state = {
onScreen: true,
choiceName: '',
choiceImage: '',
choiceBorderColor: '',
choiceExtraBorderColor: '',
houseChoice: '',
results:'',
score: 0,
setScore: props.setScore
}
this.handleClick = this.handleClick.bind(this)
this.handleTryAgainClick = this.handleTryAgainClick.bind(this)
}
/*function that handles the user choice*/
handleClick = (choiceName, choiceImage, choiceBorderColor, choiceExtraBorderColor) => () => {
this.setState({
onScreen: false,
choiceName,
choiceImage,
choiceBorderColor,
choiceExtraBorderColor,
})
/*function that get a random number between 0 and 2, and set this number to the house index*/
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
const index = getRandomInt(3)
this.setState({
houseChoice: index
})
const results = this.getResults(choiceName, index).toUpperCase()
this.setState({
results: results,
})
if(results === "WIN") {
/*************what to put here*****************/
const res = this.props.setScore
console.log(res) /*here it prints the function itself!*/
return {
res
}
} else if (results === "LOSE" && this.state.score > 0) {
/*************what to put here*****************/
console.log(this.state.score)
this.setState((prevState) => {
return {
score: prevState.score - 1
}
})
}
else {
console.log(this.state.score)
this.setState((prevState) => {
return {
score: prevState.score
}
})
}
}
/*function that get the main logic and the results of the game*/
getResults(choiceName, houseChoice) {
if(choiceName === "paper" && houseChoice === 0) {
return "draw"
} else if(choiceName === "paper" && houseChoice === 1) {
return "lose"
} else if(choiceName === "paper" && houseChoice === 2) {
return "win"
}
if(choiceName === "rock" && houseChoice === 0) {
return "lose"
} else if(choiceName === "rock" && houseChoice === 1) {
return "win"
} else if(choiceName === "rock" && houseChoice === 2) {
return "draw"
}
if(choiceName === "scissors" && houseChoice === 0) {
return "win"
} else if(choiceName === "scissors" && houseChoice === 1) {
return "draw"
} else if(choiceName === "scissors" && houseChoice === 2) {
return "lose"
}
}
/*function that switches the screen and resets the index of the house*/
handleTryAgainClick() {
this.setState({
onScreen: true,
houseChoice: ''
})
}
render() {
return(
<div>
{/*HOME PAGE*/}
<div className="main-container" style={{display: (this.state.onScreen ? "block" : "none")}}>
<div className="triangle-container">
<div onClick={this.handleClick}>
<Choice
name="paper"
image={paper}
borderColor="hsl(230, 89%, 62%)"
extraBorderColor="hsl(230, 89%, 65%)"
handleClick={this.handleClick}
/>
</div>
<div onClick={this.handleClick}>
<Choice
name="scissors"
image={scissors}
borderColor="hsl(39, 89%, 49%)"
extraBorderColor="hsl(40, 84%, 53%)"
handleClick={this.handleClick}
/>
</div>
<div style={{gridArea: "bottom"}} onClick={this.handleClick}>
<Choice
name="rock"
image={rock}
borderColor="hsl(349, 71%, 52%)"
extraBorderColor="hsl(349, 70%, 56%)"
handleClick={this.handleClick}
/>
</div>
</div>
</div>
{/*RESULT PAGE*/}
<div className="result-wrapper" style={{display: (!this.state.onScreen ? "grid" : "none")}}>
<div className="user-result-box">
<h4 className="result-title">YOU PICKED</h4>
<div
className="elem-container result-container"
style={{
borderColor: this.state.choiceBorderColor,
color: this.state.choiceExtraBorderColor
}}
>
<img src={this.state.choiceImage} className="choice-image" alt="img" />
</div>
</div>
<div className="house-result-box">
<h4 className="result-title">THE HOUSE PICKED</h4>
{this.state.houseChoice === 0 ? (
/*1*/
<div
className="elem-container result-container"
style={{
borderColor:"hsl(230, 89%, 62%)",
color:"hsl(230, 89%, 65%)"
}}
>
<img src={paper} className="choice-image" alt="img" />
</div>
) : (
this.state.houseChoice === 1 ? (
/*2*/
<div
className="elem-container result-container"
style={{
borderColor:"hsl(39, 89%, 49%)",
color:"hsl(40, 84%, 53%)"
}}
>
<img src={scissors} className="choice-image" alt="img" />
</div>
) : (
/*3*/
<div
className="elem-container result-container"
style={{
borderColor:"hsl(349, 71%, 52%)",
color:"hsl(349, 70%, 56%)"
}}
>
<img src={rock} className="choice-image" alt="img" />
</div>
))
}
</div>
<div className="final-result-container">
<h1 className="bold">YOU {this.state.results}</h1>
<TryAgain onClick={this.handleTryAgainClick}/>
</div>
</div>
</div>
)
}
}
export default Main
[1]: https://codesandbox.io/s/nice-ardinghelli-96sum?file=/src/images/icon-scissors.svg
App.js
setScore(value) {
if (this.state.score > 0) {
this.setState({
score: this.state.score + value
})
}
}
Main.js
if (results === 'WIN') {
/*************what to put here*****************/
this.props.setScore(1)
} else if (results === 'LOSE') {
/*************what to put here*****************/
this.props.setScore(-1)
}
Score.js
import React from "react";
import "./Score.css";
class Score extends React.Component {
render() {
return (
<div>
<div className="score-text">SCORE</div>
<div className="score-value">{this.props.score}</div>
</div>
);
}
}
export default Score;

React TypeScript: Cannot get map() inside other map() in render() to work

I want to render out messages and their replies in React (with TypeScript).
Messages are stored inside an array in the state and replies are stored inside a different array inside the state.
This is my current code, which results in not rendering out the message blocks:
public render(): React.ReactElement<ISpfxConversationsProps> {
const { channelTopics, topicReplies, errorMessage } = this.state;
const hideTitleContainer = this.props.isEditMode || this.props.title ? '' : styles.hidden;
const wpTitleClasses = `${styles.webpartHeader} ${hideTitleContainer}`;
return (
<div className={ styles.spfxTecanTeamsConversations }>
<div className={ styles.container }>
<div className={ wpTitleClasses }>
{ this.props.isEditMode && <textarea onChange={this.setTitle.bind(this)} className={styles["edit"]} placeholder={strings.WebpartTitlePlaceholder} aria-label={strings.WebpartTitlePlaceholder} defaultValue={this.props.title}></textarea> }
{ !this.props.isEditMode && <span className={styles["view"]}>{this.props.title}</span> }
</div>
{ errorMessage ? <p className={ styles.textError }>{errorMessage}</p> : null }
<div className={ styles.conversationsArea }>
{
channelTopics.map((topic: IChannelTopic, indexTopic) => {
return (
this.renderMessageBlock( topic.message, indexTopic),
topicReplies.filter(r => r.topicMessageId === topic.id).map((reply: ITopicReply, indexReply) => {
return (
this.renderMessageBlock(reply.message, indexReply, true)
)
})
)
})
}
</div>
</div>
</div>
);
}
public renderMessageBlock(message: IChannelMessage, index: Number, isReply: boolean = false) {
const replyStyle = isReply? '' : styles.messageReply;
const messageBlockClasses = `${styles.messageBlock} ${replyStyle}`;
return (
<div className={ messageBlockClasses} key={`teams-message-${message.id}-${index}`}>
<div className={ styles.messageHeader}>
<span className={ styles.messageAuthor }>
{ message.fromUserDisplayName ? message.fromUserDisplayName : strings.UnknownAccount }
</span>
<span className={ styles.messageDate }>
{ this.renderDate(message.createdDateTime) }
</span>
</div>
<div className={ styles.messageBody }>
{ message.deletedDateTime === null ? (message.contentType === 'html' ? renderHTML(message.content) : message.content) : this.renderDate(message.deletedDateTime) }
</div>
</div>
);
}
public renderDate(date: Date) {
return (
<div className={ styles.inlineBlock }>
<Moment format="d.M.yyyy">{date}</Moment> <Moment format="HH:mm:ss">{date}</Moment>
</div>
);
}
When I remove the 2nd .map() block + the comma right before, this one:
,
topicReplies.filter(r => r.topicMessageId === topic.id).map((reply: ITopicReply, indexReply) => {
return (
this.renderMessageBlock(reply.message, indexReply, true)
)
})
I get the first level messages, but I cannot get both to work. I haven't found yet a good example how this must be structured so that it works.
What do I need to change?
<div className={styles.conversationsArea}>
{/* The comma operator (,) evaluates each of its operands (from left to right) and returns the value of the last operand **/
channelTopics.map((topic: IChannelTopic, indexTopic) => {
return [
this.renderMessageBlock(topic.message, indexTopic),
topicReplies
.filter((r) => r.topicMessageId === topic.id)
.map((reply: ITopicReply, indexReply) => {
return this.renderMessageBlock(reply.message, indexReply, true);
})
]
})}
</div>

ReactJS rendering issue with edited array

Why does ReactJS remove the last element when the array is different after removing the middle element when using array.splice?
This is my code. I am using React-Redux.
const reducerNotesAndLogin = (state = initialState, action) => {
var tableNotes = "notities";
var tableCategories = "categories";
switch(action.type){
case "CATEGORY_REMOVE":
// Remove the category
var newCategories = state.categories;
console.log("state.categories", state.categories);
console.log("before: ", {newCategories});
var index = 0;
for(var i = 0; i < newCategories.length; i++){
if(newCategories[i].id === action.payload.categoryId){
newCategories.splice(i, 1);
index = i;
i--;
}
}
console.log("after: ", {newCategories});
state = {
...state,
categories: newCategories
}
break;
default:
break;
}
return state;
}
export default reducerNotesAndLogin;
Output below (I deleted the middle element. My web app always removes the last element of the categories (but not from the array).
Step 1: Initial state
Step 2: Remove middle item, expecting the middle item to be removed.
Step 3: Confusion
Why is the array correct, but the view incorrect? I am updating the state.categories correctly right?
This is my render code (as is - without filtering away any other code that mihgt be important)
CategoriesBody:
import React from 'react';
import { connect } from 'react-redux';
import CategoryItem from './CategoryItem';
import Button from './../../Button';
import store from '../../../redux/store-index';
class CategoriesBody extends React.Component {
render(){
return (
<div>
<ul className="list--notes">
{this.props.categories.map((category) => {
if(category.id === undefined){ // No categories
return <li>No categories</li>
} else {
return (
<div>
<CategoryItem category={category} />
<div className="mb-small hidden-sm hidden-md hidden-lg"> </div>
</div>
);
}
})}
</ul>
</div>
);
}
}
function mapStateToProps(state){
return {
categories: state.reducerNotesAndLogin.categories,
categoriesLength: state.reducerNotesAndLogin.categories.length
};
}
export default connect(mapStateToProps)(CategoriesBody);
CategoriesItem.js:
import React from 'react';
import store from './../../../redux/store-index';
import Button from './../../Button';
class CategoryItem extends React.Component {
constructor(props){
super();
this.state = {
edit: false,
categoryName: props.category.categoryName,
categoryColor: props.category.categoryColor
}
this.onClickEdit = this.onClickEdit.bind(this);
this.onChangeCategoryColor = this.onChangeCategoryColor.bind(this);
this.onChangeInputCategoryName = this.onChangeInputCategoryName.bind(this);
this.onClickEditSave = this.onClickEditSave.bind(this);
this.onClickEditCancel = this.onClickEditCancel.bind(this);
}
removeCategory(id, name){
console.log("nsvbsvbfjvbdjhbvv");
store.dispatch({ type: "CATEGORY_REMOVE", payload: {
categoryId: id
}});
// store.dispatch({type: "NOTIFY", payload: {
// type: 'success',
// message: 'Category "' + name + '" removed!'
// }});
}
onClickEdit(){
this.setState({
edit: true
});
}
onChangeCategoryColor(e){
this.setState({
categoryColor: e.target.value
});
}
onChangeInputCategoryName(e){
this.setState({
categoryName: e.target.value
});
}
onClickEditSave(){
this.setState({
edit: false,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
});
store.dispatch({type: "CATEGORY_EDIT", payload: {
categoryId: this.props.category.id,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
}});
store.dispatch({type: "NOTIFY", payload: {
type: "success",
message: "Category saved!"
}});
}
onClickEditCancel(){
this.setState({
edit: false,
categoryName: this.props.category.categoryName,
categoryColor: this.props.category.categoryColor
});
}
render(){
return (
<li key={this.props.category.id} className={this.state.edit === true ? "mt mb" : "flex-justify-between flex-align-center"}>
<div className={this.state.edit === true ? "d-none" : ""}>
<div className="input--color" style={{
backgroundColor: this.state.categoryColor
}}> </div>
{this.state.categoryName}
</div>
{/* Mobile */}
<div className={this.state.edit === true ? "d-none" : "hidden-sm hidden-md hidden-lg"}>
<Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button>
<div className="mt-small"> </div>
<Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button>
</div>
{/* Tablet and desktop */}
<div className={this.state.edit === true ? "d-none" : "hidden-xs"}>
<div style={{float:'left',}}><Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button></div>
<div style={{float:'left',marginLeft:'15px'}}><Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button></div>
</div>
{/* EDITING STATE */}
<div className={this.state.edit === true ? "" : "d-none"}>
<div className="row">
<div className="col-xs-12">
<input onChange={this.onChangeCategoryColor} className="input--wide" type="color" value={this.state.categoryColor}
style={{backgroundColor: this.state.categoryColor, height: '30px'}}
/>
<input onChange={this.onChangeInputCategoryName} className="input--wide" type="text" value={this.state.categoryName} />
</div>
</div>
<div className="row mt">
<div className="col-xs-12">
<Button buttonType="primary" onClick={() => this.onClickEditSave()}>Save</Button>
</div>
</div>
<div className="row mt-small">
<div className="col-xs-12">
<Button buttonType="secondary" onClick={() => this.onClickEditCancel()}>Cancel</Button>
</div>
</div>
</div>
</li>
)
}
}
export default CategoryItem;
I think it has something to do with the rendering. Because the arrays are correct when I console.log them. Only the view is different...
Do not modify the state in reducer directly. Create a copy of state value and then modify it.
Change:
var newCategories = state.categories;
To:
var newCategories = [...state.categories];
You should not modify the same array while looping through it.
for (var i = 0; i < newCategories.length; i++) {
if (newCategories[i].id === action.payload.categoryId) {
newCategories.splice(i, 1);
index = i;
i--;
}
}
I got the answer after looking through it with a friend of mine. The solution is pretty simple...
Lesson 101: Make sure that you have a unique "key" property when looping through an array in your UI.
The solution is to add this to my code:
<div key={category.id}>
{this.props.categories.map....
...
</div>

ReactJS: Reset-Render Child Component on Parent state change

I have a strange - at least for me :-) - issue with my Components.
I have a map() function generating a list of Child elements. When I change the filter and the list repopulates the state of the children remain as it was. For example, if the second Alumni Child Component has state {height:auto} the new second Allumni Child has again {height: auto}.
I find it really odd because this is not the same element, it's a new element corresponding to another person, with new content passed through props.
Long story short how do I force my new Child elements to have initial state {height: 0}?
There is a lot of details hidden but the core of my Parent App is as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
filterName: "",
filterInitial: "Α",
....
filteredGraduates:[],
loaderVisible:true
};
this.updateInitial = this.updateInitial.bind(this)
...
}
componentDidMount(){
...
}
updateInitial(letter) {
if (letter==='*'){
this.setState({
filterInitial: "*",
filteredGraduates : [...graduates]
})
}else{
this.setState({
filterInitial: letter,
filteredGraduates : graduates.filter( (graduate) => graduate.Surname.charAt(0)===letter )
})
}
console.log('this is the letter: ',letter);
}
render() {
return (
<div className="app">
<div className="filters">
<div className="name-pagination">
<span
onClick={ () => this.updateInitial('*')}
className={'*'==this.state.filterInitial? 'active': ''}
>Α - Ω
</span>
<hr/>
{letters.map( (letter) => {
return(
<span
onClick={ () => this.updateInitial(letter)}
className={letter==this.state.filterInitial? 'active': ''}
>{letter}</span>
)
}
)}
</div>
</div>
</div>
{this.state.loaderVisible && <Loader /> }
{!this.state.loaderVisible && <div className="graduates-wrapper">
{this.state.filteredGraduates
.sort( (x,y) => x.Surname.localeCompare(y.Surname) )
.map( (graduate) => {
return(
<div>
<Allumni
key={graduate.id}
.... a lot of props ...
/>
</div>
)
})
}
</div>}
</div>
);
}
}
and my Child is:
class Allumni extends React.Component {
constructor(props) {
super(props);
this.state = {
height:0
};
this.updateHeight = this.updateHeight.bind(this)
}
updateHeight() {
this.setState({
height: this.state.height === 0 ? 'auto' : 0,
});
};
render() {
const {
name,
surName,
.......
} = this.props
return (
<div className="allumni-wrapper">
<div className="allumni-main-info">
<span className="allumni-surname">{surName}</span><br/><span className="allumni-name">{name}</span>
</div>
<div className="allumni-extra">
<span className="allumni-year">{yearOf}</span><br/>
<span className="allumni-job">{job}</span> / <span className="allumni-home-city">{homeCity}</span>
</div>
<div className="allumni-details">
<AnimateHeight
duration={ 500 }
height={ this.state.height }
>
<p><span className="attribute-name">... {fatherName}</p>
<p><span className="attribute-name">...{studies}</p>
....
</AnimateHeight>
</div>
<div className="allumni-actions" onClick={this.updateHeight}>
<AccountCardDetailsOutlineIcon size={30}/>
<span className={'actions-toggle ' + (this.state.height >= 0 ? '' : 'rotated') }><ChevronDownIcon/></span>
</div>
</div>
);
}
}
export default Allumni
thank you

Categories