15

I have a React Component and I am creating 2 copies of it. However, when the state of 1 of them is updated, it updates the 2nd one too. I am not sure what is causing this.

Here is my code:

This is the place where I am using 2 components:

{this.state.selectedTab === 0 ?
    <Carousel
       size={1}
       cultureInfo={this.state.cultureInfo}
       collaboratorError={this.state.activeCollaborationError}
       getPinnedError={this.state.getPinnedUsersError}
       pinActionError={this.state.isPinnedServerError}
       user={this.state.activeCollaboratorContactsData}
       isPinnedTab={false}
       startDate={this.state.startDate}
       isMapped={this.state.isMapped}
       resetPosition={this.state.resetCollabCarousel}
       isVisible={this.state.selectedTab === 0 }
/>
:
<Carousel
  size={1}
  cultureInfo={this.state.cultureInfo}
  collaboratorError={this.state.activeCollaborationError}
  getPinnedError={this.state.getPinnedUsersError}
  pinActionError={this.state.isPinnedServerError}
  user={this.state.pinnedUsers}
  isPinnedTab={true}
  startDate={this.state.startDate}
  isMapped={this.state.isMapped}
  resetPosition={this.state.resetPinnedCarousel}
  isVisible={this.state.selectedTab === 1}
/>
}

Here is the Carousel Code:

interface ICarouselProps {
 size: number;
 cultureInfo: CultureInfo;
 collaboratorError: Error.DataLayerError;
 getPinnedError: Error.DataLayerError;
 pinActionError: Error.DataLayerError;
 user: ActiveCollaboratorContact[];
 isPinnedTab: boolean;
 startDate: Date;
 isMapped: boolean;
 resetPosition: boolean;
 isVisible: boolean;
}

interface ICarouselState {
 position: number;
 width: number;
 isActiveNext: boolean;
 isActivePrev: boolean;
 isActive: number;
 }

 const numSlides = 2;
 const width = 100;
 const move = width / numSlides;
 const max = width - move;
 const min = -max;

 const numOfCards = 4;

 export default class Carousel extends BaseComponent<ICarouselProps,     

      ICarouselState> {
constructor() {
    super();
    this.resetState();
}

componentWillReceiveProps(newProps: ICarouselProps): void {
    if (newProps && newProps.resetPosition) {
        this.resetState();
    }
}

doRender(): React.ReactElement<{}> {
    let wrapperWidth: number;
    let widthStyle: string;
    wrapperWidth = (this.state as ICarouselState).width;

    const style:  React.CSSProperties = {
        WebkitTransform: "translateX(" + this.state.position + "%)",
        transform: "translateX(" + this.state.position + "%)",
        MozTransform: "translateX(" + this.state.position + "%)",
        msTransform: "translateX(" + this.state.position + "%)",
        OTransform: "translateX(" + this.state.position + "%)",
        width: wrapperWidth + "%"
    } as React.CSSProperties;
    const slideWidth:  React.CSSProperties = {width: move + "%"} as React.CSSProperties;
    let nextIsDisabled: boolean;
    let prevIsDisabled: boolean;
    let slideActive: string;

    widthStyle = (this.props.size === 0) ? styles.vCard : styles.hCard;
    nextIsDisabled = !this.state.isActiveNext;
    prevIsDisabled = !this.state.isActivePrev;
    slideActive = "slideActive" + this.state.isActive;

        const totalUsers = this.props.user.length;
        const articles: JSX.Element[] = [];


        for (let i = 0; i < 2; i++) {
            const cards: JSX.Element[] = [];
            for (let j = 0; j < numOfCards; j++) {
                const userIndex = i * numOfCards + j;
                if (userIndex < totalUsers) {
                    const card: JSX.Element = <Card
                        cultureInfo={this.props.cultureInfo}
                        user={this.props.user[userIndex]}
                        startDate={this.props.startDate}
                        extraMargin={userIndex % numOfCards === 0}
                    />;
                    cards.push(card);
                }
            }
            const article: JSX.Element =
                    <article className={styles.slideSingle}
                            tabIndex={0}
                            style={slideWidth}>
                        {cards}
                    </article>;
            articles.push(article);
        }
        return this.props.isVisible ?
                <div>
                    <div className={widthStyle + " " + styles.carousel + " " + slideActive} tabIndex={-1} id="Carousel">
                        <div className={styles.slideWrapper}
                            style={style}>
                            {articles}
                        </div>
                        <nav className={styles.nav}>
                            <ul className={styles.arrows}>
                                <li className={styles.stepLeft}>
                                <a disabled={prevIsDisabled} aria-disabled={prevIsDisabled} className={styles.previous} href="#"
                                    tabIndex={0}
                                    onClick = {this.prevSlideClicked}>
                                    BUTTONPREVTEXT
                                </a>
                                </li>
                                <li className={styles.stepRight}>
                                <a disabled={nextIsDisabled} aria-disabled={nextIsDisabled} className={styles.next} href="#"
                                    tabIndex={0}
                                    onClick = {this.nextSlideClicked}>BUTTONNEXTTEXT
                                </a>
                                </li>
                            </ul>
                        </nav>
                    </div>
               </div> :
               null;

};

private resetState(): void {
    const newState: ICarouselState = {
            position: 0,
            width: numSlides * 100,
            isActiveNext: true,
            isActivePrev: false,
            isActive: 0
        };

    if (this.state) {
        this.setState(newState);
    } else {
        this.state = newState;
    }
}

private nextSlideClicked: () => void = () => {
    if (this.state.position > min + move) {
        this.setState({
            position: this.state.position - move,
            width: numSlides * 100,
            isActiveNext: true,
            isActivePrev: true,
            isActive: Math.abs((this.state.position - move) / move)
        });
    } else if ((this.state as ICarouselState).position > min) {
        this.setState({
            position: this.state.position - move,
            width: numSlides * 100,
            isActiveNext: false,
            isActivePrev: true,
            isActive: Math.abs((this.state.position - move) / move)
        });
    }
};

private prevSlideClicked: () => void = () => {
    if (this.state.position < 0 - move) {
        this.setState({
            position: this.state.position + move,
            width: numSlides * 100,
            isActiveNext: true,
            isActivePrev: true,
            isActive: Math.abs((this.state.position + move) / move)
        });
    } else if (this.state.position < 0) {
        this.setState({
            position: this.state.position + move,
            width: numSlides * 100,
            isActiveNext: true,
            isActivePrev: false,
            isActive: Math.abs((this.state.position + move) / move)
        });
    }
};

private getCarouselName(): CarouselNames {
    return "name"
}

};

Thanks.

Programmer
  • 239
  • 3
  • 9
  • 1
    Would you please post the code for the `Carousel` component? – Brad Aug 02 '16 at 01:02
  • 2
    Why you use `this.state = newState;`? This can cause weird behaviour...: _NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable._ (https://facebook.github.io/react/docs/component-api.html) – Dherik Aug 02 '16 at 01:03
  • I set this.state because this call is from the Constructor, where this.state is not initialized. – Programmer Aug 02 '16 at 03:27
  • Those two carousels can't exist at the same time (thus `isVisible` is redundant), how can you say they're 'getting the same state'? If possible, strip your component(s) down to just the relevant parts and post the whole thing in your question. – David Gilbertson Aug 02 '16 at 04:30
  • I know they are getting the same state set, because when I click the next button on one, the 2nd's position is also set to that value. That is when the condition for 2nd's visibility becomes true, it shows the same position as the other one instead of a fresh position. – Programmer Aug 02 '16 at 05:19
  • I have added the code above. – Programmer Aug 02 '16 at 18:22
  • hi - I added the Carousel code here yesterday, any ideas on what is happening? – Programmer Aug 03 '16 at 17:49
  • @Programmer, Have you got any solution on this? – Kalpesh Wadekar Oct 13 '16 at 11:51
  • I do not think so, but somehow we moved on :) – Programmer Mar 18 '17 at 06:44

2 Answers2

20

I had the same problem. I set a distinct 'key' property on each instance and the problem went away.

  • 1
    Thank you this was the problem! I knew about the key for when we render multiple elements using map() as it complains in the console. But if you use just manually paste 2 components that use state inside the state is not reset unless you add a key. – user567068 Jul 29 '20 at 15:30
4

If you are using the same component for a different route (react-router) then you have to set key property for each route.

Keys help React identify which items have changed, are added, or are removed.

For this kind of situation where you want to use the same component multiple times, you have to set key property. Otherwise, each component will have the same state.

Jaied
  • 900
  • 9
  • 13