State is not working as it supposed to react-native - reactjs

I'm mapping array like this:
{activeNewsCategories.map((i) => (
<Tags
key={i}
tagName={i}
getNewsByCategory={getNewsByCategory}
/>
))}
This is inside of Tags component:
const Tags = ({tagName, getNewsByCategory}) => {
const [selectedCategory, chooseCategory] = useState(false);
return (
<TouchableOpacity
style={
selectedCategory
? styles.chosenActiveCategoriesButton
: styles.activeCategoriesButton
}
onPress={() => {
getNewsByCategory(tagName);
chooseCategory(!selectedCategory);
}}>
<Text style={styles.activeCategoriesButtonText}>{tagName}</Text>
</TouchableOpacity>
);
};
export default Tags;
In total it renders 5 tags from array.
When I click on one of the tags its state is changing to true how it is supposed to but the problem is, when I click on the another tag the state of previous tag is not being changed to false,Any suggestions on how can I achieve it please?

The solution i would go with is saving wich 'Tag' is selected by their key where you also map them, then add something like 'isSelected' as a prop wich is true for you the one you selected.
Something like:
const [selectedCategory, setSelectedCategory] = useState();
const getNewsByCategory = (tagName) => {
setSelectedCategory(tagName);
//whatever you were doing here before
}
{activeNewsCategories.map((i) => (
<Tags
key={i}
tagName={i}
isSelected={selectedCategory === i}
getNewsByCategory={getNewsByCategory}
/>
))}
And then change the
const [selectedCategory, chooseCategory] = useState(false);
in the Tags to
const [selectedCategory, chooseCategory] = tagName

Related

How to get single value in react-select dropdown

I am trying to create a multi-select dropdown indicator (the second element shown here) using react-select.
The purpose is to show all blog post categories on a blog page, and then to only render the blog posts that are selected in the dropdown indicator.
The tags are extracted from their posts based on a GraphQL query and stored in useState variables "tags" and "renderedPosts".
How do I simply get a value from the dropdown when a category is added or removed? Reading the react-select API, I get this:
getValue () => ReadonlyArray<...>
I don't know how to use that, VS Code simply screams when I try add an arrow function as an attribute in the Select.
I understand there is supposed to be a "value" by default on the Select but if I try to use it I get undefined.
I don't know if mapping to the default value is a problem or if it has anything to do with the ContextProvider (which was necessary). There are other attributes I cannot get to work either, like maxMenuHeight (which is supposed to take a number).
Allow me to share my code (you can probably ignore the useEffect):
export default function Blog({ data }) {
const { posts } = data.blog
const [tags, setTags] = useState([])
const [renderedPosts, setRenderedPosts] = useState([])
// Context necessary to render default options in react-select
const TagsContext = createContext();
// setting all tags (for dropdown-indicator) and rendered posts below the dropdown (initially these two will be the same)
useEffect(() => {
const arr = [];
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.some(index => index.value === tag)) {
arr.push({ value: tag, label: tag })
}
})
});
setTags([arr]);
setRenderedPosts([arr]);
}, []);
function changeRenderedPosts(value???){
setRenderedPosts(value???)
}
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
defaultValue={tags.map(tag => tag) }
isMulti
name="tags"
options={tags}
className="basic-multi-select"
classNamePrefix="select"
// HOW DO I PASS THE VALUE OF THE ADDED/DELETED OPTION?
onChange={() => changeRenderedPosts(value???)}
maxMenuHeight= {1}
/>
</TagsContext.Provider>
// posts to be rendered based on renderedPosts value
{posts.map(post => {
EDIT: The closest I have now come to a solution is the following:
function changeRenderedTags(options){
console.log(options) //logs the remaining options
setRenderedTags(options) //blocks the change of the dropdown elements
}
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
...
onChange={(tagOptions) => changeRenderedTags(tagOptions)}
I click to delete one option from the dropdown and I get the other two options in "tagOptions". But then if I try to change "renderedTags", the update of the state is blocked. I find this inexplicable as "setRenderedTags" has nothing to do with the rendering of the dropdown or its data!
with isMulti option true, you get array of options(here it's tags) from onChange callback. so I guess you could just set new tags and render filtered posts depending on the selected tags like below?
const renderedPosts = posts.filter(post => post.tags.some(tag => tags.includes(tag)))
...
onChange={selectedTags => {
setTags(selectedTags ? selectedTags.map(option => option.value) : [])
}}
...
I finally solved it - I don't know if it is the best solution but it really works! Sharing it here in case anyone else is in the exact same situation, or if you are just curious. Constructive criticism is welcome.
export default function Blog({ data }) {
const { posts } = data.blog
const [tags, setTags] = useState([])
const [renderedTags, setRenderedTags] = useState([])
const TagsContext = createContext();
useEffect(() => {
const arr = [];
posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.some(index => index.value === tag)) {
arr.push({ value: tag, label: tag })
}
})
});
setTags([...arr]);
}, [posts]);
useEffect(() => {
setRenderedTags([...tags]);
}, [tags])
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
defaultValue={tags}
isMulti
name="tags"
options={tags}
className="basic-multi-select"
classNamePrefix="select"
onChange={(tagOptions) => setRenderedTags(tagOptions ? tagOptions.map(option => option) : [])}
value={renderedTags}
/>
</TagsContext.Provider>
{posts.map(post =>
(post.frontmatter.tags.some(i => renderedTags.find(j => j.value === i))) ?
<article key={post.id}>
<Link to={post.fields.slug}>
<h2>{post.frontmatter.title}</h2>
<p>{post.frontmatter.introduction}</p>
</Link>
<small>
{post.frontmatter.author}, {post.frontmatter.date}
</small>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
: null
)}
</div>
</Layout>
)
}

onClick handler is applying on all elements after map

I have the following data:
export const Bookings = ({ booking, selectedBookings }) => {
const [selected, setSelected] = React.useState(false)
const handleSelect = (data) => {
selectedBookings(data)
}
{booking.map(data => (
<div
key={nanoid()}
className={selected ? styles.selectedDescription : styles.NonSelectedDescription}
onClick={() => {
handleSelect(data.name); // when I add this it does not trigger the style toggling
setSelected(!selected) // if I keep only this handler it works but for all childrens
}}
>
{description}
</div>
In this code I want to send data to parent component and apply a specific styles when the booking is clicked, BUT I have 2 issues:
1- when I remove the handleSelect(data.name) the styles apply on whole childrens and not one single chidren.
2- when I add the handleSelect(data.name) handler the styles does not work at all, but the handler send the data to the parent
So is there any problem here?
You need to store different selected boolean for all bookings;
Try something like below:-
Change selected state to object:-
const [selected, setSelected] = React.useState({})
Change handleSelect like below:-
const handleSelect = (dataName) => {
setSelected({...selected, selected[dataName]: !selected[dataName]});
}
Change className in div like below:-
{booking.map(data => (
<div
key={nanoid()}
className={selected[data.name] ? styles.selectedDescription : styles.NonSelectedDescription}
onClick={() => { handleSelect(data.name); }}
>
{description}
</div>
))}
Note:- All booking name should be unique, If they are not unique you need to pass unique value in handleSelect function.

Only 1 true state intro array React js

What's up ?
I'm trying to reproduce the sliding button effect from frontity home page with ReactJS (NextJS).
Sliding buttons from Frontity
I managed to create the sliding button effect BUT I'm struggling with state management.
I have all my objects mapped with a "isActive : true/false" element and I would to create a function that put "isActive : true" on the clicked button BUT put "isActive: false" on all the other buttons.
I don't know the syntax / method for that kind of stuff.
Please, take a look at my codesandbox for more clarity (using react hooks):
https://codesandbox.io/s/busy-shirley-lgx96
Thank you very much people :)
UPDATE: As pointed out above by Drew Reese, even more cleaner/easier is to have just one activeIndex state:
const TabButtons = () => {
const [activeIndex, setActiveIndex] = useState(0);
const handleButtonClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ButtonsWrapper>
{TabButtonsItems.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={index === activeIndex}
onClick={() => handleButtonClick(index)}
/>
</div>
))}
<SlidingButton transformxbutton={activeIndex}></SlidingButton>
</ButtonsWrapper>
</>
);
};
I have made a slight modification of your TabButtons:
const TabButtons = () => {
const [buttonProps, setButtonProps] = useState(TabButtonsItems);
// //////////// STATE OF SLIDING BUTTON (TRANSLATE X ) ////////////
const [slidingbtn, setSlidingButton] = useState(0);
// //////////// HANDLE CLIK BUTTON ////////////
const HandleButtonState = (item, index) => {
setButtonProps((current) =>
current.map((i) => ({
...i,
isActive: item.id === i.id
}))
);
setSlidingButton(index);
};
return (
<>
<ButtonsWrapper>
{buttonProps.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={item.isActive}
onClick={() => HandleButtonState(item, index)}
/>
</div>
))}
<SlidingButton transformxbutton={slidingbtn}></SlidingButton>
</ButtonsWrapper>
</>
);
};
When we click on a button, we set its isActive state to true and all the rest buttons to isActive: false. We also should use state, since we also declared it. Changing state will force component to re-render, also we are not mutating anything, but recreating state for buttons.

Change css class of a component by onClick on Icon in React using useState hook

I am new to React and I was hoping someone could help me with this issue. I am trying to render some images called 'cards' from an array based on the same data I've received from Axios. I basically need to render an array of card props which have an <i> tag with some font-awesome class attached to them. When I click on the "fa-search-plus" font-awesome icon, I want the parent of this icon <div> to trigger the onClick such that the css property of the sibling <img> of this <div> can be changed. For some reason with the following code, this does not seem to happen. Any fix is appreciated. Thanks!
const GameCards = (cards) => {
const [cardimgclass, setCardimgclass] = useState(true);
const onClick = (e) => {
e.preventDefault();
setCardimgclass(!cardimgclass);
};
const loadCardsByCategory = (cards) => {
var allCards = [];
if (cards)
cards.forEach((item, i) => {
allCards.push(
<div key={item._id} className="card-container">
<img
className={cardimgclass ? "card-reg" : "card-big"}
src={item.src}
alt="No file"
/>
<div onClick={(e) => onClick(e)}>
{" "}
<i className="fas fa-search-plus"></i>
</div>
</div>
);
});
return allCards;
};
const loadCards = (cards) => {
return (
<Fragment>
<div className="cardgallery">{loadCardsByCategory(cards)}</div>
</Fragment>
)
};
const loadCardsUsingMemo = useMemo(() => loadCards(cards), [cards]);
return <Fragment>{loadCardsUsingMemo}</Fragment>;
};
It looks like your primary problem is that you are not destructuring your props object:
const GameCards = cards => {
You need to change that to:
const GameCards = ({ cards }) => {
Also, remove the useMemo stuff. It is not helping you here. Here is a slimmed down version of your code. I'm changing the background-color property with the class, but the concept is the same. Hope that helps!
EDIT: Also note, as in the example your logic is currently changing all of the elements. If you want to only modify the class for one element you could use the method of passing the index (or better yet, the ID!). Here is an example:
const [cardimgclass, setCardimgclass] = useState();
...
const onClick = (e, item) => setCardimgclass(item._id)
...
<div onClick={e => onClick(e, item)} />

Is setting a value attribute in TextInput necessary?

I was having issues with something like this (specifically in the TextInput value attribute):
const Stuff = props => {
const [items, setItems] = useState([]);
const handleNewItem = () => {
setItems([...items, '']);
};
const handleText = (text, index) => {
items[index] = text;
setItems(items);
// this was populating correctly in console.log
// as I type, it will come out like ["somedata", "blah"....] etc...
};
return (
<>
<View style={{marginTop: 20}}>
<View>
{items.map((items, index) => {
return (
<View key={index}>
<Text>{index + 1}</Text>
// issue with setting value attribute
// Text would disappear as I type in the input field
<TextInput value={items} onChangeText={text => handleText(text, index)} />
</View>
);
})}
<TouchableOpacity onPress={e => handleNewItem(e)}>
<Text>Add item</Text>
</TouchableOpacity>
</View>
</View>
</>
);
};
I was able to get console logged out the correct values for items, but on my mobile simulator, when I type something, the text disappears.
When I removed value={items} from the TextInput component, I'm able to type in the simulator input field, without the text disappearing. I always thought we needed a value from reactjs. Do we not need this? or am I doing something wrong?
I would suggest don't directly update your state. Instead use new object to update the state like
const handleText = (text, index) => {
let newItems = [...items];
newItems[index] = text;
setItems(newItems);
};

Resources