0

I created a like button and a dislike button. My image has the dislike button by default. When I click on the dislike button, I would like this button to disappear and the like button to take its place. My problem is that the state changes when I check it, but I don't see the change on the page except when I reload the page: the change is saved in the database. Does anyone have any idea how to solve this? Here is my reducer.

My initialState:

const initialState = {
    loading: false,
    stories: [],
    error: "",
    currentPage: 1,
    totalPages: null,
    totalStories: null
}

 case LIKE:
        return {
            ...state,
            stories: state.stories.map(storie => {
                if(storie.id === action.payload.id) {
                    return  { ...storie, 
                        userlikes: action.payload.userlikes
                    }

                } else {
                    return storie;
                }
            })
        }

This is all my reducer

import {FETCH_GALLERY_REQUEST, FETCH_GALLERY_SUCCESS, FETCH_GALLERY_FAILURE, GALLERY_CLEAR, LIKE, DISLIKE } from "./galleryTypes";

const initialState = {
    loading: false,
    stories: [],
    error: "",
    currentPage: 1,
    totalPages: null,
    totalStories: null
}

const galleryReducer = (state = initialState, action) => {
    switch(action.type) {
        case FETCH_GALLERY_REQUEST:
            return {
                ...state,
                loading: action.payload,
            }
            
        case GALLERY_CLEAR:
            return {
                ...state,
                loading: false,
                stories: [],
                error: "",
                currentPage: 1,
                totalPages: null,
                totalStories: null
        }
        
        case FETCH_GALLERY_SUCCESS:
            return {
                ...state,
                loading: false,
                stories: [...state.stories, ...action.payload].filter( 
                    (storie, index) => index === [...state.stories, ...action.payload].findIndex( 
                        elem => elem.id === storie.id && elem.id === storie.id
                        )
                    ),
                currentPage: action.currentPage,
                totalPages: action.totalPages,
                totalStories: action.totalStories,
                error: ""
            }
    
        case FETCH_GALLERY_FAILURE:
            return {
                ...state,
                loading: false,
                stories: [],
                error: action.payload
            }

        
        case LIKE:
            return {
                ...state,
                stories: state.stories.map(storie => {
                    if(storie.id === action.payload.id) {
                        return  { 
                            ...storie, 
                            userlikes: action.payload.userlikes
                        }

                    } else {
                        return storie;
                    }
                })
            }


        default: 
            return state;
    }
}

export default galleryReducer

this is my vue component

 // permet d'ajouter un like
addLike = (story_id, user_id, index) => {
    const data = {
        story_id: story_id,
        user_id: user_id
    }
    this.props.like(story_id);
    this.props.fetchLike(data);

}

// permet denlever un like
removeLike = (story_id, user_id) => {
    this.setState({
        story_id: story_id
    })

    const data = {
        story_id: story_id,
        user_id: user_id
    }

    this.props.disLike(story_id);
    this.props.fetchDislike(story_id, user_id);
    
}

my render

{storiesData.stories.map((storie, index) => (
  <div
    key={index}
    className="everyprofile-container_details item"
    style={{ height: "450px" }}
  >
    <div className="everyprofile_name">
      <span className="everyProfile-firstname"> {storie.firstname} </span>
      <span className="everyProfile-lastname"> {storie.lastname} </span>
    </div>
    <div className="everyprofile-photo_cadre">
      <div
        className="everyprofile-photo"
        style={{
          backgroundImage: `url(${config.API_URL}resources/pictures_min/${storie.picture_path})`,
          backgroundPositionY: `${storie.bg_position_y}%`,
          backgroundPositionX: `${storie.bg_position_x}%`,
          backgroundSize: "cover"
        }}
      >
        {storie.userlikes == 1 ? (
          <button
            onClick={() => this.removeLike(storie.id, user_id, index)}
            className="everyprofile-like-btn"
            style={{ color: "#FF0000" }}
          >
            <Icon size={40} icon={ic_favorite} />
          </button>
        ) : (
          <button
            onClick={() => this.addLike(storie.id, user_id, index)}
            className="everyprofile-like-btn"
            style={{ color: "#FF0000" }}
          >
            <Icon size={40} icon={ic_favorite_border} />
          </button>
        )}
      </div>
    </div>
    <div className="everyprofile-container-story">
      <div className="everyprofile-story">{storie.story}</div>
    </div>
  </div>
))}


const mapStateToProps = (state) => {
  return {
    storiesData: state.stories,
    loading: state.stories.loading,
    currentPage: state.stories.currentPage,
    totalPages: state.stories.totalPages,
    totalStories: state.stories.totalStories,
    validateStory: state.validateStorie.validateStory,
    searchStatus: state.searchStatus.searchBar,
    query: state.query.query
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    // fetchEveryProfile: (storyId, userId) => dispatch(fetchEveryProfile(storyId, userId)),
    fetchLike: (data) => dispatch(fetchLike(data)),
    fetchDislike: (story_id, user_id) => dispatch(fetchDislike(story_id, user_id)),
    like: (story_id) => dispatch(like(story_id)),
    disLike: (story_id) => dispatch(disLike(story_id)),


    fetchGalleryFirst: (searchInfo) => dispatch(fetchGalleryFirst(searchInfo)),
    fetchGallery: (searchInfo) => dispatch(fetchGallery(searchInfo))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(EvryProfile)

This is actions

// Permet d'enlever le bouton like
export const like = (story_id) => {
    return {
        type: LIKE,
        payload: {
            id: story_id,
            userlikes: 1
        }
    }

}

// Permet d'enlever le bouton dislike
export const disLike = (id) => {
    return {
        type: DISLIKE,
        payload: id
    }
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Chris Issifou
  • 25
  • 1
  • 4
  • 4
    Can you include a more [Minimal, Complete, and Reproducible](https://stackoverflow.com/help/minimal-reproducible-example) code example? The component with the buttons, action creator, full reducer, how the component connects to your redux store, etc...? In my experience this is usually caused by either mutating a state object or simply not refetching backend data (because you didn't optimistically update your local cache). – Drew Reese Jan 01 '21 at 20:29
  • .map() on a list of objects is not enough to keep immutable state - the objects themselves in the list are still references - I suspect that may be your problem. See https://stackoverflow.com/questions/34716651/js-array-prototype-map-happens-to-be-mutable – ifo20 Jan 01 '21 at 20:29
  • 2
    @ifo20 OP is shallow copying the array *and* the `storie` that is being updated, I don't see a mutation issue specifically in this snippet. – Drew Reese Jan 01 '21 at 20:31

1 Answers1

1

Issue

Your reducer appears to be missing the case handling DISLIKE. Based on your LIKE case and UI logic I would assume updating stories[someIndex].userlikes to any value other than 1 should toggle the dislike button back to a like button.

Tracing the steps

  1. Button is clicked and removeLike called
  2. removeLike dispatches disLike and fetchDislike actions
  3. I assume fetchDislike is an async action, i.e. thunk, epic, saga, etc... and works, but dislike should be handled in a reducer.
  4. Look in reducer and see no case handling DISLIKE

Solution

Add case to handle DISLIKE and update the specific story's userlikes property.

switch (true) {
  case DISLIKE:
    return {
      ...state,
      stories: state.stories.map((story) =>
        storie.id === action.payload.id
          ? {
              ...story,
              userlikes: 0
            }
          : story
      )
    };
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thank you for your answer, indeed I have an async action for the registration in database and another action for the change at the level of the view before reloading the page. When the button like or dislike is clicked, I see the change in React dev tools, but the view button does not change, unless we reload the page – Chris Issifou Jan 03 '21 at 10:10
  • This is what I do not understand. Do I make well a copy of my state or not – Chris Issifou Jan 03 '21 at 10:15
  • @ChrisIssifou All the reducer cases you shared all appear to correctly return a new state object, so I don't think *that* is the issue. I suspect it may *some* reducer case that updates the `stories` array and/or using the array index as the react key. I don't see where you allow add/remove/reorder from your UI. Could you create a *running* codesandbox example that reproduces this issue? Start with just the local code first (ignore the async actions for now). – Drew Reese Jan 03 '21 at 10:23
  • 1
    Thank you so much, I found the problem, it comes from the puglin "owl carousel" that I use. – Chris Issifou Jan 03 '21 at 16:16