Skip to content

Compatibility Issue: react-leaflet Causes Image Rendering Failure in react-material-ui-carousel #239

Description

@andrer0cha

I'm building a SaaS with the following stack:

  • Ruby 3.4.1
  • Ruby on Rails 8.0.0
  • React 19.0.0
  • Vite 5.0.0
  • Typescript 5.3.2

In my application, I was using react-material-ui-carousel (version 3.4.2) on a page without any issues. However, after adding react-leaflet (version 5.0.0-rc.2), the carousel I had and another one I tried to add stopped working. When I remove react-leaflet and its dependencies, the carousel functions correctly again.

The issue I observe is that the first image appears in the carousel, but all the other images render as blank divs.

This problem is known, and I found it mentioned in a few places(i.e.: here), but none of the solutions work when I have leaflet (version 1.9.4) installed. Since leaflet works when having the react-material-ui-carousel installed, I'm creating the issue here, but I'm unsure where I should report it first.

// package.json
{
  "name": "marteleiro",
  "private": true,
  "type": "module",
  "license": "MIT",
  "dependencies": {
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.0",
    "@fontsource/roboto": "^5.1.1",
    "@fortawesome/fontawesome-free": "6.1.1",
    "@mui/icons-material": "^7.0.2",
    "@mui/material": "^6.4.6",
    "@mui/styles": "^6.4.8",
    "@mui/x-date-pickers": "^7.27.1",
    "@popperjs/core": "2.11.8",
    "@rails/webpacker": "5.4.4",
    "@react-oauth/google": "^0.12.1",
    "@stripe/stripe-js": "^5.7.0",
    "@types/leaflet": "^1.9.17",
    "@vitejs/plugin-react-swc": "^3.8.0",
    "axios": "^1.8.1",
    "bootstrap": "5.3.2",
    "dayjs": "^1.11.13",
    "jquery": "3.7.1",
    "js-cookie": "^3.0.5",
    "leaflet": "^1.9.4",
    "react": "^19.0.0",
    "react-currency-mask": "^1.3.3",
    "react-dom": "^19.0.0",
    "react-facebook": "^9.0.12",
    "react-hook-form": "^7.54.2",
    "react-leaflet": "^5.0.0-rc.2",
    "react-material-ui-carousel": "^3.4.2",
    "react-router-dom": "^7.2.0",
    "react-toastify": "^11.0.5",
    "toastr": "2.1.4"
  },
  "version": "0.1.0",
  "devDependencies": {
    "@eslint/js": "^8.52.0",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "@typescript-eslint/eslint-plugin": "^6.13.0",
    "@typescript-eslint/parser": "^6.13.0",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^8.52.0",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "prettier": "^3.0.3",
    "ts-jest": "^29.2.6",
    "typescript": "^5.3.2",
    "vite": "^5.0.0",
    "vite-plugin-ruby": "^5.1.0",
    "vite-tsconfig-paths": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  },
  "scripts": {
    "test": "jest",
    "lint": "eslint . --ext .ts,.tsx",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "format:fix": "prettier --write .",
    "lint-format": "yarn format && yarn lint"
  }
}
// Component with both leaflet and react-material-ui-carousel
import React, { useEffect, useState } from 'react';
import { MapContainer, Marker, TileLayer, useMap } from 'react-leaflet'
import { useParams, useNavigate } from 'react-router-dom';
import {
  Box,
  Typography,
  Grid,
  Paper,
  Chip,
  Divider,
  Button,
  Card,
  CardContent,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableRow,
  useTheme,
  useMediaQuery,
  IconButton,
  Tabs,
  Tab,
  List,
  ListItem,
  ListItemIcon,
  ListItemText,
  Tooltip,
} from '@mui/material';
import {
  Home,
  BedOutlined,
  ShowerOutlined,
  DirectionsCar,
  SquareFoot,
  Favorite,
  FavoriteBorder,
  CalendarToday,
  Gavel,
  Description,
  LocationOn,
  DocumentScanner,
  ArrowBack,
  Balance,
  LockPerson,
} from '@mui/icons-material';
import { formatCurrency } from '@utils/utils';
import { propertyService } from '@services/PropertyService';
import Loading from '@shared/Loading';
import Carousel from 'react-material-ui-carousel';
import '@styles/property_detail.scss';

// Interface para TabPanel
interface TabPanelProps {
  children?: React.ReactNode;
  index: number;
  value: number;
}
...


function a11yProps(index: number) {
  return {
    id: `property-tab-${index}`,
    'aria-controls': `property-tabpanel-${index}`,
  };
}

export default function PropertyDetail() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
  const [property, setProperty] = useState<any>(null);
  const [loading, setLoading] = useState(true);
  const [isFavorite, setIsFavorite] = useState(false);
  const [tabValue, setTabValue] = useState(0);
  const [opportunityId, setOpportunityId] = useState<string | null>(null);
  const [currentImageIndex, setCurrentImageIndex] = useState(0);

  useEffect(() => {
    const fetchPropertyDetails = async () => {
      try {
        setLoading(true);
        const propertyData = await propertyService.getPropertyById(id as string);
        setProperty(propertyData);
        
        // // Verificar se já é favorito
        // const userOpportunities = await opportunityService.getUserOpportunities();
        // const existingOpportunity = userOpportunities.find(
        //   (opp) => opp.property.id === id
        // );
        
        // if (existingOpportunity) {
        //   setIsFavorite(true);
        //   setOpportunityId(existingOpportunity.id);
        // }
      } catch (error) {
        console.error('Erro ao buscar detalhes da propriedade:', error);
      } finally {
        setLoading(false);
      }
    };

    if (id) {
      fetchPropertyDetails();
    }
  }, [id]);

  const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
    setTabValue(newValue);
  };

  // const handleToggleFavorite = async () => {
  //   try {
  //     if (isFavorite && opportunityId) {
  //       // Remover dos favoritos
  //       await opportunityService.deleteOpportunity(opportunityId);
  //       setIsFavorite(false);
  //       setOpportunityId(null);
  //     } else {
  //       // Adicionar aos favoritos
  //       const response = await opportunityService.createOpportunity(id as string);
  //       setIsFavorite(true);
  //       setOpportunityId(response.id);
  //     }
  //   } catch (error) {
  //     console.error('Erro ao atualizar favoritos:', error);
  //   }
  // };

  const handleBack = () => {
    navigate(-1);
  };

      ...

      <Grid container spacing={3}>
        {/* Coluna da esquerda com imagens e informações principais */}
        <Grid item xs={12} md={8}>
          {/* Carrossel de imagens */}
          {property.images && !!property.images.length ? (
            <Paper elevation={0} sx={{ mb: 3, borderRadius: 2, overflow: 'hidden', position: 'relative' }}>
              <Carousel
                autoPlay={false}
                animation="slide"
                indicators={true}
                navButtonsAlwaysVisible={true}
                cycleNavigation={true}
                index={currentImageIndex}
                onChange={(index) => setCurrentImageIndex(index)}
                navButtonsProps={{
                  style: {
                    backgroundColor: 'rgba(0, 0, 0, 0.5)',
                    padding: '10px',
                    margin: '0 20px',
                    zIndex: 2,
                  }
                }}
                indicatorContainerProps={{
                  style: {
                    position: 'absolute',
                    bottom: '16px',
                    zIndex: 999999,
                  }
                }}
                className="property-carousel"
              >
                {property.images.map((img_link: string, index: number) => (
                  <Box 
                    key={index}
                    sx={{
                      height: isMobile ? '250px' : '400px',
                      width: '100%',
                      backgroundImage: `url(${img_link})`,
                      backgroundPosition: 'center',
                      backgroundSize: 'cover',
                    }}
                  >
                    {/* <img
                      src={image}
                      alt={`Imagem ${index + 1} do imóvel ${property.name}`}
                      style={{
                        width: '100%',
                        height: '100%',
                        objectFit: 'cover',
                      }}
                    /> */}
                  </Box>
                ))}
              </Carousel>
            </Paper>
          ) : (
            <Paper elevation={0} sx={{ mb: 3, borderRadius: 2, p: 5, textAlign: 'center', bgcolor: 'grey.100' }}>
              <Typography variant="body1" color="text.secondary">
                Nenhuma imagem disponível para este imóvel.
              </Typography>
            </Paper>
          )}

          {/* Miniaturas de imagens adicionais */}
          {property.images && property.images.length > 1 && (
            <Box sx={{ display: 'flex', gap: 1, mb: 3, overflowX: 'auto', pb: 1 }}>
              {property.images.map((img: string, index: number) => (
                <Box 
                  key={index}
                  component="img"
                  src={img}
                  alt={`Imagem ${index + 1}`}
                  sx={{ 
                    height: 80,
                    width: 120,
                    objectFit: 'cover',
                    borderRadius: 1,
                    cursor: 'pointer',
                    border: currentImageIndex === index ? '2px solid #1976d2' : 'none',
                    opacity: currentImageIndex === index ? 1 : 0.7,
                    transition: 'all 0.2s ease-in-out',
                    '&:hover': {
                      opacity: 1,
                      transform: 'scale(1.05)',
                    }
                  }}
                  onClick={() => setCurrentImageIndex(index)}
                />
              ))}
            </Box>
          )}

          {/* Informações principais */}


            ...


            <Grid container spacing={2}>
              {property.numBedrooms !== null && (
                <Grid item xs={6} sm={3}>
                  <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                    <BedOutlined color="primary" />
                    <Typography variant="body2" color="text.secondary">Quartos</Typography>
                    <Typography variant="h6">{property.numBedrooms}</Typography>
                  </Box>
                </Grid>
              )}
              
              {property.numBathrooms !== null && (
                <Grid item xs={6} sm={3}>
                  <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                    <ShowerOutlined color="primary" />
                    <Typography variant="body2" color="text.secondary">Banheiros</Typography>
                    <Typography variant="h6">{property.numBathrooms}</Typography>
                  </Box>
                </Grid>
              )}
              
              {property.numParkingSpots !== null && (
                <Grid item xs={6} sm={3}>
                  <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                    <DirectionsCar color="primary" />
                    <Typography variant="body2" color="text.secondary">Vagas</Typography>
                    <Typography variant="h6">{property.numParkingSpots}</Typography>
                  </Box>
                </Grid>
              )}
              
              {property.area !== null && (
                <Grid item xs={6} sm={3}>
                  <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                    <SquareFoot color="primary" />
                    <Typography variant="body2" color="text.secondary">Área</Typography>
                    <Typography variant="h6">{property.area} m²</Typography>
                  </Box>
                </Grid>
              )}
            </Grid>
          </Paper>

          {/* Tabs com detalhes, leilão, documentos, e mapa */}
          <Paper sx={{ borderRadius: 2 }}>
            <Tabs 
              value={tabValue} 
              onChange={handleTabChange} 
              variant={isMobile ? "scrollable" : "fullWidth"}
              scrollButtons={isMobile ? "auto" : undefined}
              sx={{ borderBottom: 1, borderColor: 'divider' }}
            >
              <Tab label="Detalhes" {...a11yProps(0)} />
              <Tab label="Leilão" {...a11yProps(1)} />
              <Tab label="Documentos" {...a11yProps(2)} />
              <Tab label="Processos" {...a11yProps(3)} />
            </Tabs>

            <TabPanel value={tabValue} index={0}>
              <Typography variant="h6" gutterBottom>Descrição</Typography>
              <Typography variant="body1" paragraph>
                {property.description || 'Descrição não disponível'}
              </Typography>
              
              <Typography variant="h6" gutterBottom>Características</Typography>


            ...


            <TabPanel value={tabValue} index={2}>
              <Typography variant="h6" gutterBottom>Documentos</Typography>
              
              {property.documents && property.documents.length > 0 ? (
                <List>
                  {property.documents.map((doc: any, index: number) => (
                    <ListItem 
                      key={index}
                      button
                      component="a"
                      href={doc.url}
                      target="_blank"
                    >
                      <ListItemIcon>
                        <DocumentScanner />
                      </ListItemIcon>
                      <ListItemText 
                        primary={doc.name || `Documento ${index + 1}`} 
                        secondary={doc.description} 
                      />
                    </ListItem>
                  ))}
                </List>
              ) : (
                <Typography variant="body1">
                  Nenhum documento disponível
                </Typography>
              )}
              
              {property.propertyRegistrationUrl && (
                <Button 
                  variant="outlined" 
                  startIcon={<DocumentScanner />}
                  href={property.propertyRegistrationUrl}
                  target="_blank"
                  sx={{ mt: 2, mr: 2 }}
                >
                  Matrícula do imóvel
                </Button>
              )}
              
              {property.propertyReportUrl && (
                <Button 
                  variant="outlined" 
                  startIcon={<Description />}
                  href={property.propertyReportUrl}
                  target="_blank"
                  sx={{ mt: 2 }}
                >
                  Laudo do imóvel
                </Button>
              )}
            </TabPanel>

          
          ...

          
          {(property.address?.latitude && property.address?.longitude) || (property.latitude && property.longitude) && (
            <Paper sx={{ p: 3, mb: 3, borderRadius: 2 }}>
              <Typography variant="h6" gutterBottom>
                Localização
              </Typography>
              <MapContainer center={[property.address?.latitude || property.latitude, property.address?.longitude || property.longitude]} zoom={13} scrollWheelZoom={false} style={{ height: "200px" }}>
                <TileLayer
                  url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                />
                <Marker position={[property.address?.latitude || property.latitude, property.address?.longitude || property.longitude]}>
                  {/* <Popup>
                    A pretty CSS3 popup. <br /> Easily customizable.
                  </Popup> */}
                </Marker>
              </MapContainer>
            </Paper>
            )}
          
          {
            property.marketValue && (
              <Paper sx={{ p: 3, mb: 3, borderRadius: 2, bgcolor: 'grey.100' }}>
                <Typography variant="h6" gutterBottom color="text.secondary">
                  Valor de mercado estimado
                </Typography>
                
                <Typography variant="body2" color="text.secondary">
                  Esta funcionalidade estará disponível em breve. O valor será calculado com 
                  base na média de diversos portais imobiliários.
                </Typography>
              </Paper>
            )
          }
        </Grid>
      </Grid>
    </Box>
  );
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions