optimizely / nuclear-js

Reactive Flux built with ImmutableJS data structures. Framework agnostic.
https://optimizely.github.io/nuclear-js/
MIT License
2.23k stars 141 forks source link

[QUESTION] Observe is only called once instead of everytime something changes in the store #165

Open MinThaMie opened 9 years ago

MinThaMie commented 9 years ago

The goal is that some items can become favorite and are then rendered in a special list.

But my code does not work cause the observer just works once. Have this problem more often so somehow my implementation of observe isn't correct.

First the user clicks on a listitem with calls upon an action:

export default class LibraryItem extends React.Component {
    constructor(){
        super();
        this._addFavorite = this._addFavorite.bind(this);
    }
    _addFavorite(bid){
        var id = bid;
        libraryBlockActions.addFavorites({ bid : id });
    }

    render() {
//      console.log('render favoritelist state',this.state.favoriteList);
        var bid = this.props.bid;
        return(
            <ListItem primaryText={this.props.title} onTouchTap={this._addFavorite.bind(this, bid)} />
        );
    }

}
export function addFavorites(bidObj) {
    console.log('actionkey:', bidObj);
//  reactor.dispatch(actionTypes.ADD_FAVO, bidObj);
    reactor.dispatch(actionTypes.SWITCH_FAVO, bidObj);
}

This reaches the store:

var favoriteList = [];

function switchFavorite(state, payload, type ) {
    console.log("switched favorites bID:", payload.bid);
    var block = payload.bid;
    if (favoriteList.indexOf(block) === -1) {
        favoriteList.push(block);
        console.log("favolist", favoriteList);
    }
    else {
        var index = favoriteList.indexOf(block);
        favoriteList.splice(index, 1);
        console.log("favolist", favoriteList);
    }
    return favoriteList;
}

class FavoriteStore extends Store {

    constructor(props) {
        super(props);
    }

    getInitialState() {
        return toImmutable([]);
    }

    initialize() {
        this.on(actionTypes.SWITCH_FAVO, switchFavorite);
    }
}

const INSTANCE = new FavoriteStore();
export default INSTANCE;

Getter:

export const favoriteBlocks = [
    ['favoritesList'],
    (favoriteblocks) => {
        console.log('GETTER favoriteblocks',favoriteblocks);
        return favoriteblocks;
    }
];

and then the observer is called:

export default class FavoriteList extends React.Component {
    constructor(){
        super();
        this.state = {
            favoritesList: reactor.evaluate(favoriteListGetters.favoriteBlocks).favoritesList
        };
    }
    componentDidMount() {
        reactor.observe(favoriteListGetters.favoriteBlocks, (favoriteblocks) => {
            console.log('reactor observe favoriteblocks', favoriteblocks);
            this.setState({favoritelist: favoriteblocks});
            console.log('favoritelist:', this.state);
        });
    }
    render() {

        //TODO: Render the items added to the list
        return(
                <div>
                    <List subheader="Favo's" subheaderStyle={{ fontSize: 18}}>
                        {this.state}
                    </List>
                </div>
        );
    }

}
mindjuice commented 9 years ago

There is no need in Nuclear to call observe() to get the updated state into your component so that it re-renders.

Instead, you just define a getDataBindings(0 function for the component, and whenever the data changes, Nuclear will call setState() for you, thereby causing React to re-render your component.

Remove the observe call (you don't need componentDidMount() here either) and also remove the this.state = {} part from your constructor.

Then, add something like this to your React class:

getDataBindings() {
  return {
    favoritesList: favoriteListGetters.favoriteBlocks
  };
}

Then you can access this.state.favoritesList in your component.

I would also recommend (re)reading the NuclearJS docs on how getters work.

MinThaMie commented 9 years ago

@mindjuice Thank you for your answer. Sadly it still does not work. I tried rereading the NuclearJS docs, but I don't seem to get the hang of it. My code now is:


export default class FavoriteList extends React.Component {
    constructor(){
        super();
        this._getDataBindings = this._getDataBindings.bind(this);
        this.state={

        }
    }
    _getDataBindings(){
        return {
            favoritesList: favoriteListGetters.favoriteBlocks
        };
    }
    render() {
        //TODO: Render the items added to the list
        return(
                <div>
                    <List subheader="Favo's" subheaderStyle={{ fontSize: 18}}>
                        {this.state.favoritesList}
                    </List>
                </div>
        );
    }

}

Could you tell me where I go wrong, we could also do this over PM in Gitter if you would prefer that.

mindjuice commented 9 years ago

I haven't actually used Nuclear with ES6 class syntax. I use createClass() with the mixin instead.

The nuclear-js-react-addons repo has some wrappers and decorators that let you use ES6 syntax with Nuclear, so I'd look there next.

There was also some discussion about this here: https://github.com/optimizely/nuclear-js/issues/44