View background won't change after clicking it and using ternary - reactjs

So I am displaying 5 stars. They are all gray on default when they are not selected. When I click on the star, I want the background color to change of the View but it is not changing after clicking it. Also, if I click star 3, I want the stars before star 3 to also change the background color. But nothing is working.
I used console.log to view the changed list when handleStarClicked runs and it saves the changes but not sure what is going on. Would really appreciate some help, please
const stars = [{ id: 0, selected: false }, { id: 1, selected: false }, { id: 2, selected: false }, { id: 3, selected: false }, { id: 4, selected: false }];
const [holdStars, setHoldStars] = useState(stars);
const handleStarClicked = (id) => {
for (let i = 0 ; i < holdStars.length; i++) {
if (i <= id) {
holdStars[i].selected = true;
} else {
holdStars[i].selected = false;
}
}
setHoldStars(holdStars);
}
return (
<FlatList
data={holdStars}
horizontal
scrollEnabled={false}
renderItem={({ item }) =>
<Pressable onPress={() => handleStarClicked(item.id)} className="ml-1 p-1 rounded-xl items-center justify-center" style={{ backgroundColor: item.selected ? "#6ECCAF" : "#CCCCCC" }}>
<Icon name="star" type="AntDesign" size={32} color="white" />
</Pressable>
}
/>
)

The issue here is that you are modifying the holdStars array directly in the handleStarClicked function and that is causing the issue.
Try this :
const handleStarClicked = (id) => {
const newHoldStars = [...holdStars]; // create a new copy of the holdStars array
for (let i = 0 ; i < newHoldStars.length; i++) {
if (i <= id) {
newHoldStars[i].selected = true;
} else {
newHoldStars[i].selected = false;
}
}
setHoldStars(newHoldStars);
}

Related

How can I add CSS class when I click on React?

My development environment: react, recoil, javascript, styled-component.
When I click on the technology stack for each field classified as a tab, I want to change the CSS only when it is clicked and included in the selected Tags.
const [selectedTags, setSelectedTags] = useRecoilState(selectedTagsState);
const tabContArr = [
{
tabTitle: 'FE',
tabCont: ['JavaScript', 'TypeScript', 'React', 'Vue', 'Svelte', 'Nextjs'],
},
{
tabTitle: 'BE',
tabCont: [
'Java',
'Spring',
'Nodejs',
'Nextjs',
'Go',
'Kotlin',
'Express',
'MySQL',
'MongoDB',
'Python',
'Django',
'php',
'GraphQL',
'Firebase',
],
},
{
tabTitle: 'etc',
tabCont: [],
},
];
const onTagClick = (e) => {
const newSelectedTags = [...selectedTags];
const filterTarget = newSelectedTags.filter(
(el) => el.tagName === e.target.textContent,
);
if (filterTarget.length === 0 && newSelectedTags.length < 5) {
let tagObj = {};
tagObj.tagName = e.target.textContent;
newSelectedTags.push(tagObj);
setSelectedTags(newSelectedTags);
} else if (
filterTarget.length !== 0 ||
selectedTags.length >= 5
) {
{
(''); // nothing change
}
}
};
// FE, BE Choose
tabContArr[activeIdx].tabCont.map((skillTag, idx) => {
return (
<div
key={idx}
className={
// HERE!!!
selectedTags.includes(skillTag)
? 'skill-tag skill-selected-tag'
: 'skill-tag'
}
onClick={onTagClick}
aria-hidden="true"
>
{skillTag}
</div>
);
})
I tried to write the code like the one marked "HERE!!!" but it didn't work when I did this. Please help me on how to change the CSS (Class) only for the names in the selected Tag!!
className={
// HERE!!!
selectedTags.includes(skillTag)
? 'skill-tag skill-selected-tag'
: 'skill-tag'
}
In tabContArr[activeIdx].tabCont.map((skillTag, idx) => {, skillTag is a string, while selectedTags is an array of objects, like this: [{tagName: 'Java'}, {tagName: 'Spring'}, ...].
You are determining whether the array of objects contains a string, which will never occur. console.log(selectedTags, skillTag) will make it easier to understand.

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.

how to hit enter without creating an enter space (paragraph space)?

When you hit enter a new filed will be created but the problem is the focus and the focus will move to the newly created filed, but the problem is : its leave an enter space in the previous field
`
import React, { useState } from "react";
import "./styles.css";
export default function App() {
let elements = [
{ id: 0, text: "first", autoFocus: true },
{ id: 1, text: "second", autoFocus: true },
{ id: 2, text: "third", autoFocus: true }
];
const [state, setstate] = useState(elements);
function handChange(e) {
if (e.keyCode == 13) {
const index = state.findIndex((item) => item.id == e.target.id);
//i'm using autoFocus to move the focus (I-beam pointer) to the nex field.
//but i still get errors with it
Object.assign(state, (state[index].autoFocus = false));
setstate((pre) => {
return [
...pre.slice(0, index + 1),
{ id: 3, text: "xxx", autoFocus: true },
...pre.slice(index + 1, state.length)
];
});
setTimeout(() => {
document.getElementById(index + 1).focus();
}, 0);
}
}
return (
<div className="App">
{state.map((e) => (
<div contentEditable="true" id={e.id} onKeyUp={handChange}>
{e.text}
</div>
))}
</div>
);
}
on codesandbox
here is a quick hack for it, remove any Enter char from the text
if (e.keyCode == 13) {
const index = state.findIndex((item) => item.id == e.target.id);
e.target.innerText = e.target.innerText.split('\n').join('')
// the rest of your code here
}
Add preventDefault will prevent the default event occurring, in this case, the default event of the enter key, which is creating a new line, will be stopped.
if (e.keyCode == 13) {
e.preventDefault(); //This should be at the top of the process.
//....
}

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));
}

Increment value in react native

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})
}

Resources