Search code examples
javascriptreactjsrapidapi

Trying to change image dynamically for every bodyPart item but getting same image for every bodyPart


I am working on fitness app using react js where there is a list of bodyparts. I wanted to have a seperate image for each body part but getting same image for all

BodyPart.js

import React,{useState} from 'react'
import { Stack, Typography } from '@mui/material';

const BodyPart = ({ item, setBodyPart, bodyPart}) => {
    
    const image = require(`../assets/images/bodypart/${bodyPart}.jpg`);
   
    return (
        <Stack type="button"
            alignItems="center"
            justifyContent="center"
            className="bodyPart-card"
            sx={{
                borderTop: bodyPart === item ? '4px solid #ff2625' : '',
                backgroundColor: "#fff",
                borderBottomLeftRadius: '20px',
                width: '270px',
                height: '280px ',
                cursor: 'pointer',
                gap: '47px'
            }}
            onClick={()=>{
                setBodyPart(item);
                window.scrollTo({top:1800, left:100, behavior:'smooth'})
            
            }}
        >
            <img
                src= {image}
                alt='dumbell'
                style={{ width: '70px', height: '70px' }}
            />
            <Typography fontSize="24px" fontWeight="bold" color="#3A1212">
                {item}
            </Typography>
        </Stack>
    )
}

export default BodyPart

HorizontalScroll.js

import React, { useContext } from 'react'
import { Box, Typography } from '@mui/material';
import BodyPart from './BodyPart';
import { ScrollMenu, VisibilityContext } from 'react-horizontal-scrolling-menu';
import RightArrowIcon from '../assets/icons/right-arrow.png';
import LeftArrowIcon from '../assets/icons/left-arrow.png';
import ExerciseCard from './ExerciseCard';


const LeftArrow = () => {
    const { scrollPrev } = useContext(VisibilityContext);

    return (
        <Typography onClick={() => scrollPrev()} className="right-arrow">
            <img src={LeftArrowIcon} alt="right-arrow" />
        </Typography>
    );
};

const RightArrow = () => {
    const { scrollNext } = useContext(VisibilityContext);

    return (
        <Typography onClick={() => scrollNext()} className="left-arrow">
            <img src={RightArrowIcon} alt="right-arrow" />
        </Typography>
    );
};


const HorizontalScrollbar = ({ data, bodyParts, setBodyPart, bodyPart}) => {
    
    return (<ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
        {data.map((item) => (
            <Box
                key={item.id || item}
                itemId={item.id || item}
                title={item.id || item}
                m="0 40px"
            >
                {bodyParts ? <BodyPart item={item} 
                    setBodyPart={setBodyPart} bodyPart={bodyPart}/>: <ExerciseCard exercise={item}/> }
            </Box>
        ))}
    </ScrollMenu>
)
}

export default HorizontalScrollbar

SearchExercises.js

import React,{useEffect, useState} from 'react'
import {Box, Button, Stack,TextField, Typography} from '@mui/material';

import {fetchData, exercisesOptions} from '../utils/fetchData';
import HorizontalScrollbar from './HorizontalScrollbar';
import images from './images'

const SearchExercises = ({setExercises,bodyPart,setBodyPart}) => {

  const [search, setSearch] = useState('');
  const [bodyParts, setBodyParts] = useState([])

  useEffect(()=>{
    const fetchExercisesData = async () => {
      const bodyPartData = await fetchData('https://exercisedb.p.rapidapi.com/exercises/bodyPartList',
      exercisesOptions);
      setBodyParts(['all',...bodyPartData]);
   }

   fetchExercisesData();
  }, [])
  
  const handleSearch = async () => {
       if(search) {
        const exercisesData = await fetchData(
          'https://exercisedb.p.rapidapi.com/exercises',
          exercisesOptions
        );
       
        const searchedExercises = exercisesData.filter(
          (item) => item.name.toLowerCase().includes(search)
                 || item.target.toLowerCase().includes(search)
                 || item.equipment.toLowerCase().includes(search)
                 || item.bodyPart.toLowerCase().includes(search),
        );
        setSearch('');
        setExercises(searchedExercises);
       }
  }

  return (
    <Stack alignItems="center" mt="37px"
    justifyContent="center" p="20px">
        <Typography fontWeight={700}
        sx={{fontSize:{lg: '44px', xs:'30px'}}}
        mb="50px" textAlign="center">
            Awesome Exercises You <br/>
            Should know
        </Typography>
        <Box position="relative" mb="72px">
        <TextField 
        sx={{
            input: {fontWeight: '700',
            border :'none', 
            borderRadius:'4px'
        },
        width :{lg:'800px', xs:'350px'},
        backgroundColor : "#fff",
        borderRadius:'40px'
        }}
        height="76px"
        value={search}
        onChange={(e)=>{setSearch(e.target.value.toLowerCase())}}
        placeholder="Search Exercises"
        type="text" />
        <Button className="search-btn"
        sx={{
            bgcolor:'#FF2625',
            color:"#fff",
            textTransform :'none',
            width:{lg:'175px', xs:'80px'},
            fontSize :{lg:'20px', xs:'14px'},
            height:'56px',
            position : 'absolute',
            right:0
        }} onClick={handleSearch}>
          Search
        </Button>
        </Box>

        <Box sx={{position:'relative', width:'100%', p:'20px'}}>
             <HorizontalScrollbar data={bodyParts} bodyParts
              setBodyPart={setBodyPart} bodyPart={bodyPart} isBodyParts/>
        </Box>
    </Stack>
      
  )
}

export default SearchExercises

getting this as output

I wanted to have separate image for each body part. I tried to create array of all bodyParts data with images and name but that also didn't work in this scenarios.

This "https://exercisedb.p.rapidapi.com/exercises/bodyPartList" API returns below data

Added data return by the API

I have already render the required data as individual box here. I just wanted to apply image for each bodyPart separately currently it is applying same image


Solution

  • There is some bodyPart prop being passed to SearchExercises all the way through HorizontalScrollbar to BodyPart... which is the same value for all values the HorizontalScrollbar component is mapping.

    The HorizontalScrollbar component's data prop is the body parts data that should be mapped, not the passed bodyPart prop.

    SearchExercises

    const SearchExercises = ({ activeBodyPart, setExercises, setBodyPart }) => {
      ...
      const [bodyParts, setBodyParts] = useState([]);
    
      useEffect(() => {
        const fetchExercisesData = async () => {
          const bodyPartData = await fetchData(
            'https://exercisedb.p.rapidapi.com/exercises/bodyPartList',
            exercisesOptions
          );
          setBodyParts(['all', ...bodyPartData]);
       }
    
       fetchExercisesData();
      }, []);
    
      ...
    
      return (
        ...
        <HorizontalScrollbar
          data={bodyParts}
          bodyParts
          setBodyPart={setBodyPart}
          activeBodyPart={activeBodyPart}
        />
        ...
      );
    

    HorizontalScrollbar

    const HorizontalScrollbar = ({ activeBodyPart, data, bodyParts, setBodyPart }) => {
      return (
        <ScrollMenu LeftArrow={LeftArrow} RightArrow={RightArrow}>
          {data.map((bodyPart) => (
            <Box
              key={bodyPart}
              itemId={bodyPart}
              title={bodyPart}
              m="0 40px"
            >
              {bodyParts
                ? (
                  <BodyPart
                    activeBodyPart={activeBodyPart}
                    setBodyPart={setBodyPart}
                    bodyPart={bodyPart}
                  />
                ) : <ExerciseCard exercise={bodyPart} />
              }
            </Box>
          ))}
        </ScrollMenu>
      )
    }
    

    BodyPart

    const BodyPart = ({ activeBodyPart, setBodyPart, bodyPart}) => {
      const image = require(`../assets/images/bodypart/${bodyPart}.jpg`);
       
      return (
        <Stack
          type="button"
          alignItems="center"
          justifyContent="center"
          className="bodyPart-card"
          sx={{
            borderTop: bodyPart === activeBodyPart ? '4px solid #ff2625' : '',
            backgroundColor: "#fff",
            borderBottomLeftRadius: '20px',
            width: '270px',
            height: '280px ',
            cursor: 'pointer',
            gap: '47px'
          }}
          onClick={() => {
            setBodyPart(bodyPart);
            window.scrollTo({ top: 1800, left: 100, behavior: 'smooth' });
          }}
        >
          <img
            src={image}
            alt='dumbell'
            style={{ width: '70px', height: '70px' }}
          />
          <Typography fontSize="24px" fontWeight="bold" color="#3A1212">
            {bodyPart}
          </Typography>
        </Stack>
      );
    }