0

I am managing the states of my component using redux and accessing the array data for the component from redux store using a new map. I need to be able to get my icon's value to increment when clicked. I am not sure how to get access to the values of my reactions to make this work and handle my click event. I need to update the reactions of my card using the updateUploadReaction function. I want the value of the reactions to increment by 1 when the icon is clicked

I wrote out two lines of pseudo-code for a better understanding of the possible ways to go about it. Here's a link if you need to access the code directly. It is under Home directory

reducers.js

import { UPDATE_REACTION, REQUEST_UPLOAD_LIST } from './actionTypes';
import { INITIAL_STATE, USER_UPLOADS } from './constants';

/**
 * Creates a Javascript Map with the user uploads mapped by id
 *
 * @param {Array} USER_UPLOADS - a users uploads
 * @return {Map} - the user uploads
 */

function generateUploadsMap() {
    const uploads = new Map();

    USER_UPLOADS.forEach(userUpload => {
        const { id } = userUpload;

        uploads.set(id, userUpload);
    });

    return uploads;
}

function updateUploadReaction(id, reaction, uploads) {
    const updatedUploads = new Map([...uploads.entries()]);
    const userUpload = updatedUploads.get(id);

    const { type } = updatedUploads.reaction;
    // I need to get access values and increment
// Set reaction by type to value

    *// value.set(type, reaction);*

    updatedUploads.set(id, userUpload);
    return updatedUploads;
}
export default (state = { ...INITIAL_STATE }, action) => {
    switch (action.type) {
        case REQUEST_UPLOAD_LIST: {
            return {
                ...state,
                uploads: generateUploadsMap(),
            };
        }
        case UPDATE_REACTION: {
            const { uploads } = state;

            return {
                ...state,
                uploads: updateUploadReaction(action.id, action.reaction, uploads),
            };
        }

        default:
            return state;
    }
};

constants.js

/** @constant */
export const INITIAL_STATE = {
    uploads: new Map(),
};

export const USER_UPLOADS = [
    {
        id: 0,
        // eslint-disable-next-line max-len
        image: 'http://sugarweddings.com/files/styles/width-640/public/1.%20The%20Full%20Ankara%20Ball%20Wedding%20Gown%20@therealrhonkefella.PNG',
        reactions: {
            dislike: 0,
            like: 0,
            maybe: 0,
        },
        story: "It's my birthday next week! What do you think?",
        user: 'Chioma',
    },
    {
        id: 1,
        // eslint-disable-next-line max-len
        image: 'https://dailymedia.com.ng/wp-content/uploads/2018/10/7915550_img20181007141132_jpeg01c125e1588ffeee95a6f121c35cd378-1.jpg',
        reactions: {
            dislike: 0,
            like: 0,
            maybe: 0,
        },
        story: 'Going for an event. Do you like my outfit?',
        user: 'Simpcy',
    },
    {
        id: 2,
        // eslint-disable-next-line max-len
        image: 'https://i0.wp.com/www.od9jastyles.com/wp-content/uploads/2018/01/ankara-styles-ankara-styles-gown-ankara-tops-ankara-gowns-ankara-styles-pictures-latest-ankara-style-2018-latest-ankara-styles-ankara-ankara-styles.png?fit=437%2C544&ssl=1',
        reactions: {
            dislike: 0,
            like: 0,
            maybe: 0,
        },
        story: 'Saturdays are for weddings. Yay or nay?',
        user: 'Angela',
    },
];

Home.js

import PropTypes from 'prop-types';
import React from 'react';
import { Avatar, Card, Icon, List } from 'antd';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { LIST_TEXTS, STYLES } from '../constants';
import * as actions from '../actions';
import { getUploads } from '../selectors';

const { AVATAR, CARD_CONTAINER, CARD_LIST, ICON, USER_LIST } = STYLES;
const { INNER, MORE, UPLOAD, VERTICAL } = LIST_TEXTS;

const IconText = ({ type, text }) => (
    <span>
        <Icon type={type} style={ICON} />
        {text}
    </span>
);
function createReactionsIcon(item, updateReaction) {
    const { like, dislike, maybe } = item.reactions;
    const icons = [
        { reaction: 'like', text: `${like}`, type: 'heart' },
        { reaction: 'dislike', text: `${dislike}`, type: 'dislike' },
        { reaction: 'maybe', text: `${maybe}`, type: 'meh' },
    ];
    return icons.map(({ reaction, text, type }) => (
        <IconText
          onClick={() => updateReaction(item.id, reaction)}
          key={reaction}
          type={type}
          text={text}
        />
    ));
}

class Home extends React.Component {
    componentDidMount() {
        const { actions: { requestUploadList } } = this.props;

        requestUploadList();
    }

    updateReaction = (id, reaction) => {
        const { actions: { updateReaction } } = this.props;
        updateReaction(id, reaction);
    }

    render() {
        const { uploads } = this.props;
        const values = [...uploads.values()];

        return (
            <div style={CARD_CONTAINER}>
                <List
                  itemLayout={VERTICAL}
                  dataSource={values}
                  renderItem={item => (
                      <List.Item style={USER_LIST}>
                          <Card
                            actions={createReactionsIcon(item, this.updateReaction)}
                            cover={<img alt={UPLOAD} src={item.image} />}
                            extra={<Icon type={MORE} />}
                            hoverable
                            title={(
                                <a href="/">
                                    <Avatar src={item.image} style={AVATAR} />
                                    {item.user}
                                </a>
                            )}
                            type={INNER}
                            style={CARD_LIST}
                          >
                              {item.story}
                          </Card>
                      </List.Item>
                  )}
                />
            </div>
        );
    }
}

Home.propTypes = {
    actions: PropTypes.object,
    uploads: PropTypes.instanceOf(Map),
};

const mapStateToProps = state => ({
    uploads: getUploads(state),
});

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(actions, dispatch),
});

export default connect(mapStateToProps, mapDispatchToProps)(Home);
Ada
  • 559
  • 1
  • 3
  • 19
  • A few tips, the `updateReaction` method must be bound to the instance with `bind` in the constructor, so that you can use it from `this`, it seems like you are calling it recursively? As soon I see `redux` and `Map` I know there will be dragons out there. Maps don't work with redux, their changes can't be detected as they are not optimized for immutability, the same instance produced with `new Map()` will have different values, but the same heap reference. Also,about redux in this case, I use redux a lot, but *do* other app components *need* to know of this specific state in this example? – Dimitar Nikovski Apr 30 '19 at 11:34

1 Answers1

0

Two things to make things working in code -

1) Home.jsx - You are not using onClick in IconText component only passing it.

const IconText = ({ type, text, onClick }) => {
  return (
    <span>
      <Icon type={type} style={ICON} onClick={onClick} />
      {text}
    </span>
  );
};

2) Reducers.js - I am not sure where you want to write incremental logic, I have just written it inside reducer and is working.

function updateUploadReaction(id, reaction, uploads) {
  const updatedUploads = new Map([...uploads.entries()]);
  const userUpload = updatedUploads.get(id);
  userUpload.reactions[reaction] = userUpload.reactions[reaction] + 1;
  updatedUploads.set(id, userUpload);
  return updatedUploads;
}

At last working codesanbox link

Hope that helps!!!

tarzen chugh
  • 10,561
  • 4
  • 20
  • 30
  • This is part of what I want. I also want to be able to reverse number to previous state when clicked again. – Ada Apr 30 '19 at 14:42
  • I think using redux for things that are local to the component is overkill. See this [link](https://stackoverflow.com/questions/35328056/react-redux-should-all-component-states-be-kept-in-redux-store) when to put state into store. For local state toggling icon you could refer to this [link](https://stackoverflow.com/questions/49814095/change-button-icon-on-click-react) – tarzen chugh Apr 30 '19 at 16:39