im working on a feedback app,and im updating an state by filtering it, so i can click on a tag and display the feedbacks filtered by tags, im passing the values through context
const handleFiltering = (category) => {
const filteredData = cardInfo?.filter((item: feedbackTypes) => item.categories === category);
setCardInfo(filteredData);
};
return <DataContext.Provider value={{ cardInfo }}>{children}</DataContext.Provider>;
and these are, the card component (where im rendering the feedbacks)
const NewTable: FC = (): JSX.Element => {
const { cardInfo, setCardInfo } = useContext(DataContext);
return (
<>
<Stack spacing={6} mt={4} ml="38.2vw">
{cardInfo ? (
cardInfo?.map((values: feedbackTypes) => (
<div key={values._id}>
<Link
to={{
pathname: '/editFeedback',
state: {
id: values._id,
title: values.title,
feedback: values.text,
category: values.categories
}
}}
>
<Card
title={values.title}
feedback={values.text}
cardId={values._id}
category={values.categories} />
</Link>
</div>
))
) : (
<EmptyCard />
)}
</Stack>
</>
);
};
and the components where the tags are rendered
const NavBar = () => {
const { cardInfo, handleFiltering, permanentData } = useContext(DataContext);
return (
<>
<Flex direction="row">
<Box borderRadius="10px" className={styles.leftCard}>
<Heading fontSize="xl" color="white" mt={14} ml={6}>
Frontend Mentor
</Heading>
<Text mt={2} ml={6}>
Feedback Board
</Text>
</Box>
<Box borderRadius="10px" className={styles.leftCardSort}>
<Heading fontSize="xl" color="white" mt={10} ml={6}></Heading>
<Text ml={4} className={styles.tagSort}>
{permanentData?.map((values, idx) => (
<div key={idx}>
<Tag
className={selectedTag?.includes(values as never) ? styles.tagSelected : styles.tagUnselected}
onClick={(e) => {
handleFiltering(values);
handleBtnStyle(values as never);
}}
>
{values}
</Tag>
</div>
))}
</Text>
</Box>
whenever i click on each tag, the feedbacks that contain those tags are filtered correctly, but when i click on another tag after selecting one, it does not render the other feedbacks until i click on 'All' (and thats because im fetching all the data again), i dont know how could i re render other feedbacks after i already clicked on one, because the feedback component needs to be filtered just like the tag is filtering them by categories, i dont know what logic to follow from here on
first tag selected
second tag selected after the first
Related
I see this warning in console. But where is my mistake? I have Warning: Each child in a list should have a unique "key" prop. but I put the KEY PROP in all components what I render with map function.
One warning in this map function:
{data && data.map(item => (
<Card key={item.id}>
<CardTitle>{item.title}</CardTitle>
<CardPrice>{item.price}</CardPrice>
<CardDescription>{item.description}</CardDescription>
<CardItems>
{item.benefits.map(benefitsItem => (
<CardItem>
<CheckCircleIcon />
<CardItemText>{benefitsItem}</CardItemText>
</CardItem>
))}
</CardItems>
<StyledPopup
trigger={<CardButton className="BlackButton">Замовити сайт</CardButton>}
modal
nested
lockScroll
>
{close => (
<div className='modal'>
<button className="closeModal" onClick={close}>×</button>
<Feedback
isPlan={{
name: item.title,
description: item.description,
price: item.price
}}
/>
</div>
)}
</StyledPopup>
</Card>
))}
And Other warning in this component:
<Navigation>
{cards && cards.map(item => renderLinks(item))}
</Navigation>
<CardsWrapper>
{cards && cards.map(item => renderCard(item))}
</CardsWrapper>
There is the render functions.
const renderCard = (cardData) => {
if(cardData.cardId === activeCard){
return(
<Card key={cardData.cardId}>
<ImageWrapper>
<Image src={cardData.cardImage} />
</ImageWrapper>
<CardInfoWrapper>
<CardTitle>{cardData.cardTitle}</CardTitle>
<CardDescription>
{cardData.cardDescription}
</CardDescription>
<Pluses>
{cardData.cardOpportunities && cardData.cardOpportunities.map(opportunity => (
<Plus>
<Ok><CheckCircleIcon /></Ok>
{opportunity}
</Plus>
))}
</Pluses>
</CardInfoWrapper>
</Card>
)
}
}
And finnely
const renderLinks = (cardData) => {
if(cardData.cardId === activeCard) {
return(
<div key={cardData.cardId}>
<NavigationItem
className="navigationLink"
width={cardData.cardLinkWidth}
active
>
{cardData.cardLink}
</NavigationItem>
</div>
)
} else {
return(
<div key={cardData.cardId}>
<NavigationItem
className="navigationLink"
width={cardData.cardLinkWidth}
onClick={() => linkClickHandler(cardData.cardId)}
>{cardData.cardLink}</NavigationItem>
</div>
)
}
}
Looks like there's a missing key prop on line 10 at
{item.benefits.map(benefitsItem => (
<CardItem>
CardItem needs a key prop.
Each CardItem within the Card also needs it's own key as there are multiple CardItem components mapped from the benefits array. i.e <CardItem key={benefitsItem.id}/>
I have some menu items in a component and some styling added to the selected menu item. When I refresh the page, I want the selected menu to persist and not the initial state.
import ClaimData from "./Data";
const Services = () => {
const [tabIndex, setTabIndex] = useState(1);
const [selected, setSelected] = useState(1);
return (
<section >
<h3>
Menu
</h3>
<div>
{ClaimData.map((item, index) => {
return (
<div
key={index}
style={
selected === item.tabNum
? {
borderBottom: "3px solid green",
backgroundColor: "#E8E8E8",
}
: null
}
onClick={() => setSelected(item.tabNum)}
>
<p
onClick={() => setTabIndex(item.tabNum)}
style={{ color: item.color }}
>
<item.icon />
<span>{item.title}</span>
</p>
</div>
);
})}
</div>
<div>
{tabIndex === 1 && <MenuOneComponent />}
{tabIndex === 2 && <MenuTwoComponent />}
{tabIndex === 3 && <MenuThreeComponent />}
</div>
</section>
);
};
export default Services;
I have removed some codes for brevity. I would appreciate any help
To presist state on refresh you need to store the state outside of react.
Easiest would propbably be to use localStorage or sessionStorage. But it is of course possible to save it in a database/redis.
One way is to use url to determine which menu to highlight.
I've created a component to create follow and unfollow buttons and now I want to use this component in other components (like Suggestions).
In the suggestions component I want to show only the button that its value is equal to the user.id, but I am only able to get the 5 buttons from the original component.
Is there a way to select only the button that is equal to the user.id?
This is the component that creates the buttons:
render() {
const { users, followingUsers } = this.state
const userId = this.props.user[0].id
return(
<div>
{users.map((user, index) => {
if(userId !== user.id) {
if(followingUsers.includes(user.user_name)) {
return(
<Button key={index} value={user.id} onClick={this.onUnfollow}>Unfollow</Button>
)
} else {
return(
<Button key={index} value={user.id} onClick={this.onFollow}>Follow</Button>
)
}
}
})}
</div>
)
}
}
export default withUser(Unfollowfollow);
Here is the suggestions component:
render() {
const { users } = this.state
const userId = this.props.user[0].id
return (
<div>
<ul>
{users.map((user, index) => {
if(user.id !== userId) {
return (
<Card className="users" key= {index}>
<CardBody>
<CardImg className="picfollowers" top width="9%" src={user.image} />
<CardTitle onClick={() => this.handleClick(user.id)}>{user.user_name}</CardTitle>
<Unfollowfollow />
</CardBody>
</Card>
)}
})}
</ul>
</div>
)
}
}
export default withUser(Suggestions);
I have a navbar that uses eventKeys to switch between the buttons
const CustomNav = ({ active, onSelect, ...props }) => {
return (
<Nav
{...props}
activeKey={active}
onSelect={onSelect}
style={{ marginBottom: "15px" }}>
<Nav.Item eventKey='all'>All</Nav.Item>
<Nav.Item eventKey='movies'>Movies</Nav.Item>
<Nav.Item eventKey='shows'>Shows</Nav.Item>
<Nav.Item eventKey='people'>People</Nav.Item>
</Nav>
);
};
I did this:
const Content = () => {
if (this.state.active === "all") {
return (
<div>
{trending.results &&
trending.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
} else if (this.state.active === "movies") {
return (
<div>
{trendingMovies.results &&
trendingMovies.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
}
};
Called it here:
return (
<div className='Home'>
<FlexboxGrid justify='center'>
<Panel bordered header='Trending today!'>
<CustomNav
className='customNav'
appearance='subtle'
active={active}
onSelect={this.handleType}
/>
<Content />
<Pagination
{...this.state}
style={{ marginTop: "15px" }}
maxButtons={5}
size='sm'
pages={totalPages}
activePage={this.state.activePage}
onSelect={this.handlePage}
/>
</Panel>
</FlexboxGrid>
</div>
);
}
}
To display the correct data for each tab, but when I'm on the movies tab it shows all the data from the first "all" tab + data on the "movies" tab. I wanna show each data individually corresponding to the correct tab which is controlled by "this.state.active". Tried a switch statement too and that did not work
you are using the arrow syntax
const Content = () => { ... }
and also using this.state variable in your code !!!
if you want to use this.state, then you want to use the class syntax, like
class Content extends React.Component { ... }
but don't mix the two styles.
what you are probably wanting to do is to send the active variable as a prop
try:
const Content = ({active}) => {
if (active === 'all') {
return (...)
} else if (active === 'movies') {
return (...)
}
return null
}
and where you are calling the component you send the active value in as a prop
<Content active={active} />
Note also that you are using the variables trending and trendingMovies and it is unclear where those come from, you may need to send those via props also.
Now you can also leave the if..else logic outside of your Content component like so
const Content = ({myTrending}) => {
return (
<div>
{myTrending.results &&
myTrending.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
}
and then where you call that component you have
<Content
myTrending={active === 'all' ? trending : trendingMovies}
/>
You need to pass active and other variables as props to the Content component, since it doesn't access them otherwise:
const Content = ({active, trending=[], trendingMovies=[]}) => {
if (active === "all") {
return (
<div>
{trending.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
} else if (active === "movies") {
return (
<div>
{trendingMovies.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
}
};
return (
<div className='Home'>
<FlexboxGrid justify='center'>
<Panel bordered header='Trending today!'>
<CustomNav
className='customNav'
appearance='subtle'
active={active}
onSelect={this.handleType}
/>
<Content active={this.state.active} trending={this.state.trending} trendingMovies={this.state.trendingMovies} />
<Pagination
{...this.state}
style={{ marginTop: "15px" }}
maxButtons={5}
size='sm'
pages={totalPages}
activePage={this.state.activePage}
onSelect={this.handlePage}
/>
</Panel>
</FlexboxGrid>
</div>
);
}
}
I am trying to create a post feed like the one instagram has (on the main page).
I'm using Infinite-loader for fetching, Window-scroller for using the window as the scroll, auto-sizer for sizing the list how i want and CellMeasurer for measuring the 'post component' once after the image has been loaded.
Here is code for the list component:
class PostsPartial extends React.PureComponent<IProps>{
state: IPostsPartialState = { posts: [], hasMorePosts: true }
private cache: CellMeasurerCache;
private get rowCount(): number {
return this.state.hasMorePosts ? this.state.posts.length + 1 : this.state.posts.length;
}
constructor(props: IProps) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 1000
});
this.renderRow = this.renderRow.bind(this);
}
private fetchPosts = ({ startIndex, stopIndex }: { startIndex: number, stopIndex: number }) => {
return getNewPostsChunk(startIndex, stopIndex - startIndex, this.props.token).then((res: IPostsChunkResponse) => {
if (res.success) {
if (res.posts.length === 0) {
// no more posts
this.setState({ hasMorePosts: false })
}
else {
let newPosts = [...this.state.posts, ...res.posts];
this.setState({ posts: newPosts })
}
}
else {
// internal error
}
})
};
private renderRow({ index, key, parent, style }: any) {
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure, registerChild }: any) => (
<div className={styles.paddingContainer} ref={registerChild} style={style}>
<Post
isLoaded={this.isRowLoaded({index})}
measure={measure}
post={this.state.posts[index]}
/>
</div>
)}
</CellMeasurer>
);
}
private isRowLoaded = ({ index }: { index: number }) => {
return !!this.state.posts[index];
};
public render() {
return (
<div className={styles.mainContainer}>
<InfiniteLoader
isRowLoaded={this.isRowLoaded}
loadMoreRows={this.fetchPosts}
rowCount={this.rowCount}
>
{({ onRowsRendered, registerChild }: InfiniteLoaderChildProps) => (
<WindowScroller>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
<AutoSizer disableHeight>
{
({ width }: any) => (
<List
ref={registerChild}
onRowsRendered={onRowsRendered}
autoHeight
width={width}
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow}
rowCount={this.rowCount}
overscanRowCount={10}
/>
)
}
</AutoSizer>
)}
</WindowScroller>
)}
</InfiniteLoader>
</div>
);
}
and here is code for the post component:
const Post:React.FC<IProps> = (props:IProps) => {
if(props.post && props.isLoaded)
return (
<div className={styles.container}>
<Segment className={styles.profileSegmentInternal} attached='top'>
<Image className={styles.verySmallImg} circular size='tiny' src={`${settings.BASE_URL}/feed/photo/user/${props.post.creator}`}></Image>
<Link to={`/profile/${props.post.creator}`}>
<Header size='small' className={styles.headerName} as='span'>{props.post.creator}</Header>
</Link>
</Segment>
<div className={styles.imageContainer}>
<Image onLoad={props.measure} src={`${settings.BASE_URL}/feed/photo/post/${props.post._id}`} className={styles.image}></Image>
</div>
<Segment className={styles.bottomSegment} attached='bottom'>
<>
<Menu className={styles.postMenu}>
<Item className='left'>
<Icon className={styles.iconBtn} size='big' name='heart outline'></Icon>
<Icon className={styles.iconBtn} size='big' name='comment outline'></Icon>
<Icon className={styles.iconBtn} size='big' name='paper plane outline'></Icon>
</Item>
<Item className='right'>
<Icon className={styles.iconBtn} size='big' name='bookmark outline'></Icon>
</Item>
</Menu>
</>
<Header className={styles.likes} size='tiny'>{props.post.likesCount} likes</Header>
<Header className={styles.description} size='tiny'>
<Header size='tiny' className={styles.commentUsername} as='span'>{props.post.creator}</Header>
<Header className={styles.commentText} as='span' size='tiny'> {props.post.description}</Header>
</Header>
<Link to='#'>
<Header className={styles.viewAllComments} size='tiny' disabled>View all comments</Header>
</Link>
{
//backend will return the first 3-4 messeges only
// props.post.messeges.map((messege,index) => (
// ))
}
<Form className={styles.commentForm}>
<Form.Field className={styles.commentField}>
<Form.Input
className={styles.commentInput}
placeholder='Adding comment ...'
>
</Form.Input>
<Button className={styles.commentSubmit} size='medium' primary>Comment</Button>
</Form.Field>
</Form>
</Segment>
</div>
)
else
return (
<p>loading</p>
)
Even if I remove everything from the post component and leave only the image, it still won't run with more then 45-50fps sometimes going under 40fps too.
Can I optimize my approach in any way or am I doing something wrong?
Should I provide anything else that might be helpful?
Thank you in advance!
So I fixed my problem by resizing the image when uploading it (in the backend using sharp).
This way the fetching is faster witch makes the (just in time) measure of a post component way faster as less data needs to be loaded on mount, and html+css doesn't have to resize the high image into a smaller container.
Sounds silly, but I didn't think of this being the issue and instead focused on my infinite scrolling implementation :D
Ya live and ya learn
EDIT:
I forgot to mention, a small change I did when setting the image src.
Instead of making an express route that retrieves the src, I can just use it while receiving the post info. The file source won't be printed with console.log or whatever, but it is there and can be used like so:
<Image className={styles.image} onLoad={props.measure} src={`data:${props.post.source.contentType};base64,${Buffer.from(props.post.source.data).toString('base64')}`} />