2

I'm working on small drag and drop app.
I want to be able to persist it's state after page refresh so I thought about using Redux and persisting chosen keys.
I'm using React-dnd and have drag and drop logic already in place. On some events (e.g. drop) I've added actions to be dispatched. These actions change redux state and move chosen items from one list to another. I can see the changes in redux store but they don't appear in the UI. Basically props doesn't change even though Redux state does change. At the same time using the same selectors I receive correct initial state of my UI.

My issue: props don't update on state change.

My question: why and what am I doing wrong?

MaveISeenItContainer.js

import React, {Component} from 'react';
import {connect} from 'react-redux';
import HTML5Backend from 'react-dnd-html5-backend';
import {DragDropContext} from 'react-dnd';

import * as actions from '../../actions/MoviesActions';

import HaveISeenItComponent from './HaveISeenItComponent';
import {getSelectedMovies, getAllMovies, getStatus} from '../../reducers/MoviesReducer';

let HaveISeenItContainer = props => <HaveISeenItComponent {...props} />;

const mapDispatchToProps = dispatch => ({
    getMovies: () => dispatch(actions.getMovies.request()),
    selectMovie: values => dispatch(actions.selectMovie.request(values)),
    removeMovie:  values => dispatch(actions.removeMovie.request(values))
});

const mapStateToProps = state => ({
    allMovies: getAllMovies(state),
    selectedMovies: getSelectedMovies(state),
    status: getStatus(state)
});

HaveISeenItContainer = DragDropContext(HTML5Backend)(HaveISeenItContainer);

export default connect(mapStateToProps, mapDispatchToProps)(HaveISeenItContainer);

HaveISeenItComponent.js

class HaveISeenItComponent extends Component {
    componentDidMount(id, index) {
        if (this.props.status !== 'SUCCESS') {
            this.props.getMovies();
        }
    }

    handleMovieSelect = (id, card) => {
        this.props.selectMovie({listId: id, id: card.id, card: card})
    };

    handleMovieRemove = (id, index) => {
        this.props.removeMovie({listId: id, cardId: index});
    };

    render() {
        const {selectedMovies, allMovies} = this.props;
        let availableMoviesList = null;
        let moviesToBeSeenList = null;
        if (this.props.status === 'SUCCESS') {
            const allMoviesList = _.map(allMovies, (value, key) => ({
                    id: key,
                    title: value.title
                })
            );
            const selectedMoviesList = _.map(selectedMovies, (value, key) => ({
                    id: key,
                    title: value.title
                })
            );
            availableMoviesList = <List
                id={1}
                list={allMoviesList}
                onMovieRemove={this.handleMovieRemove}
                onMovieSelect={this.handleMovieSelect}
            />;
            moviesToBeSeenList = <List
                id={2}
                list={selectedMoviesList}
                onMovieRemove={this.handleMovieRemove}
                onMovieSelect={this.handleMovieSelect}
            />;
        }
        return (
            <div className={styles.mainComponent}>
                {moviesToBeSeenList}
                {availableMoviesList}
            </div>
        );
    }
}

HaveISeenItComponent.propTypes = {
    getMovies: PropTypes.func
};

export default HaveISeenItComponent;

List.js

class List extends Component {
    constructor(props) {
        super(props);
        this.state = {cards: props.list};
    }

    pushCard(card) {
        const {onMovieSelect, id} = this.props;
        onMovieSelect(id, card);
    }

    removeCard(index) {
        const {onMovieRemove, id} = this.props;
        onMovieRemove(id, index);
    }

    moveCard(dragIndex, hoverIndex) {
        const {cards} = this.state;
        const dragCard = cards[dragIndex];
        this.setState(update(this.state, {
            cards: {
                $splice: [
                    [dragIndex, 1],
                    [hoverIndex, 0, dragCard]
                ]
            }
        }));
    }

    render() {
        const {cards} = this.state;
        const {canDrop, isOver, connectDropTarget} = this.props;
        const isActive = canDrop && isOver;
        const style = {
            width: "200px",
            height: "404px",
            border: '1px dashed gray'
        };

        const backgroundColor = isActive ? 'lightgreen' : '#FFF';

        return connectDropTarget(
            <div style={{...style, backgroundColor}}>
                {cards.map((card, i) => {
                    return (
                        <Card
                            key={card.id}
                            index={i}
                            listId={this.props.id}
                            card={card}
                            removeCard={this.removeCard.bind(this)}
                            moveCard={this.moveCard.bind(this)}/>
                    );
                })}
            </div>
        );
    }
}

const cardTarget = {
    drop(props, monitor, component) {
        const {id} = props;
        const sourceObj = monitor.getItem();
        if (id !== sourceObj.listId) component.pushCard(sourceObj.card);
        return {
            listId: id
        };
    }
}

export default DropTarget("Card", cardTarget, (connect, monitor) => ({
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    canDrop: monitor.canDrop()
}))(List);

MoviesReducer.js (with basic selectors)

import _ from "lodash";

// import * as actions from 'actions/MoviesActions';
// import { REQUEST_STATUS } from "constants/types";

import * as actions from '../actions/MoviesActions';
import {REQUEST_STATUS} from '../constants/types';
import {LIST_INDEX} from '../constants/constants';

const initialState = {
    allMovies: [],
    selectedMovies: []
};

const MoviesReducer = (state = initialState, action) => {

    switch(action.type){
        case actions.GET_MOVIES.REQUEST:
            return {
                ...state,
                status: REQUEST_STATUS.PENDING
            };

        case actions.GET_MOVIES.SUCCESS:
            return {
                ...state,
                status: REQUEST_STATUS.SUCCESS,
                allMovies: action.response.movies
            };

        case actions.GET_MOVIES.FAILURE:
            return {
                ...state,
                status: REQUEST_STATUS.FAILURE
            };

        case actions.SELECT_MOVIE.REQUEST:
            if(action.values.listId === LIST_INDEX.SELECTED_MOVIES){
                state.selectedMovies.splice(action.values.cardId, 0, action.values.card);
            } else if (action.values.listId === LIST_INDEX.ALL_MOVIES){
                state.allMovies.splice(action.values.cardId, 0, action.values.card);
            }
            return state;

        case actions.REMOVE_MOVIE.REQUEST:
            if(action.values.listId === LIST_INDEX.SELECTED_MOVIES){
                state.selectedMovies.splice(action.values.cardId, 1);
            } else if (action.values.listId === LIST_INDEX.ALL_MOVIES){
                state.allMovies.splice(action.values.cardId, 1);
            }
            return state;

        case actions.MOVE_MOVIE.REQUEST:
            return state;

        default:
            return state;
    }
};

export const MOVIES_STATE_KEY = 'moviesState';

// Selectors
export const getStatus = state => _.get(state, [MOVIES_STATE_KEY, 'status']);

export const getAllMovies = state => _.get(state, [MOVIES_STATE_KEY, 'allMovies']);

export const getSelectedMovies = state => _.get(state, [MOVIES_STATE_KEY, 'selectedMovies']);

export default MoviesReducer;
ninigi
  • 143
  • 1
  • 3
  • 14
  • please add your selectors code, maybe the problem is there. – shahar taite Apr 08 '18 at 15:13
  • @shahartaite, thank you for your suggestion I added selectors code. – ninigi Apr 08 '18 at 15:20
  • do you have moviesState defined as a property on your state object? You also have no `status` property on the `initialState` and you do not provide a default value to the `get` function. – Theo Wittkovskiy Apr 08 '18 at 15:52
  • I have one more suggestion. It could have something to do with your higher order components. They might have gone lost along the way. Maybe define a constructor, where you pass in the props. – Theo Wittkovskiy Apr 08 '18 at 16:00

0 Answers0