Create separate state for similar react children object in an array - reactjs

I have created a small social media app where there is a like button to like the post. When I press like on first button, second post's like changes color too which is state based.
Here is the code:
Likes.js
<LikedPanel
posts_id={posts_id}
person_id={person_id}
toggleLikePost={this.toggleLikePost}
liked={this.state.liked}
/>
const LikedPanel = ({posts_id, person_id, liked, toggleLikePost}) => (
<Panel onClick={() => toggleLikePost(posts_id, person_id)}>
{liked
?
<Fragment>
<LikeIcon nativeColor={indigo[500]}/>
<LikedText>Like</LikedText>
</Fragment>
:
<Fragment>
<LikeIcon/>
<LikeText>Like</LikeText>
</Fragment>
}
</Panel>
);
toggleLikePost = (posts_id, person_id) => {
// make api call and then setState
this.setState(liked: res.data.liked);
};
Update
Posts.js
const Posts = ({ feed, deletePost }) => (
<Card style={{ margin: '25px 0'}} square>
<ContentMedia post={feed.post} deletePost={deletePost} />
<Like liked={feed.post.likes.length > 0} posts_id={feed.post.id} person_id={feed.post.person_id} />
<Comments post_id={feed.post.id} comments={feed.post.comments} />
</Card>
);
Newsfeed.js
<div style={{ maxWidth: '500px', margin: '0 auto', paddingTop: '20px'}}>
{this.renderPosts(this.state.feed)}
</div>
renderPosts = (feeds) =>
feeds.map(feed => <Posts key={feed.post.id} feed={feed} deletePost={this.deletePost} />);
It loads fine when I refresh. But when I click like on any one post, all the other like icon gets toggled and vice versa. I see that it uses the same state for all elements. Is there a way to tell react to have its own self-contained state for every post's like panel?

Instead of storing a boolean value liked that it used by all the like buttons to determine if they should be toggled you can store an array of the liked posts. Then you can add liked post id to that array and detect in your LikedPanel if the current post id is in the liked array.
<LikedPanel
posts_id={posts_id}
person_id={person_id}
toggleLikePost={this.toggleLikePost}
liked={this.state.liked.indexOf(posts_id) !== -1 }
/>
toggleLikePost = (posts_id, person_id) => {
// make api call and then setState
this.setState(prevState => {
// add post id to liked array only if it's not added yet
if (prevState.liked.indexOf(posts_id) === -1) {
return {liked: [...prevState.liked, posts_id]};
}
});
};

Related

How do I access specific items in a mapped list through an onClick?

So I'm losing my mind over here trying to essentially just select the sibling element of things that are mapped.
I basically map over an object from an API, grab some stuff from it, and display some text and an ul with some li in it.
I ultimately want to click on h2 and have it alter the ul below it. In this case, I want the ul item below it to have a display of none until clicked.
I also tried returning the JSX of the ul in the onClick of the h2 tag, but it didn't add any elements to the page.
This wouldn't be so bad if I knew what anything was going to be, and it was a set object, but being an API, I could have 1 or 1000 ul to work with that all need the same functionality.
I was reading to do it by state, but I don't know how many items there are going to be, so how would I set state on an undefined number of items? Also, wouldn't having 100 states for this be crazy if that's what my API returned??
return myData.map((obj) => (
<div style={{ maxWidth: "500px", marginLeft: "25%" }} key={obj.name}>
<img
src={obj.image}
width="500px"
></img>
<h1>{obj.name}</h1>
<h2 id={'ingredients'+Math.Random} onClick={(e) => {console.log(e)}}>Ingredients</h2>
<ul id={'UL'+Math.random()}>
{obj.ingredients.map((index) => (
<ListItem key={"ingredients" + Math.random()} value={index} />
))}
</ul>
Side note - this code is ultimately for my practice only, so ignore bad naming etc...
You can achieve this by only using one state. Assuming your obj.name is unique since you are using it as a key value. You can use this name to determine wheter or not to display your ul, if the id is in the array then display the ul, if not do not display.
const YourComponent = () => {
// state to track all the ids of all the expanded ul's
const [expandedIds, setExpandedIds] = useState([]);
const toggleVisibility = (idToToggle) => {
// set new state
setExpandedIds((prevIds) => {
// if the id to toggle is in the array, if not add it
if (!expandedIds.includes(idToToggle)) return [...prevIds, idToToggle];
// else remove the id
return prevIds.filter((id) => id !== idToToggle);
});
};
return myData.map((obj) => (
<div style={{ maxWidth: "500px", marginLeft: "25%" }} key={obj.name}>
<img src={obj.image} width="500px"></img>
<h1>{obj.name}</h1>
<h2 onClick={() => toggleVisibility(obj.name)}>Ingredients</h2>
{expandedIds.includes(obj.name) && (
<ul id={"UL" + Math.random()}>
{obj.ingredients.map((index) => (
<ListItem key={"ingredients" + Math.random()} value={index} />
))}
</ul>
)}
</div>
));
};
I hope this helps you with your project.

How do I grab the value of an object within a functional component in React and set it within useState?

I am building a blog with React and Material UI. I have a dedicated page where I can view my current posts. I have added a delete icon that when clicked should delete the post from the local JSON file. I have tried using useState to store the value of the post id, and then insert it into the fetch API endpoint. However, I am unable to select the respective post id. I am also unsure if I am using the fetch request the right way.
const [postId, setPostId] = useState();
const deletePost = () => {
fetch(`http://localhost:8000/posts/${postId}`, {
method: 'DELETE'
})
}
return (
<>
{posts.map((post) => (
<Container maxWidth="lg" sx={{ border: 1, borderRadius: 3, mb: 5, p: 5 }}>
<IconButton onClick={deletePost} setPostId={post.id}>
<DeleteIcon />
</IconButton>
</Container>
))}
</>
);
I have tried using useState to store the value of the post id, and then insert it into the fetch API
You don't actually need to store it in the state. Just pass it directly to the deletePost function.
const deletePost = (postId) => {
...
}
<IconButton onClick={() => deletePost(post.id)}>

pass database data to another screen in react native

Now I wanted to implement a function that when I click on a list of data from mysql server on my react native app, it will move to another screen and pass the detail of the data into it here is the code that I have implement
const GetJobDetail=useCallback= (user,job, jobid, machinecode, startTime) =>{
navigation.navigate('Jobview', {
UserId : user,
Job : job,
JobId:jobid,
MachineCode : machinecode,
StartTime : startTime
},[]);
}
return (
<View style={{ flex: 1, padding: 24 }}>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<Button title={item.job} style={styles.rowViewContainer} onPress={()=>navigation.navigate(GetJobDetail(item.user,item.job,item.jobid,item.machinecode,item.startTime))}/>
)}
/>
)}
</View>
);
I have already successful fetch the data into my react native apps, the data look like
the data is only show the job of the user instead of showing all detail if I enter userid='1111' then it will only show the job for userid='1111'. but now i wish to implement that if I clicked the job in this list it will move to next screen and send all the details to that screen using route.params. I have tried using callback function but it gave me error "useCallback" is read-only.
I also founded some source from other site but it is in class component the code will look like this.
i am sure that this first is the function that pass the value to another screen
GetStudentIDFunction=(student_id,student_name, student_class, student_phone_number, student_email)=>{
this.props.navigation.navigate('Third', {
ID : student_id,
NAME : student_name,
CLASS : student_class,
PHONE_NUMBER : student_phone_number,
EMAIL : student_email
});
}
and this one is calling the function and pass the data to another screen
renderRow={ (rowData) => <Text style={styles.rowViewContainer}
onPress={this.GetStudentIDFunction.bind(
this, rowData.student_id,
rowData.student_name,
rowData.student_class,
rowData.student_phone_number,
rowData.student_email
)} >
{rowData.student_name}
</Text> }
pls help me to point out what is my mistake or error. Thanks
update:
after tried multiple time finally it works with this code. On the first page it get the data
const GetDetail=(id,user,job,jobid,machinecode,startTime)=>{
navigation.navigate('JobView',{Id:id,UserId:user,Job:job,JobId:jobid,MachineCode:machinecode,StartTime:startTime});
}
return (
<View style={{ flex: 1, padding: 24,backgroundColor:'cyan' }}>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<Text style={styles.rowViewContainer} onPress={()=>{GetDetail(item.id,item.user,item.job,item.jobid,item.machinecode,item.startTime)}}>{item.job}</Text>
)}
/>
)}
</View>
);
}
On this screen it pass the data from previous screen by using route.params&&route.params.variable\\variable=your own variable that carry the data from th eprevious screen
useEffect(()=>{
setCode(route.params && route.params.MachineCode)
setUserid(route.params && route.params.UserId)
setJob(route.params && route.params.Job)
setJid(route.params && route.params.JobId)
setStarttime(route.params && route.params.StartTime)
setId(route.params && route.params.Id)
},[]
)
Hope this will help you guys when faced the same problem :)
Store your data in some useState after fetching. You can send data to another screen/component in two ways:
You can pass data as props or use a callBack if those screens/components are in parent-child relationship.
Parent to child: Pass as props
<FlatList
data={customerData}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
keyExtractor={(item, index) => index}
renderItem={({item, index}) => (
<KYC_ListCell
itemdata={customerData[index]}
onItemClick={() => {
props.updateCustomerInformation(customerData[index]);
navigation.navigate('Customer360Info');
}}
/>
Using a call back to pass data from child to parent
//Parent
<HeaderView
username={'HelpDesk'}
showHeaderWithSearch
placeholderText={'Customer ID'}
showRefreshIcon
onMenuClick={() => {
setModalVisible(true);
}}
onRefreshClicked={() => {
getComplaintsListOnPageLoad();
}}
onSearchClicked={text => {
setSearch(text);
getSearchedItem(text);
}}
/>
//child
<Search
value={search}
placeholderText={props.placeholderText}
onChangeText={text => setSearch(text)}
onClearText={() => setSearch('')}
onSearchPressed={() => props.onSearchClicked(search)}
/>
You can pass data while navigating as route param

Setting Active State on mapped component

I have a mapped component which iterates through API data. It passes props to each one and therefore each card looks different. See example below.
https://gyazo.com/39b8bdc4842e5b45a8ccc3f7ef3490b0
With the following, I would like to achieve two goals:
When the component is selected, it uses state to STAY SELECTED, and changes the colour as such to lets say blue for that selected component.
I hope this makes sense. How do I index a list as such and ensure the colour and state remains active based on this selection?
See below.
The level above, I map the following cards using these props.
{
jobs.length > 0 &&
jobs.map(
(job) =>
<JobCard key={job.id} job={job}
/>)
}
I am then using the following code for my components:
const JobCard = ({ job }) => {
const responseAdjusted = job.category.label
const responseArray = responseAdjusted.split(" ")[0]
return (
<CardContainer>
<CardPrimary>
<CardHeader>
<CardHeaderTopRow>
<Typography variant = "cardheader1">
{job.title}
</Typography>
<HeartDiv>
<IconButton color={open ? "error" : "buttoncol"} sx={{ boxShadow: 3}} fontSize ="2px" size="small" fontSize="inherit">
<FavoriteIcon fontSize="inherit"
onClick={()=> setOpen(prevOpen => !prevOpen)}/>
</IconButton>
</HeartDiv>
</CardHeaderTopRow>
<Typography variant = "subtitle4" color="text.secondary">
{job.company.display_name}
</Typography>
</CardHeader>
<CardSecondary>
</CardSecondary>
</CardPrimary>
</CardContainer>
)
}
export default JobCard
My suggestion is to use a state in the wrapping component that keeps track of the current open JobCard.
const [openCard, setOpenCard] = useState()
and then pass this down to job card together with a function to update.
jobs.map(
(job) =>
<JobCard
key={job.id}
job={job}
isSelected={openCard === job.id}
onSelectCard={() => setOpenCard(job.Id)}
/>)
So now you can format your JobCard differently depending on isSelected, and run onSelectCard when the card is pressed.

Accessing a component state from a sibling button

I'm building a page that will render a dynamic number of expandable rows based on data from a query.
Each expandable row contains a grid as well as a button which should add a new row to said grid.
The button needs to access and update the state of the grid.
My problem is that I don't see any way to do this from the onClick handler of a button.
Additionally, you'll see the ExpandableRow component is cloning the children (button and grid) defined in SomePage, which further complicates my issue.
Can anyone suggest a workaround that might help me accomplish my goal?
const SomePage = (props) => {
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { /* Need to access MyGrid state */ }} />
Add Row
</button>
<MyGrid>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
const ExpandableRowsComponent = (props) => {
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
return (
<>
{data.map((dataItem) => (
<ExpandableRow id={dataItem.id} />
))}
</>
);
};
const ExpandableRow = (props) => {
const [expanded, setExpanded] = useState(false);
return (
<div className="row-item">
<div className="row-item-header">
<img
className="collapse-icon"
onClick={() => setExpanded(!expanded)}
/>
</div>
{expanded && (
<div className="row-item-content">
{React.Children.map(props.children, (child => cloneElement(child, { id: props.id })))}
</div>
)}
</div>
);
};
There are two main ways to achieve this
Hoist the state to common ancestors
Using ref (sibling communication based on this tweet)
const SomePage = (props) => {
const ref = useRef({})
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { console.log(ref.current.state) }} />
Add Row
</button>
<MyGrid ref={ref}>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
Steps required for seconds step if you want to not only access state but also update state
You must define a forwardRef component
Update ref in useEffect or pass your API object via useImerativeHandle
You can also use or get inspired by react-aptor.
⭐ If you are only concerned about the UI part (the placement of button element)
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
(Mentioned point by #Sanira Nimantha)

Resources