How to make search bar using React-Redux? - javascript

So guys, I have this component:
const Iphone = ({phones,searchQuery}) => {
const filterIphone = phones.map((p, index) =>
(<div className="model" key={index}>
<NavLink to={'/p/' + p.id}>{p.body.model}</NavLink>
</div>
))
return (
<div>
{filterIphone}
</div>
);
};
export default Iphone;
phones - array with objects using which I return model(title of phones) from an object.
searchQuery- value which i get from input. (from Redux)
So, I want to make Search Bar, but I don't know how in this situatuon i can filter " filterIphone " beause i have used map before. I need to make function which filters my titles (model).

Try this,
const phones = searchQuery && searchQuery.trim().length ? phones.filter(p => {
if(p && p.body && p.body.model && p.body.model.toLowerCase().includes(searchQuery.toLowerCase())){
return p
}
}) : phones
#const filterIphone = ......

Related

How can I evaluate each element in array and use useState() on one condition?

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>

how to write Foreach with out key in react js [duplicate]

I am making a React app that allows you to make a list and save it, but React has been giving me a warning that my elements don't have a unique key prop (elements List/ListForm). How should I create a unique key prop for user created elements? Below is my React code
var TitleForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var listName = {'name':this.refs.listName.value};
this.props.handleCreate(listName);
this.refs.listName.value = "";
},
render: function() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input className='form-control list-input' type='text' ref='listName' placeholder="List Name"/>
<br/>
<button className="btn btn-primary" type="submit">Create</button>
</form>
</div>
);
}
});
var ListForm = React.createClass({
getInitialState: function() {
return {items:[{'name':'item1'}],itemCount:1};
},
handleSubmit: function(e) {
e.preventDefault();
var list = {'name': this.props.name, 'data':[]};
var items = this.state.items;
for (var i = 1; i < items.length; i++) {
list.data.push(this.refs[items[i].name]);
}
this.props.update(list);
$('#'+this.props.name).remove();
},
handleClick: function() {
this.setState({
items: this.state.items.concat({'name':'item'+this.state.itemCount+1}),
itemCount: this.state.itemCount+1
});
},
handleDelete: function() {
this.setState({
itemCount: this.state.itemCount-1
});
},
render: function() {
var listItems = this.state.items.map(function(item) {
return (
<div>
<input type="text" className="list-form" placeholder="List Item" ref={item.name}/>
<br/>
</div>
);
});
return (
<div>
<form onSubmit={this.handleSubmit} className="well list-form-container">
{listItems}
<br/>
<div onClick={this.handleClick} className="btn btn-primary list-button">Add</div>
<div onClick={this.handleDelete} className="btn btn-primary list-button">Delete</div>
<button type="submit" className="btn btn-primary list-button">Save</button>
</form>
</div>
)
}
});
var List = React.createClass({
getInitialState: function() {
return {lists:[], savedLists: []};
},
handleCreate: function(listName) {
this.setState({
lists: this.state.lists.concat(listName)
});
},
updateSaved: function(list) {
this.setState({
savedLists: this.state.savedLists.concat(list)
});
},
render: function() {
var lst = this;
var lists = this.state.lists.map(function(list) {
return(
<div>
<div key={list.name} id={list.name}>
<h2 key={"header"+list.name}>{list.name}</h2>
<ListForm update={lst.updateSaved} name={list.name}/>
</div>
</div>
)
});
var savedLists = this.state.savedLists.map(function(list) {
var list_data = list.data;
list_data.map(function(data) {
return (
<li>{data}</li>
)
});
return(
<div>
<h2>{list.name}</h2>
<ul>
{list_data}
</ul>
</div>
)
});
var save_msg;
if(savedLists.length == 0){
save_msg = 'No Saved Lists';
}else{
save_msg = 'Saved Lists';
}
return (
<div>
<TitleForm handleCreate={this.handleCreate} />
{lists}
<h2>{save_msg}</h2>
{savedLists}
</div>
)
}
});
ReactDOM.render(<List/>,document.getElementById('app'));
My HTML:
<div class="container">
<h1>Title</h1>
<div id="app" class="center"></div>
</div>
There are many ways in which you can create unique keys, the simplest method is to use the index when iterating arrays.
Example
var lists = this.state.lists.map(function(list, index) {
return(
<div key={index}>
<div key={list.name} id={list.name}>
<h2 key={"header"+list.name}>{list.name}</h2>
<ListForm update={lst.updateSaved} name={list.name}/>
</div>
</div>
)
});
Wherever you're lopping over data, here this.state.lists.map, you can pass second parameter function(list, index) to the callback as well and that will be its index value and it will be unique for all the items in the array.
And then you can use it like
<div key={index}>
You can do the same here as well
var savedLists = this.state.savedLists.map(function(list, index) {
var list_data = list.data;
list_data.map(function(data, index) {
return (
<li key={index}>{data}</li>
)
});
return(
<div key={index}>
<h2>{list.name}</h2>
<ul>
{list_data}
</ul>
</div>
)
});
Edit
However, As pointed by the user Martin Dawson in the comment below, This is not always ideal.
So whats the solution then?
Many
You can create a function to generate unique keys/ids/numbers/strings and use that
You can make use of existing npm packages like uuid, uniqid, etc
You can also generate random number like new Date().getTime(); and prefix it with something from the item you're iterating to guarantee its uniqueness
Lastly, I recommend using the unique ID you get from the database, If you get it.
Example:
const generateKey = (pre) => {
return `${ pre }_${ new Date().getTime() }`;
}
const savedLists = this.state.savedLists.map( list => {
const list_data = list.data.map( data => <li key={ generateKey(data) }>{ data }</li> );
return(
<div key={ generateKey(list.name) }>
<h2>{ list.name }</h2>
<ul>
{ list_data }
</ul>
</div>
)
});
It is important to remember that React expects STABLE keys, meaning you should assign the keys once and every item on your list should receive the same key every time, that way React can optimize around your data changes when it is reconciling the virtual DOM and decides which components need to re-render.
So, if you are using UUID you need to do it at the data level, not at the UI level.
Also keep in mind you can use any string you want for the key, so you can often combine several fields into one unique ID, something like ${username}_${timestamp} can be a fine unique key for a line in a chat, for example.
Keys helps React identify which items have changed/added/removed and should be given to the elements inside the array to give the elements a stable identity.
With that in mind, there are basically three different strategies as described bellow:
Static Elements (when you don't need to keep html state (focus, cursor position, etc)
Editable and sortable elements
Editable but not sortable elements
As React Documentation explains, we need to give stable identity to the elements and because of that, carefully choose the strategy that best suits your needs:
STATIC ELEMENTS
As we can see also in React Documentation, is not recommended the use of index for keys "if the order of items may change. This can negatively impact performance and may cause issues with component state".
In case of static elements like tables, lists, etc, I recommend using a tool called shortid.
1) Install the package using NPM/YARN:
npm install shortid --save
2) Import in the class file you want to use it:
import shortid from 'shortid';
2) The command to generate a new id is shortid.generate().
3) Example:
renderDropdownItems = (): React.ReactNode => {
const { data, isDisabled } = this.props;
const { selectedValue } = this.state;
const dropdownItems: Array<React.ReactNode> = [];
if (data) {
data.forEach(item => {
dropdownItems.push(
<option value={item.value} key={shortid.generate()}>
{item.text}
</option>
);
});
}
return (
<select
value={selectedValue}
onChange={this.onSelectedItemChanged}
disabled={isDisabled}
>
{dropdownItems}
</select>
);
};
IMPORTANT: As React Virtual DOM relies on the key, with shortid every time the element is re-rendered a new key will be created and the element will loose it's html state like focus or cursor position. Consider this when deciding how the key will be generated as the strategy above can be useful only when you are building elements that won't have their values changed like lists or read only fields.
EDITABLE (sortable) FIELDS
If the element is sortable and you have a unique ID of the item, combine it with some extra string (in case you need to have the same information twice in a page). This is the most recommended scenario.
Example:
renderDropdownItems = (): React.ReactNode => {
const elementKey:string = 'ddownitem_';
const { data, isDisabled } = this.props;
const { selectedValue } = this.state;
const dropdownItems: Array<React.ReactNode> = [];
if (data) {
data.forEach(item => {
dropdownItems.push(
<option value={item.value} key={${elementKey}${item.id}}>
{item.text}
</option>
);
});
}
return (
<select
value={selectedValue}
onChange={this.onSelectedItemChanged}
disabled={isDisabled}
>
{dropdownItems}
</select>
);
};
EDITABLE (non sortable) FIELDS (e.g. INPUT ELEMENTS)
As a last resort, for editable (but non sortable) fields like input, you can use some the index with some starting text as element key cannot be duplicated.
Example:
renderDropdownItems = (): React.ReactNode => {
const elementKey:string = 'ddownitem_';
const { data, isDisabled } = this.props;
const { selectedValue } = this.state;
const dropdownItems: Array<React.ReactNode> = [];
if (data) {
data.forEach((item:any index:number) => {
dropdownItems.push(
<option value={item.value} key={${elementKey}${index}}>
{item.text}
</option>
);
});
}
return (
<select
value={selectedValue}
onChange={this.onSelectedItemChanged}
disabled={isDisabled}
>
{dropdownItems}
</select>
);
};
Hope this helps.
Do not use this return `${ pre }_${ new Date().getTime()}`;. It's better to have the array index instead of that because, even though it's not ideal, that way you will at least get some consistency among the list components, with the new Date function you will get constant inconsistency. That means every new iteration of the function will lead to a new truly unique key.
The unique key doesn't mean that it needs to be globally unique, it means that it needs to be unique in the context of the component, so it doesn't run useless re-renders all the time. You won't feel the problem associated with new Date initially, but you will feel it, for example, if you need to get back to the already rendered list and React starts getting all confused because it doesn't know which component changed and which didn't, resulting in memory leaks, because, you guessed it, according to your Date key, every component changed.
Now to my answer. Let's say you are rendering a list of YouTube videos. Use the video id (arqTu9Ay4Ig) as a unique ID. That way, if that ID doesn't change, the component will stay the same, but if it does, React will recognize that it's a new Video and change it accordingly.
It doesn't have to be that strict, the little more relaxed variant is to use the title, like Erez Hochman already pointed out, or a combination of the attributes of the component (title plus category), so you can tell React to check if they have changed or not.
edited some unimportant stuff
Let React Assign Keys To Children
You may leverage React.Children API:
const { Children } = React;
const DATA = [
'foo',
'bar',
'baz',
];
const MyComponent = () => (
<div>
{Children.toArray(DATA.map(data => <p>{data}</p>))}
</div>
);
ReactDOM.render(<MyComponent />,document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
To add the latest solution for 2021...
I found that the project nanoid provides unique string ids that can be used as key while also being fast and very small.
After installing using npm install nanoid, use as follows:
import { nanoid } from 'nanoid';
// Have the id associated with the data.
const todos = [{id: nanoid(), text: 'first todo'}];
// Then later, it can be rendered using a stable id as the key.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)
Another option is weak-key: https://www.npmjs.com/package/weak-key
import weakKey from "weak-key";
const obj1 = {a : 42};
const obj2 = {b : 123};
const obj3 = {a : 42};
console.log(weakKey(obj1)); // 'weak-key-1'
console.log(weakKey(obj2)); // 'weak-key-2'
console.log(weakKey(obj3)); // 'weak-key-3'
console.log(weakKey(obj1)); // 'weak-key-1'
For a simple array of text-strings; I'm trying one of the two ways:
1. encodeURI which is available on both; NodeJS and browser
const WithEncoder = () => {
const getKey = useCallback((str, idx) => encodeURI(`${str},${idx}`), [])
return (
<div>
{["foo", "bar"].map((str, idx) => (
<div key={getKey(str, idx)}>{str}</div>
))}
</div>
)
}
2. window.btoa which is available only in browser.
const WithB2A = () => {
const getKey = useCallback((str, idx) => window.btoa(`${str}-${idx}`), [])
return (
<div>
{["foo", "bar"].map((str, idx) => (
<div key={getKey(str, idx)}>{str}</div>
))}
</div>
)
}
Depends on the situation, choose a uniqueId creator is ok when you just want render silly items, but if you render items like drag&drop etc and you haven't any uniqueId for each item, I recommend remap that data in your redux, mapper, wherever and add for each item an uniqueId (and not in the render like <Item key={...}) because React couldn't perform any check between renders (and with that all the benefits).
With that remapped that you can use that new Id in your Component.
Here is what I have done, it works for reordering, adding, editing and deleting. Once set the key is not changed, so no unnecessary re-render. One PROBLEM which may be a show stopper for some: it requires adding a property to your object at first render say "_reactKey".
Example for functional component in psuedo TS (ie it won't run in snippets):
interface IRow{
myData: string,
_reactKey?:number
}
export default function List(props: {
rows: Array<IRow>
}) {
const {myRows} = props;
const [nextKey, setNextKey] = useState(100);
const [rows, setRows] = useState<Array<IRow>|undefined>();
useEffect(function () {
if (myRows) {
for (let row of myRows){
if (!row._reactKey){
row._reactKey = nextKey;
setNextKey(nextKey+1);
}
}
setRows(myRows);
} else if (!rows) {
setRows([]);
}
}, [myRows, columns]);
addRow(){
let newRow = { blah, blah, _reactKey : nextKey};
setNextKey(nextKey+1);
rows.push(newRow);
setRows({...rows});
}
function MyRow(props:{row:IRow}){
const {row} = props;
return <tr><td>{row._reactKey}</td><td>row.myData</td></tr>
}
return <table>
<tr><th>Index</th><th>React Key</th><th>My Data</th></tr>
rows.map((row, key)=>{
return <MyRow key={row._reactKey} row={row} />
}
</table>
}
I don't use react too much, but the last time I saw this issue I just created a new state array, and tracked the keys there.
const [keys, setKeys] = useState([0]);
const [items, setItems] = useState([value: "", key: 0,])
Then when I add a new item to list, I get the last key from the keys array, add 1, then use setKeys to update the keys array. Something like this:
const addItemWithKey = () => {
// create a new array from the state variable
let newKeyArr = [...keys];
// create a new array from the state variable that needs to be tracked with keys
let newItemArr = [...items];
// get the last key value and add 1
let key = newKeyArr[newKeyArr.length-1] + 1;
newKeyArr.push(key);
newItemArr.push({value: "", key: key,});
// set the state variable
setKeys(newKeyArr);
setItems(newItemArr);
};
I don't worry about removing values from the keys array because it's only being used for iterating in the component, and we're trying to solve for the case where we remove an item from the list and/or add a new item. By getting the last number from the keys array and adding one, we should always have unique keys.
import React, {useState} from 'react';
import {SafeAreaView,ScrollView,StyleSheet,Text,View,Dimensions} from 'react-native';
const {width}=Dimensions.get('window');
function sayfalar(){
let pages=[]
for (let i = 0; i < 100; i++) {
pages.push(<View key={i} style={styles.pages}><Text>{i}</Text></View>)
}
return pages
}
const App=()=>{
return(
<View style={styles.container}>
<ScrollView horizontal={true} pagingEnabled={true}>
{sayfalar()}
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
container:{
flexDirection:'row',
flex:1
},
pages:{
width:width
}
})
export default App;
Use the mapped index (i)
things.map((x,i) => {
<div key=i></div>
});
Hope this helps.
You can use react-html-id to generate uniq id easely : https://www.npmjs.com/package/react-html-id
The fastest solution in 2021 is to use uniqid: Go to https://www.npmjs.com/package/uniqid for more info but to sum up:
First in your terminal and your project file: npm install uniqid
Import uniqid in your project
Use it in any key that you need!
uniqid = require('uniqid');
return(
<div>
<div key={ uniqid() } id={list.name}>
<h2 key={ uniqid() }>{list.name}</h2>
<ListForm update={lst.updateSaved} name={list.name}/>
</div>
</div>
)
});
I am using this:
<div key={+new Date() + Math.random()}>

REACT: How to swap elements in To Do list by their priorities

I'm doing to do list and I want to make function that swap tasks by their priorities. For example:
Go to gym
Learn react
I want to make button that move elements up and down and get:
Learn react
Go to gym
I have function that I'm pretty sure working correct but I think problem in <div> where I use this function
const moveUpDown = (currentIndex, nextIndex) =>{
const newCounts = [...todos]
const currentCounts = newCounts[currentIndex]
const previousCounts = newCounts[nextIndex]
newCounts[currentIndex] = previousCounts
newCounts[nextIndex] = currentCounts
setTodos(newCounts)
}
This is my return:
function ToDo(props) {
return (
<div key={props.todo} className="item-todo">
<div
className={props.todo.complete ? "item-text strike" : "item-text"}
onClick={() => props.toggleTask(props.todo.id)}
>
{props.todo.task}
</div>
<div className="item-delete" onClick={() => props.removeTask(props.todo.id)}>
X
</div>
// Lines below I'm using function moveUpDown
<div
className="item-moveUpDown" disabled = {props.todo === 0} onClick={() => props.moveUpDown(props.todo, props.todo - 1)}
>
Up
</div>
<div
className="item-moveUpDown" disabled = {props.todo === 0} onClick={() => props.moveUpDown(props.todo, props.todo + 1)}
>
Down
</div>
</div>
)
}
The parameters you're passing to moveUpDown don't make sense - it looks like they should be numbers, but you're passing props.todo which is an object.
Maybe if you changed the moveUpDown function to accept the "to do" you want to move, and a delta to indicate which direction you wanted to move it; eg:
const moveUpDown = (todo, delta) =>{
const newCounts = [...todos];
// Remove from the array
const currentIndex = newCounts.indexOf(todo);
newCounts.splice(currentIndex, 1);
// Now put it back in at the new position
newCounts.splice(currentIndex + delta, 0, todo);
setTodos(newCounts)
}
Now you can change your callers, eg:
<div
className="item-moveUpDown"
disabled={props.todo === 0}
onClick={() => props.moveUpDown(props.todo, 1)}
>
Down
</div>
Note also that your disabled attribute doesn't make sense - props.todo is an object so it will never be equal to zero. Perhaps you should be passing the index of the todo item as a separate property?

React single line component

Hello I have a component which doesnt return anything. Im following a tutorial and the person is using newer syntax which confuses me a bit. The component looks like this:
const Alert = ({alerts}) => alerts !== null && alerts.length > 0 && alerts.map(alert => (<div key={alert.id} className={`alert-${alert.type}`}>{alert.msg}</div>));
I simply want to know how to write this without it being single line. So i can see what's going on. Much appreciated in advance. For as far as i am aware you always need to return something.
const Alert = ({ alerts }) => {
if (alerts !== null && alerts.length > 0) {
return alerts.map(alert => (
<div key={alert.id} className={`alert-${alert.type}`}>
{alert.msg}
</div>
));
}
return null
};
Things at play here are:
Arrow Functions
Array.Map
JSX
Template Literals
Basically its a component that takes in an alerts property (Array) as a prop (<Alert alerts={[...]} />). It checks whether the passed array is present and is not empty and then maps over it. For every item in the array, we are rendering a div containing the alert message.
Hope this helps!
Very roughly (i.e., untested):
const Alert = ({alerts}) => {
if ((alerts === null) || (alerts.length === 0)) {
return null
}
return alerts.map(alert => (
<div
key={alert.id}
className={`alert-${alert.type}`}
>
{alert.msg}
</div>
))
}
const Alert = ({alerts}) => {
if (!alerts || !alerts.length) return null
return (
<>
{alerts.map(alert => (
<div key={alert.id} className={`alert-${alert.type}`}>{alert.msg}</div>
)}
</>
)
}
I think what you are struggling with is generally the one-liner syntax, which doesn't need a return if there are no braces present.
What I mean is that this line
return alerts.map(alert => {
return (<div key={alert.id} className={`alert-${alert.type}`}>{alert.msg} </div>)
})
Would be the same as this line
return alerts.map(alert => (<div key={alert.id} className={`alert-${alert.type}`}>{alert.msg} </div>))

How can I give a key in JSX the value of a variable depending on conditions

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.

Categories