0

I'm learning to implement Unstated in my react app.

I made a simple app that shows a Note using Unstated.

This is my NoteContainer:

class NoteContainer extends Container {
    state = {
        title: 'My Note',
        desc: 'This note state is managed with unstated',
        isDisplaying: false,
    }

    constructor() {
        super()
        this.show = this.show.bind(this);
    }

    show() {
        this.setState({ isDisplaying: !this.state.isDisplaying })
        console.log(this.state);
    }
}

As you see, is really simple, it just change the isDisplaying property in the state so i can use it on Note.js component to show the title and the message of the note like this:

class Note extends Component {
    state = {
        isShowing: false,
    }

    render() {
        if (this.state.isShowing) {
            return (
                <Subscribe to={[NoteContainer]}>
                    {noteContainer => (
                        <div className="container">
                            <p>{noteContainer.state.title}</p>
                            <p>{noteContainer.state.desc}</p>
                        </div>
                    )}
                </Subscribe>
            )
        }
        return (
                <Subscribe to={[NoteContainer]}>
                    {noteContainer => (

                        <div className="container">
                            <button className="btn btn-success" onClick={() => {
                                noteContainer.show();
                                this.setState({
                                    isShowing: noteContainer.state.isDisplaying,
                                })
                                console.log(this.state);
                            }}>
                                See Note!
                            </button>
                        </div>
                    )}
                </Subscribe>
            )
    }
}

The desired functionallity is that when i click the See Note! button, it shows the title and the description of the note above the button, and if i click it again, it hides them.

However, what i obtain is that it creates another Subscribe component and this appears to delete the "See Note!" button part. Here is an image of the result.

Result

The problem it's that i cant use the button again to hide the note information, i'm clearly using the subscribe component wrong, but i can't figure another way to use a conditional using unstated, so any help on this would be appreciated.

Thanks in advance, have a nice week!

Rohr Facu
  • 607
  • 1
  • 12
  • 29

2 Answers2

0

This is an anti pattern:

this.setState({ isDisplaying: !this.state.isDisplaying })

Either check first and then set state:

let answer = this.state.isDisplaying ? true : false
this.setState({isDisplaying: answer})

Or use prevState variable

this.setState((prevState, props) => ({
  isDisplaying: prevState.isDisplaying ? true : false
}));
GifCo
  • 1,350
  • 7
  • 13
  • Hi! This might be an antipattern, but it's not the focus of the question, the focus is how to manage a conditional to show both the button and the Note information in the Subscribe component. – Rohr Facu Feb 04 '19 at 15:12
  • What's the difference between your first and second example? How does assigning the new value to variable first, then calling setState makes any difference to doing that inline? – Joel Cox Feb 04 '19 at 15:57
  • @RohrFacu well its more than an anti pattern it is just wrong. Calls to setState are aysnc you cant use this.state in setState() – GifCo Feb 04 '19 at 16:43
  • @JoelCox nothing really. see my above answer, setState() is async never use this.state in setState() and see here https://reactjs.org/docs/faq-state.html – GifCo Feb 04 '19 at 16:45
  • @GifCo - Of course you could. "this.state" is not async. "this.setState" being async is irrelevant as "this.state" is checked before. – Adrian Bartholomew Mar 17 '19 at 16:22
  • @AdrianBartholomew no its not. As per the react docs I already linked to - "Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState" – GifCo Mar 18 '19 at 17:09
  • WRT - "this.setState({ isDisplaying: !this.state.isDisplaying })", this.state is checked BEFORE this.setState is executed. – Adrian Bartholomew Mar 26 '19 at 13:49
0

I've managed to solve it with an inline if conditional. However i don't feel confortable about this solution, i would like to know how to implement a complete if conditional, so if anyone knows please leave a comment!

render() {
        return (
            <Subscribe to={[NoteContainer]}>
                {noteContainer => (
                    <div className="container">
                        <p>{noteContainer.state.isDisplaying ? noteContainer.state.title : ''}</p>
                        <p>{noteContainer.state.isDisplaying ? noteContainer.state.desc : ''}</p>
                        <button className="btn btn-success" onClick={() => {
                            noteContainer.show();
                        }}>
                            See Note!
                        </button>
                    </div>
                )}
            </Subscribe>
        )
    }
Rohr Facu
  • 607
  • 1
  • 12
  • 29
  • 1
    That is how you render things in react. Inline ternaries are a standard pattern. You would use the exact same thing when loading data, but instead of just displaying nothing if the ternary is false you display a Loading spinner. Once state updates and ternary is true loader is replaced by rendered data or imgs or whatever. – GifCo Feb 04 '19 at 16:47
  • Thanks a lot @GifCo, didn't know that! – Rohr Facu Feb 04 '19 at 17:29