Increment value in react native - reactjs

I'm getting data from a payload which has a total number of likes on each post. On the user screen, there's an icon for the user to like a post and what i want to achieve is when the user taps on it, the value show be increased to plus 1 against that particular post
VIEW:
{
posts.map((item, i) => {
return (
<View key={i} style={styles.user}>
<Card>
<ListItem
titleStyle={{ color: '#36c', fontWeight:'500' }}
titleNumberOfLines={2}
hideChevron={false}
roundAvatar
title={item.headline}
avatar={{uri:'https://s3.amazonaws.com/uifaces/faces/twitter/brynn/128.jpg'}}
/>
<Text style={{marginBottom: 10, fontSize:16, color:'#4a4a4a', fontFamily:'HelveticaNeue-Light'}}>
{item.text}
</Text>
<TouchableOpacity style={styles.likeContainer}>
<Text style={{fontSize:14}}>{item.likesCount}{"\n"}</Text>
<Icon
onPress={()=>onLikePost(item)}
name='md-thumbs-up'
type='ionicon'
iconStyle={[(item.isLiked=== true) ? styles.likedColor : styles.unLikedColor]}
/>
</TouchableOpacity>
</Card>
</View>
);
})
}
CONTAINER:
state = {
posts : [],
id: '',
user: ''
}
componentDidMount = () => {
const { navigation } = this.props;
this.setState({
id : navigation.getParam('id'),
user: navigation.getParam('user')
}, ()=> this.getData())
}
getData = () => {
const api = create({
baseURL: 'https://url.com/api',
headers: {'Accept': 'application/json'}
});
api.get('/groups/'+`${this.state.groupID}`+'/posts').then((response) => {
let data = response.data.data
this.setState({ posts: data });
console.log(JSON.stringify(this.state.posts))
})
}
onLikePost = (item) => {
item.likeCount = item.likeCount+1
}

You are storing posts data in state variable so use setState to update it. Use map and check for each post, whenever id (unique property of each post) matches to id of the clicked item, increase its likesCount otherwise return the same data.
Write it like this:
onLikePost = (item) => {
this.setState(prevState => ({
posts: prevState.posts.map(el => el.id === item.id? {...el, likesCount: el.likesCount+1} : el)
}))
}
Update: Put the check before updating the count value and change the isLiked bool also.
onLikePost = (item) => {
this.setState(prevState => ({
posts: prevState.posts.map(el => {
if(el.id === item.id) {
return {
...el,
isLiked: !el.isLiked,
likesCount: !el.isLiked? el.likesCount+1 : el.likesCount-1,
}
}
return el;
})
}))
}
Note: I am assuming each post has a key id unique value, if it doesn't exist then use any other unique property of the each post.

If array sequence is not an issue, you can use item index and use setState to update it.
<Icon
onPress={()=>onLikePost(i)}
...
/>
...
onLikePost = (i) => {
let posts = this.state.posts;
posts[i].likesCount = !posts[i].isLiked ? posts[i].likesCount + 1 : posts[i].likesCount - 1;
posts[i].isLiked = !posts[i].isLiked;
this.setState({ posts: posts})
}

Related

Expected to find a valid target react dnd

I am experiencing this error with react dnd. The weird thing is that it depends on the key i specify to my react component. if i specify index, one part of my function fires this error, and when i specify item.id, another part doesnt fire. it doesnt make sense. please help.
When I specify the key to be index, the error fires when forum has no parent. however when i specify the key to be forum._id, the error fires when forum has parent. i dont know what to do, please help :)
Please visit this sandbox to reproduce:
https://codesandbox.io/s/proud-wind-hklt6
To reproduce:
Drag item 1ba on top of item 1, and then drag the item 1ba down the path.
Forum.jsx
const Forum = ({ forum, forums, setForums, move, find }) => {
const [{ isOver, canDrop }, drop] = useDrop({
accept: "forum",
hover: throttle((item, monitor) => {
if (item._id === forum._id) {
return;
}
if (!monitor.isOver({ shallow: true })) {
return;
}
if (!canDrop) return;
move(item, forum, forum.parent);
item = forum;
}, 200),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
});
const [, drag, preview] = useDrag({
item: {
_id: forum._id,
title: forum.title,
type: "forum",
children: forum.children,
parent: forum.parent,
},
isDragging(props, monitor) {
return props._id == monitor.getItem()._id;
},
});
const getChildren = async (forumId) => {
const _forums = await ForumService.getChildren(forumId, forums);
setForums(_forums);
};
return (
<Wrapper ref={drop}>
<ForumContainer ref={drag}>
{!!forum.childrenIds?.length && (
<div>
{!forum.isOpen ? (
<ForumChevron
className="fas fa-chevron-down"
onClick={() => getChildren(forum._id)}
></ForumChevron>
) : (
<ForumChevron
className="fas fa-chevron-up"
onClick={() =>
setForums(ForumService.resetChildren(forum._id, forums))
}
></ForumChevron>
)}
</div>
)}
<ForumTitle>{forum.title}</ForumTitle>
</ForumContainer>
{forum.children && !!forum.children.length && (
<ForumChildrenWrapper>
{forum.children.map((child, index) => (
<Forum
forum={child}
setForums={setForums}
forums={forums}
key={index}
move={move}
find={find}
/>
))}
</ForumChildrenWrapper>
)}
</Wrapper>
);
};
export default Forum;
ForumManager.jsx
if (!item.parent) {
console.log("here 1");
const dest = findItem(afterItem._id, _forums);
if (!dest.children) dest.children = [];
foundItem.parent = afterItem._id;
const idx = _forums.findIndex((f) => f._id === item._id);
_forums.splice(idx, 1);
if (dest.parent === foundItem._id) {
dest.parent = "";
if (foundItem.children.length) {
// When key is item.id, error shows up here
console.log("parent & has children");
for (let child of [...foundItem.children]) {
if (child._id === dest._id) {
child.children.splice(0, 0, {
...foundItem,
children: [],
childrenIds: [],
});
}
_forums.push(child);
}
} else {
console.log("no children");
dest.children.unshift({
...foundItem,
children: [],
childrenIds: [],
});
}
} else {
// When key is index, error shows up here
console.log("no parent");
console.log(dest);
dest.parent = "";
dest.children.splice(0, 0, {
...foundItem,
children: [],
childrenIds: [],
});
}
}
Try adding debounce to the hover handler (with trailing option). The components are updating too quickly by setting the state before DnD could catch up, and the target ID had changed by the time the user dropped the item.
Also - don't use index as the key, as it will change each time.
If you remove monitor.canDrop() inside collect function, then it works. Not sure, but this is one way.

Single selection item that saves the state (REACT-NATIVE)

I currently have 4 TouchableHighlights and my code looks as follows:
state:
this.state = {
selected: null,
selectedButton: ''
}
Touchable Highlight (they're all the same except for text)
<TouchableHighlight
onPress={this.selectedButton}
underlayColor='blue
style={[styles.box, { backgroundColor: (this.state.selected === true ? 'red' : 'blue') }]}>
<Text>1</Text>
</TouchableHighlight>
my functions:
selectedButton = () => {
this._handlePress('flag', '1')
this.setState({
selected: true,
});
};
_handlePress(flag, button) {
if (flag == 1) {
this.setState({ selected: true });
}
this.setState({ SelectedButton: button })
}
Current behaviour: Whenever I select one button, all become highlighted and cannot be unpressed.
Desired behaviour: I want only one button to be selected at the time with its state being saved somewhere.
Note: I could get the desired behaviour by creating 4 different functions that contain a different flag number, however, I'd like to get this done in the cleanest way possible.
Any guidance please?
Thank you
You can create an array of button texts, then use .map(), which provides the current index value, to iterate through them. For example:
render() {
const renderHighlight = (text, index) => (
<TouchableHighlight
onPress={() => {
if(this.state.selectedIndex === index) {
// "Unpress" functionality
this.setState({selectedIndex: undefined});
}
else {
this.setState({selectedIndex: index
}
})
style={this.state.selectedIndex === index ?
{backgroundColor: "#0F0" : {}}
>
<Text>{text}</Text>
</TouchableHighlight>;
);
const buttons = ["button 0", "button 1", "button 2"];
return buttons.map((text, i) => this.renderHighlight(text, i));
}

React fetching parameters from URL

I'm new to React and have been using it for couple weeks.
I was wondering how to make my cod work to dynamically get data from url and display it
searchSome() {
var serachValue=
"http://localhost:8005/api?act=search&term=" + this.state.valSearch+ "+";
fetch(serachValue)
.then(data => data.json())
.then(result => {
// console.log(this.state.library);
var object= JSON.stringify(this.state.library);
var stringify = JSON.parse(object);
});
}
The link result in data such as id, name etc.
I need to filter that as the user enters words in the search bar and display it.
display code
const filter = this.state.library.filter(book=> {
return (
book.name.indexOf(this.searchSome()) !== -1 ||
book.description
.indexOf(this.searchSome()) !== -1
);
});
Any help much appreciated!
Try below
searchSome(searchTerm) {
var serachValue=
"http://localhost:8005/api?act=search&term=" + searchTerm + "+";
fetch(serachValue)
.then(data => data.json())
.then(result => {
// Not sure what is your format, but you have to set this data to state
this.setState({ library: result });
});
}
render() {
return (
<View>
<TextInput
onChangeText={(text) => {
// Should debounce the call to `searchSome` to avoid multiple request
this.searchSome(text);
this.setState({ searchValue: text })
}}
text={this.state.searchValue}
/>
<FlatList
data={this.state.library}
/>
</View>
)
}

Interupt code and wait for user interaction in a loop - React

I am trying to implement an "add all" button in my react app. to do that, i pass this function to the onClick method of the button :
for (element in elements) {
await uploadfunction(element)
}
const uploadfunction = async (element) => {
if (valid) {
// await performUpload(element)
}
else if (duplicate) {
//show dialog to confirm upload - if confirmed await performUpload(element)
}
else {
// element not valid set state and show failed notification
}
}
const performUpload = async (element) => {
// actual upload
if(successful){
// set state
}else{
// element not successful set state and show failed notification
}
}
the uploadfunction can have three different behaviors :
Add the element to the database and update the state
Fail to add the element and update the state
Prompt the user with the React Dialog component to ask for confirmation to add duplicat element and update the state accordingly
My problem now is since i'm using a for loop and despite using Async/await , i can't seem to wait for user interaction in case of the confirmation.
The behavior i currently have :
The for loop move to the next element no matter what the result
The Dialog will show only for a second and disappear and doesn't wait for user interaction
Wanted behavior:
Wait for user interaction (discard/confirm) the Dialog to perform the next action in the loop.
How can i achieve that with React without Redux ?
Here is an example of a component that might work as an inspiration for you.
You might split it in different components.
class MyComponent extends Component {
state = {
items: [{
// set default values for all booleans. They will be updated when the upload button is clicked
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_1',
}, {
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_1',
}, {
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_2',
}],
performUpload: false,
};
onUploadButtonClick = () => {
this.setState(prevState => ({
...prevState,
items: prevState.items.map((item, index) => ({
isValid: validationFunction(),
isDuplicate: prevState.items.slice(0, index).some(i => i.data === item.data),
shouldUploadDuplicate: false,
data: item.data
})),
performUpload: true,
}), (nextState) => {
this.uploadToApi(nextState.items);
});
};
getPromptElement = () => {
const firstDuplicateItemToPrompt = this.getFirstDuplicateItemToPrompt();
const firstDuplicateItemIndexToPrompt = this.getFirstDuplicateItemIndexToPrompt();
return firstDuplicateItemToPrompt ? (
<MyPrompt
item={item}
index={firstDuplicateItemIndexToPrompt}
onAnswerSelect={this.onPromptAnswered}
/>
) : null;
};
getFirstDuplicateItemToPrompt = this.state.performUpload
&& !!this.state.items
.find(i => i.isDuplicate && !i.shouldUploadDuplicate);
getFirstDuplicateItemIndexToPrompt = this.state.performUpload
&& !!this.state.items
.findIndex(i => i.isDuplicate && !i.shouldUploadDuplicate);
onPromptAnswered = (accepted, item, index) => {
this.setState(prevState => ({
...prevState,
items: prevState.items
.map((i, key) => (index === key ? ({
...item,
shouldUploadDuplicate: accepted,
}) : item)),
performUpload: accepted, // if at last an item was rejected, then the upload won't be executed
}));
};
uploadToApi = (items) => {
if (!this.getFirstDuplicateItemToPrompt()) {
const itemsToUpload = items.filter(i => i.isValid);
uploadDataToApi(itemsToUpload);
}
};
render() {
const { items } = this.stat;
const itemElements = items.map((item, key) => (
<MyItem key={key} {...item} />
));
const promptElement = this.getPromptElement();
return (
<div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{itemElements}
</div>
<Button onClick={this.onUploadButtonClick}>Upload</Button>
{promptElement}
</div>
)
}
}

Change data in row, that has been rendered by cloneWithRows

I want to implement something like facebook 'like' functionality on some posts, but I haven't understood what should I do in order to mutate the data from datasource, and re-render a specific row, based on user pressing a button, basically a counter.
This is my code:
export default class PrivateFeedList extends Component {
constructor(props) {
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 != r2
})
this.state = {
datasource: ds.cloneWithRows( [] ),
refreshing: false
}
componentWillMount() {
this.fetchPrivateFeed()
}
_onRefresh() {
this.setState({refreshing: true});
this.fetchPrivateFeed()
.then(() => {
this.setState({refreshing: false});
});
}
render() {
return(
<View style= {styles.container}>
<ListView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
/>
}
dataSource={this.state.datasource}
renderRow={this.renderRow.bind(this)}
/>
</View>
)
}
fetchPrivateFeed() {
fetch('http://000.111.22.333:3000/', {
method: 'GET',
headers: {
'Authorization': 'id_token ' + token
}
})
.then((response) => response.json())
.then((feedItems) => {
console.log(feedItems)
this.setState({
datasource: this.state.datasource.cloneWithRows(feedItems)
});
})
}
renderRow(rowData) {
return(
<View style={styles.cardWrapper}>
<Text>{rowData.numberOfLikes}</Text>
{this.props.showLikeButton
? <Button onPress={()=> this.handleLikePress(rowData)}>
{this.hasUserLiked(rowData)
? <Text>LIKED</Text>
: <Text>LIKE!!</Text>
}
</Button>
: null
}
</View>
)
}
hasUserLiked(transaction) {
let result = false;
//userLiked is an array that contains all the usernames that have
liked the transaction. We cycle through this array, to check if the
present user is within the array, meaning that he has already liked
the transaction.
_.each(transaction.userLiked, function(userLikedItem) {
//has the user liked ? true or false
result = user.userName === userLikedItem;
})
return result;
}
handleLikePress(rowData) {
//Increase the numberOfLikes counter
//Push present user's username into the userLiked array.
//Re-render row.
}
I want to do the last 3 things I have written in the comments at the handleLikePress().
You do not mutate the dataSource instead you again populate the dataSource with new rows
handleLikePress(rowData) {
//Increase the numberOfLikes counter
//Push present user's username into the userLiked array.
this.setState({dataSource: this.state.dataSource.cloneWithRows(
newDataSource,
)});
}

Resources