i used a custom hook and i want to show a single item which is matched by particular id,i tried multiple method but didnt work.here is my code :
const SingleServiceDetail = () => {
const {idOfService} = useParams();
const [data] = useCustom();
const singleItem = data.find(element =>element._id === idOfService);
console.log(singleItem);
return (
<div>
<h2>You have Choosed :{idOfService}</h2>
{/* <p>{singleItem.name}</p> */}
</div>
);
};
Possibly idOfService from useParams is a string and element._id is a number.
Try with double equal it will type coerce and check the id.
const singleItem = data.find(element => element._id == idOfService);
else change the idOfService to number
const singleItem = data.find(element => element._id === +idOfService);
Related
everyone!
I have a component that displays an input field. This component has some patterns it has to meet before doing anything with its data. The regex is: /^(\d*)\s*(\([+-]\d+\))?$/. Basically, it accepts a number, optionally followed by another number between parenthesis and a signal (+ or -), like: 4 (+2); 5 (-1); 75 (-20); 10; 2.
I couldn't find a proper way to build my component that meets this pattern while displaying the proper text to the user. In this iteration, I created three states: one for the text field, one for the first value, one for the value between parenthesis.
The problem with this approach below is that there are two sources of truth: field and modifier|value, which is not ideal and lead to unnecessary code trying to sync them.
const CharacterTraitField: React.FC<CharacterTraitFieldProps> = (props) => {
const [field, setField] = useState(`${props.value} (+${props.modifier})` /*string*/);
const [value, setValue] = useState(props.value /*number*/);
const [modifier, setModifier] = useState(props.modifier /*number*/);
const onValueBlur: React.FocusEventHandler<HTMLInputElement> = (ev) => {
const match = field.match(FIELD_REGEX);
if (!match) {
setField('0');
setValue(0);
setModifier(0);
return console.warn(value, 'input is not valid.');
}
const val = parseInt(match[1]) || 0;
const mod = parseInt(match[2].replace(/\(|\)/g, '')) || 0;
setValue(val);
setModifier(mod);
};
return (
<>
<div>
<FormControl
// pattern='^(\d*)\s*(\([+-]\d+\))?$'
className='text-center'
size='sm'
value={field}
onChange={(ev) => setField(ev.target.value)}
onBlur={onValueBlur}
/>
</div>
</>
);
};
I have tried using only modifier|value but this leads to poor text manipulation when handling onChange event:
const CharacterTraitField: React.FC<CharacterTraitFieldProps> = (props) => {
const [value, setValue] = useState(props.value /*number*/);
const [modifier, setModifier] = useState(props.modifier /*number*/);
return (
<>
<div>
<FormControl
pattern='^(\d*)\s*(\([+-]\d+\))?$'
className='text-center'
size='sm'
value={`${value} (+${modifier})`}
onChange={(ev) => {
if (ev.target.validity.patternMismatch) return;
setField(ev.target.value);
}}
/>
</div>
</>
);
};
What is the best approach, given these props, that is both efficient and user-friendly?
I'm making a blackjack hand simulation and I've encountered an issue with my code.
The game goes like this: users gets two random cards and a total of the points, clicks 'hit' to get another random card from the deck. Now that's all working but there's one more rule: if that card is an Ace, user chooses if they want to get 1 or 10 points. I implemented it before when I only had one card at a time with useEffect, however now I refactored my code and the total isn't kept in useState + the array has two cards that need to evaluated, not the most recent one.
I've tried putting my loop and if statement in a useEffect and conditionally render the Popup to let the user decide (with and without dependencies), but when I put the useState() to trigger the condition, it throws an error that there have been too many renders and I'm not sure why that is.
Here's my Home component:
import {useState, useEffect} from 'react'
import Card from '../components/Card';
import Total from '../components/Total';
import Popup from '../components/Popup'
import {shuffle} from '../hooks/shuffleCards'
import {deckArray} from '../utils/data'
export default function Home(){
const startHandSize = 2
const [starterDeck, setStarterDeck] = useState(shuffle(deckArray))
const [howManyDealt, setHowManyDealt] = useState(startHandSize)
const [triggerPopup, setButtonPopup] = useState(false)
const deal = () => {
setHowManyDealt(startHandSize)
setStarterDeck(shuffle(deckArray))
}
const hit = () => !bust && setHowManyDealt(prev => prev + 1)
const usersCards = starterDeck.slice(-howManyDealt)
var total = 0
usersCards.forEach(function (arrayItem) {
if(arrayItem.card === "A"){
alert("you have an ace")
}
else{
total += arrayItem.value
}
});
const bust = total > 21;
return(
<div>
<button onClick={deal}>DEAL</button>
<button disabled={bust} onClick={hit}>HIT</button>
<button disabled={bust}>STAND</button>
<Total total={total} usersCards={usersCards}/>
<Card usersCards={usersCards}/>
{triggerPopup && <Popup total={total} setButtonPopup={setButtonPopup}/>}
</div>
)
}
and my Popup:
export default function Popup({total, setButtonPopup}){
const handleClick = (points) => {
total += points
setButtonPopup(false)
}
return(
<div className="popup">
<div className="popup-inner">
<h4>You've got an Ace. Would you like to collect 1 or 10 points?</h4>
<button className=".btn-popup" onClick={() => handleClick(1)}>1 POINT</button>
<button className=".btn-popup" onClick={() => handleClick(10)}>10 POINTS</button>
</div>
</div>
)
}
Any help much appreciated!
Good attempt. However, there seems to be a general misunderstanding about state. Consider this code:
const handleClick = (points) => {
total += points
setButtonPopup(false)
}
total is a purely local variable to Popup, so this += pretty much does nothing. To change state in the caller, you'd normally pass a callback that can trigger a setState and move the new value for total into state.
Remember: any data change must happen immutably, and if you want to trigger a re-render, you have to set state. Of course, there are ways to circumvent this flow using refs and so forth, but these are escape hatches you shouldn't use if you don't have to.
However, a design with total kept in state strikes me as redundant. We already know the total based on the cards in play. A better strategy seems to be having ace values individually settable via the popup modal, assuming you don't want to auto-compute these ace values to be as high as possible without busting or use a toggle switch instead of a modal.
I kept going with my code from your previous question and added the modal. I'm treating high aces as 11 per the rules of Blackjack, but you can easily make that 10 if you want.
As before, I'm hoping you can apply the techniques here to your code. The keys are the handleAceSet callback and the new piece of state aceToSet, which is a ace the user has picked, or null if the user hasn't chosen an ace. aceToSet is like your setButtonPopup, but tracks an object or null rather than a boolean. When aceToSet isn't null, the user has selected an ace and we show the modal to let them pick a value for it.
handleAceSet may seem a bit complex, but it has to be due to immutability. It finds the index of the ace the user wants to set in the deck array, then creates a new object at this index with the new value and glues the subarray slices before and after the index back together.
// utility library "import"
const cards = (() => {
const shuffle = a => {
a = a.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = ~~(Math.random() * (i + 1));
const x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
};
const frz = (...args) => Object.freeze(...args);
const suits = frz([..."HCSD"]);
const faces = frz([..."AJQK"]);
const pips = frz([...Array(9)].map((_, i) => i + 2));
const ranks = frz([...pips, ...faces]);
const cards = frz(
suits.flatMap(s =>
ranks.map(r =>
frz({
rank: r,
suit: s,
str: r + s,
value: isNaN(r) ? (r === "A" ? 1 : 10) : r,
})
)
)
);
const shuffled = () => shuffle(cards);
return {shuffled};
})();
const {Fragment, useState} = React;
const AceSetterModal = ({handleSetLow, handleSetHigh}) => (
<div>
<button onClick={handleSetLow}>Set ace low</button>
<button onClick={handleSetHigh}>Set ace high</button>
</div>
);
const Card = ({card, handleAceSet}) => (
<div>
{card.str}
{card.rank === "A" && (
<Fragment>
{" "}
<button onClick={handleAceSet}>
Set ({card.value})
</button>
</Fragment>
)}
</div>
);
const Game = () => {
const startHandSize = 2;
const goal = 21;
const lowAce = 1;
const highAce = 11;
const [deck, setDeck] = useState(cards.shuffled());
const [cardsDealt, setCardsDealt] = useState(startHandSize);
const [aceToSet, setAceToSet] = useState(null);
const handleAceSet = value => {
setDeck(deck => {
const i = deck.findIndex(e => e.str === aceToSet.str);
return [
...deck.slice(0, i),
{...aceToSet, value},
...deck.slice(i + 1),
];
});
setAceToSet(null);
};
const deal = () => {
setCardsDealt(startHandSize);
setDeck(cards.shuffled());
};
const hit = () => !bust && setCardsDealt(prev => prev + 1);
const cardsInPlay = deck.slice(-cardsDealt);
const total = cardsInPlay.reduce((a, e) => a + e.value, 0);
const bust = total > goal;
return (
<div>
{aceToSet ? (
<AceSetterModal
handleSetLow={() => handleAceSet(lowAce)}
handleSetHigh={() => handleAceSet(highAce)}
/>
) : (
<Fragment>
<button onClick={deal}>Deal</button>
<button disabled={bust} onClick={hit}>
Hit
</button>
<div>
{cardsInPlay.map(e => (
<Card
key={e.str}
handleAceSet={() => setAceToSet(e)}
card={e}
/>
))}
</div>
<div>Total: {total}</div>
<div>{bust && "Bust!"}</div>
</Fragment>
)}
</div>
);
};
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Game />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
in my react native app I have a list of tags where people can choose from, they click on an item and add it to the list of array of item ids, if they click over an item which id is already in the array, I want to remove it from array.
Right now all I can do is add ids to array, I can't remove it if already present.
PD: I also check if list of ids is lower than 10. Also, is there a cleanest way to write the function?
const [selectedItems, setSelectedItems] = useState([]);
const toggleItem = useCallback((itemId) =>
{
setSelectedItems(prev => prev.includes(itemId) ? prev.filter(obj => obj.id != itemId) : prev.length < 10 ? [ ...prev, itemId] : prev);
},[])
It looks like you're mixing and matching the items in the array - is it a list of IDs, or the list of objects?
prev.includes(itemId) // This looks like a list of IDs
? prev.filter(obj => obj.id != itemId) // This looks like a list of objects
In terms of "a cleaner way to write the function" - you can be a bit more efficient if you don't do the includes check first, as that involves an extra iteration over your items. In the example below I've changed the callback function to assume it gets passed the whole item, instead of just the id:
const [selectedItems, setSelectedItems] = useState([]);
const toggleItem = item => setSelectedItems(prev => {
const next = prev.filter(selectedItem => selectedItem.id !== item.id);
// The item wasn't removed from the list so it needs to be added
if (next.length === prev.length && next.length < 10) {
next.push(item);
}
return next;
});
You could also consider using a Map instead of an array:
const [selectedItems, setSelectedItems] = useState(new Map());
const toggleItem = item => setSelectedItems(prev => {
const next = new Map(prev);
if (!next.delete(item.id) && next.size < 10) {
next.set(item.id, item);
}
});
// If you need the items as an array
const selectedItemsArray = Array.from(selectedItems.values());
Finally - I'd also remove the useCallback because you probably don't need it.
Example of code:
https://stackblitz.com/edit/react-filtering-hooks-search-category?embed=1&file=index.js
In the above StackBlitz example, I am attempting to filter a list of items from a search input as well as by filtering out by categories through buttons. I already have this working with the search input and one button toggle, but I am struggling to make this conditional to work with multiple category selections. I'm stuck on coming up with the expression that filters by searchText as well as all the combinations of category selections. as seen in the logic below:
React.useEffect(() => {
const searchRegex = searchText && new RegExp(`${searchText}`, "i");
const filteredData = items.filter(
(item) =>
(!searchRegex ||
searchRegex.test(item.title) ||
!searchRegex ||
searchRegex.test(item.source)) &&
(!productsFilter || item.type === "product")
);
setFilteredItems(filteredData);
}, [searchText, productsFilter, resourceFilter, extraFilter]);
I started by removing the individual state you had for each filter and created a new one, which simply represented Filter as a whole:
const [filter, setFilter] = React.useState(new Set());
As each filter is selected, we can check the Set if it's included. If it is, we can remove it, otherwise we can insert it:
const filterClick = (value) => {
if (filter.has(value)) {
setFilter(prevFilter => {
const newSet = new Set(prevFilter);
newSet.delete(value);
return newSet;
});
} else {
setFilter(prevFilter => {
const newSet = new Set(prevFilter);
newSet.add(value);
return newSet;
});
}
};
We can indicate the ToggleButton is selected by checking the filter state:
<ToggleButton
togglable={true}
selected={filter.has("product")}
onClick={() => filterClick("product")}
>
Finally I combine this, together with your Input Search so we can filter on both the Filter and the Input Search:
let filteredItems = items.filter(item => {
if (filter.size > 0 && !filter.has(item.type))
return false;
if (searchText.length > 0 && !`${item.title}${item.descriptoin}`.includes(searchText))
return false;
return true;
}).map((item, i) => <li>{item.title} <br/><span>{item.descriptoin}</span></li>);
btw: descriptoin is spelt incorrectly and should be description.
I have a working example here:
https://stackblitz.com/edit/react-filtering-hooks-search-category-fktr2w
I'm learning React by implementing a front-end interface for the note app API that I created. I have succeeded in having a list of all the note titles in my database appear. I want to be able to click on a title and have the note expand into the text of the note. The easiest way I've found for this is to give the "key" attribute of the 'li' as a variable and to also declare the same variable in the JSX { } object because they have the same name.
I've been looking for an answer for this for a few days and have been unable to find this exact problem. You can put a variable in a normal JSX expression but I need to do it on the 'li' which means technically in the HTML.
Here's some code to understand what I'm saying.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
//AXIOS CALL
setTitles(response.data[0]);
});
}, []);
//^^^^^add the array there to stop the response.data from repeating WAY TOO MANY TIMES
let listTitles = titles.map(titles => (
<li className="noteTitles" key={titles.title}>
{titles.title}
</li>
));
let showText = titles.map(titles => (
<li className="openText" key= {titles.text_entry}>
{titles.text_entry}
</li>
))
let openNote = () => {
setOpen(open => !open);
if (open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{showText}
</ul>
</div>
);
}
if (!open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listTitles}
</ul>
</div>
);
}
};
return { openNote };
};
export default NoteData;
That is the code I currently have. Here's showing a more simplified version of the openNote function that maybe makes more sense and shows what I'm trying to do:
VariableHere = "";
let openNote = () => {
setOpen(open => !open);
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
};
let listNotes = titles.map(titles => (
<li className="noteTitles" key={VariableHere}>
{VariableHere}
</li>
));
return (
<div>
<ul onClick={openNote}>
{listNotes}
</ul>
</div>
);
On click of each element there should be a switch of the key elements so if the element is 'open' the key variable and given variable in the JSX object should be mapped to titles.text_entry and on '(!open)' the key and JSX should be mapped to titles.title.
first of all, you're using a ternary in a weird way:
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
Ternaries are meant to be expressions whose value is conditional, but you're using it like a shorthand if/else. Try something like
VariableHere = open ? titles.text_entry : titles.title;
which is both shorter and more readable.
Second of all, keys in an array of elements are meant to help React determine which elements to update, if an item represents the same object, its key shouldn't change. In this case, regardless of what you're displaying, an item in the array represents the same note. Always using the title as the key should be fine provided items can't have the same title. If they can, use some sort of unique ID instead. If the order of the items doesn't change throughout the life of the component, using the array index as the key is fine.
Lastly, what you seem to want to do is called "conditional rendering". There are many ways to achieve this in react, one such way is to use the pre-cited ternary operator. Here is a minimal working example:
const listNotes = titles.map(note => (
<li className="noteTitles" key={note.title}>
{open ? note.title : note.text_entry}
</li>
));
const openNote = () => {
setOpen(!open);
}
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listNotes}
</ul>
</div>
)
You could also use a ternary in the key expression, but as I talked about above, it's not a good idea to do so.
Given your data-structure, I think you can simplify your code a bit. There is no need to create separate arrays for titles and contents. It sounds like you just want to expand and collapse a note when it is selected.
Here is a really simplified version on how you an do this. I'll use a sample data-set since we don't have access to your API.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [currentNote, setCurrentNote] = useState({});
useEffect(() => {
//AXIOS CALL
// setTitles(response.data[0]);
let data = [
{ id: 1, title: "a", text_entry: "what" },
{ id: 2, title: "b", text_entry: "is" },
{ id: 3, title: "c", text_entry: "up?" }
];
setTitles(data);
}, []);
const handleClick = noteId => {
let selectedTitle = titles.find(title => title.id == noteId);
//"collapse" if already selected
if (noteId === currentNote.id) {
setCurrentNote({});
} else {
setCurrentNote(selectedTitle);
}
};
let listTitles = titles.map(title => (
<li
className="noteTitles"
key={title.title}
onClick={() => handleClick(title.id)}
>
{title.title}
{title.id === currentNote.id && <div>{title.text_entry}</div>}
</li>
));
return (
<div>
Click on link item
<ul>{listTitles}</ul>
</div>
);
};
See working sandbox: https://codesandbox.io/s/old-silence-366ne
The main updates:
You don't need to have an "open" state. To be more succinct and
accurate, you should have a currentNote state instead, which is
set when clicking on a list item.
Have your handleClick function accept a noteId as an argument.
Then use that noteId to find the corresponding note in your titles
state. Set that found note as the currentNote. If the selected
note was already the currentNote, simply set currentNote to an
empty object {}, thus creating our expanding/collapsing effect.
In the JSX, after the title, use a ternary operator to conditionally
display the currentNote. If the note being mapped matches the
currentNote, then you would display a div containing the
text_entry.