I'm trying to return a list of 'cards', each containing three components.
The parent, ItemCard, returns this:
<>
<article>
<ItemName data={items} />
<ItemMap data={items} />
<FavouriteButton data={items} />
</article>
</>
)
The child components each have a .map to render each item from the array:
const ItemName = (props) => {
return (
<>
{props.data.map((item) =>
<p key={item.title}>{item.title}</p>
)}
</>
)
}
I would like to surround the three components returned from ItemCard so that each instance is its own article. Currently I get one big article containing a list of ItemNames, then ItemMaps, then Buttons. I'd like 25 individual articles, each with ItemName, ItemMap and Button.
My only idea was to use a forEach to do this, but I can't get it working.
Any tips much appreciated!
Hi Hanna and welcome to stack overflow. You almost got the right answer with the data.map function. You just need to put the article inside the return of the map iterator.
const App = (props) => {
return (
<>
{props.data.map((item) => (
<ItemCard item={item} />
))}
</>
);
};
const ItemCard = ({item}) => {
return (
<article>
<ItemName item={item} />
<ItemMap item={item} />
<FavouriteButton item={item} />
</article>
);
};
You'll need to change the implementation of ItemName, ItemMap and FavouriteButton to account for the change in props structure.
Thanks so much for your help!
I've refactored with your suggestions in mind but can't get it working properly. I've now got the parent as ItemsListContainer, and this component fetches the data, stores it in state and returns the ItemCard(s).
const dataUrl = "https://s3-eu-west-1.amazonaws.com/olio-staging-images/developer/test-articles-v4.json"
const ItemsListContainer = () => {
const [items, setItems] = useState([])
const fetchItemData = async () => {
const response = await fetch(dataUrl)
const jsonData = await response.json()
setItems(jsonData)
console.log(items)
}
useEffect(() => {
fetchItemData()
}, [])
if(items.length > 0) {
return (
<>
{items.map((item) => (
<ItemCard item={item} />
))}
</>
)
} else {
return (
<div>Loading items...</div>
)
}
}
ItemCard returns the three components:
const ItemCard = ({item}) => {
return (
<>
<article>
<ItemName item={item} />
<ItemMap item={item} />
<FavouriteButton item={item} />
</article>
</>
)
}
ItemName returns each:
const ItemName = (props) => {
console.log(props.item.title)
return (
<>
{props.item.map((item) => (
<p key={item.title}>{item.title}</p>
))}
</>
)
}
The error I get in the console is 'Uncaught TypeError: props.item.map is not a function at ItemName (ItemName.js:6)'.
The console.log works and prints what I want displayed in the p tag.
Related
I want to filter an array of objects and then map over them all and return the JSX but I can't get the syntax right.
...
const filterFiles = (): ReactElement => {
const filtered = cleanFiles?.filter(file => file.key === params.key);
return(
filtered?.map(
(file, index) =>
<div key={index}>
<FileView data={file} layout={view} />
</div>
);
);
};
return (
<>
<div>{filterFiles()}</div>
</>
);
};
export { FileBrowser };
Ive also tried
const filterFiles = (): ReactElement => {
const filtered = cleanFiles?.filter(file => file.key === params.key);
return(
<>
filtered?.map((file, index) =>
<div key={index}>
<FileView data={file} layout={view} />
</div>
);
</>
);
};
The issue with your second approach (which will return a single React element, as your types suggest) is that you are missing the {} around the code inside the fragment <></>
const filterFiles = (): ReactElement => {
const filtered = cleanFiles?.filter(file => file.key === params.key);
return <>
{filtered?.map((file, index) =>
<div key={index}>
<FileView data={file} layout={view} />
</div>
)}
</>
};
I would use the above option, but for the sake of completeness:
The issue with your first option, was probably that you are saying the function returns a ReactElement, but it was in fact returning: Array<ReactElement> | undefined due to the map() returning an array, and the optional chaining (?.) returning undefined if the property didn't exist.
sorry if the title doesn't make much sense.
I've been refactoring my code from this.state to useState, and I finally got things working except for the refs...
In my original code I was making individual axios calls and using this.state along with this refs code:
const refs = response.data.reduce((acc, value) => {
acc[value.id] = createRef();
return acc;
}, {});
but now I refactored my axios call to .all:
const getData = () => {
const getSunSigns = axios.get(sunSignAPI);
const getDecans = axios.get(decanAPI);
const getNums = axios.get(numbersAPI);
axios.all([getSunSigns, getDecans, getNums, refs]).then(
axios.spread((...allData) => {
const allSunSigns = allData[0].data;
const getAllDecans = allData[1].data;
const getAllNums = allData[2].data;
setSunSigns(allSunSigns);
setDecanSign(getAllDecans);
setNumerology(getAllNums);
})
);
};
useEffect(() => {
getData();
}, []);
so the response.data.reduce doesn't work cuz I'm not using 'response'.
I've tried several things but none worked.. unfortunately I deleted all the previous code but this is what I currently have, which works but obviously only takes one api:
const refs = sunSigns.reduce((acc, value) => {
acc[value.id] = createRef();
return acc;
}, {});
onClick = (id) => {
refs[id].current.scrollIntoView({
behavior: "smooth",
});
};
from the research I've done and the code I've tried I'm sure I'd have to map through the apis and then maybe use the reduce(???).. but I'm really not entirely sure how to go about it or how to rephrase my google search to get more accurate results.
what I'm trying to do specifically: on certain pages an extra nav bar appears with the symbol of a specific sign/number. the user can click on one and it'll scroll to that specific one. I'm going to have several pages with this kind of feature so I need to dynamically set refs for each api.
any help or guidance will be highly appreciated!!
edit**
the above codes are in my Main component and this is where I'm setting the refs:
return (
<div className='main'>
<div className='main__side-container'>
<SideBar />
<div className='main__card-container'>
<Card
sunSigns={sunSigns}
numerology={numerology}
decanSign={decanSign}
refs={refs}
/>
</div>
</div>
<div className='main__bottom-container'>
<BottomBar
sunSigns={sunSigns}
numerology={numerology}
onClick={onClick}
/>
</div>
</div>
);
}
then this is the card:
export default function Card({ sunSigns, decanSign, refs, numerology }) {
return (
<>
<div className='card'>
<Switch>
<Route path='/astrology/western/zodiac'
render={(routerProps) => <Zodiac refs={refs} sunSigns={sunSigns} />}
/>
<Route path='/numerology/pythagorean/numbers'
render={(routerProps) => <NumberPage refs={refs} numerology={numerology} />}
/>
</Switch>
</div>
</>
);
}
and then this is the Zodiac page:
export default function Zodiac({ sunSigns, refs }) {
return (
<>
<div className='zodiac__container'>
<TitleBar text='ZODIAC :' />
<div className='card-inset'>
<div className='container-scroll'>
<SunSignsList sunSigns={sunSigns} refs={refs} />
</div>
</div>
</div>
</>
);
}
and the SunSignsList component:
export default function SunSignsList({ sunSigns, refs }) {
return (
<>
<div className='sunsignsitemlist'>
<ul>
{sunSigns.map(sign => {
return (
<SunSigns
refs={refs}
key={sign.id}
id={sign.id}
sign={sign.sign}
/>
);
})}
</ul>
</div>
</>
);
}
and the SunSigns component:
export default function SunSigns({
id,
sign,
decans,
refs
}) {
return (
<li ref={refs[id]}>
<section className='sunsigns'>
<div className='sunsigns__container'>
<div className='sunsigns__header'>
<h3 className='sunsigns__title'>
{sign}
{decans}
</h3>
<h4 className='sunsigns__symbol'>{symbol}</h4>
</section>
</li>
);
}
the above code is where my ref code is currently accessing correctly. but the end goal is to use them throughout several pages and comps in the same manner.
You can create three different objects holding the ref data for each list or if the id is same you can generate a single object which holds all the list refs.
const generateAllRefsObj = (...args) => {
const genSingleListRefsObj = (acc, value) => {
acc[value.id] = createRef();
return acc;
}
return args.reduce((acc, arg) => ({ ...arg.reduce(genSingleListRefsObj, acc), ...acc }), {})
}
Usage
const allRefs = generateAllRefsObj(sunSigns,decanSign,numerology)
I want to delete it.But it won't be deleted.
If you click the delete text in the modal, it should be deleted, but it doesn't work.What should I do to delete it?
There's an error saying that onRemove is not a function.Please help me.
I want to delete it.But it won't be deleted.
If you click the delete text in the modal, it should be deleted, but it doesn't work.What should I do to delete it?
There's an error saying that onRemove is not a function.Please help me.
export default function Modal({ onRemove, id }) {
return (
<OptionModalWrap>
<ModalWrapper>
<TitleWrap>Edit</TitleWrap>
<TitleWrap>Duplicate</TitleWrap>
<DeleteText onClick={() => onRemove(id)}>Delete</DeleteText>
</ModalWrapper>
</OptionModalWrap>
);
}
export default function GroupList({ title, onRemove }) {
const [showModal, setShowModal] = useState(false);
const optionModal = () => {
setShowModal(prev => !prev);
};
return (
<AdGroups>
<Header>
<Container>
<ActiveWrap>
<ActiveIcon src={Active} />
<SubTitle>Active</SubTitle>
</ActiveWrap>
<Alaram>
<Bell src={bell} />
<Text className="alarmCount">10</Text>
</Alaram>
</Container>
<EtcIconWrap>
<EtcIcon src={plus} onClick={optionModal} />
{showModal && (
<OptionModal showModal={showModal} onRemove={onRemove} />
)}
</EtcIconWrap>
</Header>
<GroupTitle>{title}</GroupTitle>
</AdGroups>
);
}
export default function GroupPage() {
const [Groupdata, setGroupData] = useState([]);
const onRemove = item => {
setGroupData(Groupdata.filter(item => item.id !== item));
};
useEffect(() => {
fetch('./data/AdsGroup/AdsGroupList.json')
.then(res => res.json())
.then(res => setGroupData(res));
}, []);
return (
<GroupPages>
{Groupdata.map(item => {
return (
<GroupList key={item.id} title={item.title} onRemove={onRemove} />
);
})}
</GroupPages>
);
}
You have not passed the id in GroupList and then also to the OptionModal component.
So here is the revised code:
Group Page Component:
Passing the id to GroupList Component
const onRemove = id => {
setGroupData(Groupdata.filter(item => item.id !== id)); // you were item.id !== item which was wrong
};
<GroupList key={item.id} title={item.title} id={item.id} onRemove={onRemove} /> // passing the id
Group List Component:
Added id in the props and passed that to Modal Component. Also calling optionModal function to close the Modal after it deleted
export default function GroupList({ id, title, onRemove }) {
const [showModal, setShowModal] = useState(false);
const optionModal = () => {
setShowModal(prev => !prev);
};
return (
<AdGroups>
<Header>
<Container>
<ActiveWrap>
<ActiveIcon src={Active} />
<SubTitle>Active</SubTitle>
</ActiveWrap>
<Alaram>
<Bell src={bell} />
<Text className="alarmCount">10</Text>
</Alaram>
</Container>
<EtcIconWrap>
<EtcIcon src={plus} onClick={optionModal} />
{showModal && (
<OptionModal id={id} showModal={showModal} onRemove={onRemove;optionModal} />
)}
</EtcIconWrap>
</Header>
<GroupTitle>{title}</GroupTitle>
</AdGroups>
);
}
Modal Component: No change in this component
export default function Modal({ onRemove, id }) {
return (
<OptionModalWrap>
<ModalWrapper>
<TitleWrap>Edit</TitleWrap>
<TitleWrap>Duplicate</TitleWrap>
<DeleteText onClick={() => onRemove(id)}>Delete</DeleteText>
</ModalWrapper>
</OptionModalWrap>
);
}
Didn't your IDE complaint about this piece of code? both of the onRemove & filter functions' props are called item, it shouldn't be.
const onRemove = itemId => {
setGroupData(Groupdata.filter(item => item.id !== itemId));
};
My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})
I've looked at similar questions, but haven't found an answer that actually works. Here is my code for GridView:
const GridView = ({ products }) => {
return (
<Wrapper>
<div className="products-container">
{products.map((product) => {
return <Product key={product.id} {...product} />
})}
</div>
</Wrapper>
)
}
here is my code for ProductList:
const ProductList = () => {
const {filtered_products:products} = useFilterContext();
return <GridView products={products}>games list</GridView>
}
here is my code for FilteredContext:
const initialState = {
filtered_products:[],
all_products:[]
}
const FilterContext = React.createContext()
export const FilterProvider = ({ children }) => {
const {products} = useProductsContext();
const[state,dispatch] = useReducer(reducer, initialState)
useEffect(() => {
dispatch({type:LOAD_PRODUCTS, payload:products})
},[products])
return (
<FilterContext.Provider value={{...state}}>
{children}
</FilterContext.Provider>
)
}
Use conditional rendering, in case of products is null or undefined then you might get this error.
const GridView = ({ products }) => {
return (
<Wrapper>
<div className="products-container">
{products && products.map((product) => {
return <Product key={product.id} {...product} />
})}
</div>
</Wrapper>
)
}
also, make sure that products is an array.
add Elvis operator (? question mark is Elvis operator) after the products for map, this operator check if products isn't null, run the map code and if it is null won't run the map
const GridView = ({ products }) => {
return (
<Wrapper>
<div className="products-container">
{products?.map((product) => {
return <Product key={product.id} {...product} />
})}
</div>
</Wrapper>
)
}
refer to this link for more info about what is Elvis operator?
In the ProductList you should be passing filtered_products instead of products to the GridView
const ProductList = () => {
const {filtered_products} = useFilterContext();
return <GridView products={filtered_products}>games list</GridView>
}
Additionally make sure the product variable is defined before using map on it. You can do that using conditional rendering https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator
const GridView = ({ products }) => {
return (
products && <Wrapper>
<div className="products-container">
{products.map((product) => {
return <Product key={product.id} {...product} />
})}
</div>
</Wrapper>
)
}