Change item image on hover

286

📘

Components:

  • [components/Cards/Product/view.tsx]

In order for the below code to work correctly, the front end will need a specific field to be returned in the API response. Namely, the filter field 'image_2_url' will have to be returned for at least search response, and optionally autocomplete (if desired).

Here's how to set that up.

  1. Go to your Findify Merchant Dashboard and navigate to Settings > Primary Setup > Filtering.

  2. Find 'image_2_url' within your lists of filters.

1880

👍

The filter doesn't have to be set to active in order to be included in the API response.

  1. Click on the pencil/edit icon to see further options, and toggle the desired buttons to the active setting: at least 'Return in Search Response', and also optionally 'Return in Autocomplete Response'.
1899

Now you're ready to work with the code.

In the below example, we will use withStateHandlers from recompose to add a state to the Image component. You can find out more about this helper in the official recompose documentation.

/**
 * @module components/Cards/Product
 */

import React from 'react'
import classNames from 'classnames'
import Image from 'components/common/Image'
import Truncate from 'components/common/Truncate'
import Text from 'components/Text'
import Rating from 'components/Cards/Product/Rating';
import Price from 'components/Cards/Product/Price';
import template from 'helpers/template';
import { DiscountSticker, OutOfStockSticker  } from 'components/Cards/Product/Stickers';
import { Map, List } from 'immutable'
import { IProduct, MJSConfiguration, ThemedSFCProps } from 'types';

// Import helper
import { withStateHandlers } from 'recompose';

const Title: any = ({ text, theme, ...rest }) => (
  <Text display-if={!!text} className={theme.title} {...rest}>{text}</Text>
);

const Description: any = ({ text, theme, ...rest }) => (
  <p
    display-if={!!text}
    className={theme.description}
    {...rest}
  >
    <Truncate>{text}</Truncate>
  </p>
);

export interface IProductCardProps extends ThemedSFCProps {
  item: IProduct;
  config: MJSConfiguration;
}

const ProductCardView: React.SFC<IProductCardProps> = ({
  item,
  config,
  theme,
 
  imageSrc, // Add state and handlers, that we've created at the end of the file
  onMouseOver,
  onMouseOut
}: any) => (
  <a
    onClick={item.onClick}
    href={item.get('product_url')}
    className={classNames(
      theme.root,
      config.get('simple') && theme.simple,
      theme.productCard,
    )}
  >
    // add events to be called when a user moves the mouse in or out of the container
    <div
      onMouseOver={onMouseOver}
      onMouseOut={onMouseOut}
      className={classNames(theme.imageWrap)}>
      <Image
        className={classNames(theme.image)}
        aspectRatio={config.getIn(['product', 'image', 'aspectRatio'], 1)}
        thumbnail={item.get('thumbnail_url')}
        // set the source
        src={imageSrc}
        alt={item.get('title')}
      />
      <div display-if={config.getIn(['product', 'stickers', 'display'])}>
        <DiscountSticker
          config={config}
          className={theme.discountSticker}
          discount={item.get('discount')}
          display-if={
            config.getIn(['stickers', 'discount']) &&
            config.getIn(['product', 'stickers', 'display']) &&
            item.get('discount', List()).size &&
            item.getIn(['stickers', 'discount'])
          } />
      </div>
    </div>
    <div display-if={config.getIn(['product', 'reviews', 'display']) && !!item.getIn(['reviews', 'count'])} className={theme.rating}>
      <Rating value={item.getIn(['reviews', 'average_score'])} count={item.getIn(['reviews', 'count'])} />
    </div>
    <div
      className={theme.variants}
      display-if={
        config.getIn(['product', 'variants', 'display']) &&
        item.get('variants', List()).size > 1
      }
      >
      {
        template(config.getIn(['product', 'i18n', 'variants'], 'Available in %s variants'))(
          item.get('variants', List()).size
        )
      }
    </div>
    <div className={theme.content}>
      <Title
        theme={theme}
        display-if={config.getIn(['product', 'title', 'display'])}
        text={item.get('title')}
        config={config.getIn(['product', 'title'])} />
      <Description
        theme={theme}
        display-if={config.getIn(['product', 'description', 'display'])}
        text={item.get('description')}
        config={config.getIn(['product', 'description'])} />
      <Price
        className={theme.priceWrapper}
        display-if={config.getIn(['product', 'price', 'display'])}
        price={item.get('price')}
        oldPrice={item.get('compare_at')}
        discount={item.get('discount')}
        currency={config.get('currency').toJS()} />
      <OutOfStockSticker
        display-if={item.getIn(['stickers', 'out-of-stock'])}
        config={config} />
    </div>
  </a>
)

// Wrap component with helper: set either image_url or image_2_url depending on which event is called
export default withStateHandlers(
  ({ item }) => ({ imageSrc: item.get('image_url') }),
  {
    onMouseOver: (_, { item }) => () => ({ imageSrc: item.get('image_2_url') || item.get('image_url') }),
    onMouseOut: (_, { item }) => () => ({ imageSrc: item.get('image_url') })
  }
)(ProductCardView);