Break map loop inside return in component - javascript

I'm trying to loop into an array to create the component content, if condition on array element is not satisfied then break the loop and return 1 component
leaves.map(leave => leave.id === currentUser.id ? <div> {leave} </div> : <div> no leaves </div>)
This is the code I have so far, no leaves is printed out every time leave.id is different than currentUser's id
What I need to is, print no leaves only when none of the leaves's id matches the currentUser's id and therefore I want to break the map after printing no leaves

You should use Array.prototype.some. In your case, you would pass a function to the method to check whether it satisfies your condition and use the ternary operator to render the content.
const arrWithId = [670760658, 250026214, 126834449, 987103760, 882536150, 666896331, 488576796, 186598055, 103751309, 419995457, 503676712, 487691896, 744253979, 269253696, 102370148, 237328910, 409016979, 979651614, 743486466, 445993562, 779323321, 939834768, 296731253, 925812473, 114149678];
const arrWithoutId = [123456789, 250026214, 126834449, 987103760, 882536150, 666896331, 488576796, 186598055, 103751309, 419995457, 503676712, 487691896, 744253979, 269253696, 102370148, 237328910, 409016979, 979651614, 743486466, 445993562, 779323321, 939834768, 296731253, 925812473, 114149678];
const id = 123456789;
const checker = elem => id === elem;
console.log(`With ID: ${arrWithId.some(checker) ? "yes" : "no"}`)
console.log(`Array: ${arrWithId}`);
console.log(`Without ID: ${arrWithoutId.some(checker) ? "yes" : "no"}`)
console.log(`Array: ${arrWithoutId}`);

Related

React state update produces wrong result

My Fixtures.js code:
const [selectedButton, setSelectedButton] = useState(1);
console.log("selected button = ",selectedButton);
const dateClick = (e) => {
const clcikedDivId = e.currentTarget.id;
// some code here!
setSelectedButton(clcikedDivId);
}
};
<div className={classes.roundDatesContainer}>
{roundDates.map((item, index) => (
<Button
id={index + 1}
key={index}
lowOpacity={index + 1 !== selectedButton ? true : false}
className="primary"
customCssStyle={customCssStyle}
click={dateClick}
>
{item}
</Button>
))}
</div>
In my above code snippet, I have a round dates array which I'm iterating over with the map function and for each item I'm outputting a Button component that will have an id property equal to index + 1 and a lowOpacity property of true in case the index + 1 is different from the selectedButton value and false otherwise. In addition it has a click property that tells the button to execute the dateClick function when the button is clicked, and this function will execute some code (that doesn't matter for this question) and at the end of the fucntion the selectedButton value will be set to the clicked button id
My Button component:
function Button(props) {
const { id, type, click, className, lowOpacity, customCssStyle } = props;
console.log("id = ", id, " & low opacity from btn componenet = ", lowOpacity);
return (
<button
id={id}
type={type || "button"}
onClick={
click ||
(() => {
return;
})
}
className={`${classes[className || ""]} ${lowOpacity ? classes.lowOpacity : undefined}`}
>
<span style={{ ...customCssStyle }}>{props.children}</span>
</button>
);
}
As you can see in this component I'm setting a class of lowOpacity to the button in case that prop is set to true (which will be true when index + 1 is different from the selectedButton value).
Now when the page first loads I get the following correct result:
(Note that I have react strict mode on which is why the fixtures.js is loading twice)
Since at first the selectedButton value defaults to 1 and index+1 is equal to 1 that means all buttons except the first 1 will have their lowOpacity property set to true (though there is also an undefined value for a Button with an id of undefined which I have no idea where did that come from).
The problem is when I click on another button all buttons will now have the lowOpacity propert set to true:
For some reason when I click the second (or third) Button the selectedButton changes and become equal to the id of that clicked Button however as you can see from the above image all of the Button components now have their lowOpacity property set to true (plus one of undefined for a button of id undefined which I have no idea where that came from)
The issue here is that IDs are treated as strings in the DOM, so here: index + 1 !== selectedButton (Where selectedButton is set from e.currentTarget.id)
You are comparing a number to a string, and they will never be equal, so !== will always return true
E.g.
function example(e) {
console.log(e.currentTarget.id)
console.log(1 === e.currentTarget.id)
}
<input id="1" onClick="example(event)" placeholder="Click me" />
You will need to either:
Convert the index + 1 to a string before comparing the values
Parse the ID as a number before the comparison
Parse the ID as a number before storing it to selectedButton
I would recommend replacing:
const clcikedDivId = e.currentTarget.id
with
const clcikedDivId = parseInt(e.currentTarget.id)

Define variable status by priority

I have a situation here.
I want to define a search status based on some priorities.
this are the possible status: notGenerated, generated, processing, invalidInput
I have a array like this:
['notGenerated', 'generated', 'generated', 'processing', 'invalidInput']
the priority is, if some element on array has 'notGenerated' the search status is 'notGenerated'
for the search status be 'generated', the elements on array cant be 'processing' or 'notGenerated', and need to has 'generated' in some of them.
for the search status be 'processing', some element on array must be 'processing' and the elements on array cant be 'notGenerated'.
for the 'invalidInput', we need that every element on the array be 'invalidInput'.
I make this code but i think its very ugly, how can i improve it?
resultsStatus = ['someStatus', 'someStatus', 'someStatus']
let searchStatus;
const hasNotGenerated = resultsStatus.includes('notGenerated');
const hasProcessing = resultsStatus.includes('processing');
const hasGenerated = resultsStatus.includes('generated');
const allInvalidInput = resultsStatus.filter((result) => result.status === 'invalidInput');
if (hasNotGenerated) searchStatus = 'notGenerated';
if (hasGenerated && !hasProcessing) searchStatus = 'generated';
if (resultsStatus.length === allInvalidInput.length) searchStatus = 'invalidInput';
return searchStatus;
You can just define "exit points" from your function, as soon as a criterion is satisfied.
Additionally, it's preferable to use an enum, as it guarantees no typos or other inconsistencies in your statuses.
const Status = {
NOT_GENERATED: 'notGenerated',
GENERATED: 'generated',
PROCESSING: 'processing',
INVALID: 'invalidInput',
};
function determineStatus(arr) {
if (arr.includes(Status.NOT_GENERATED)) return Status.NOT_GENERATED;
// If the array included 'notGenerated', it'd be returned on the previous check
if (arr.includes(Status.PROCESSING)) return Status.PROCESSING;
// If the array included 'notGenerated' or 'processing', it'd be returned on the previous checks
if (arr.includes(Status.GENERATED)) return Status.GENERATED;
if (arr.every(element => element === Status.INVALID)) return Status.INVALID;
}

how to stop rerendering in react on checkbox check?

I have simple list which is dynamically added on add button click. in my list there is a checkbox is also present .so I have an issue when I toggle the checkbox my whole list is re render why ?
let take example I added A,B,C,D in my list when I toggle D checkbox it should only render D item currently it render whole list why ?
here is my code
https://codesandbox.io/s/stupefied-wildflower-gv9be
const Item = ({ text, checked, onCheckedHandler }) => {
console.log(checked, "ssss");
return (
<div className={checked ? "bg" : ""}>
<span>{text}</span>
<input type="checkbox" onChange={e => onCheckedHandler(e, text)} />
</div>
);
};
Every time items changes (whether by adding a new item or checking a value), you are creating a new onCheckedHandler in your App. This propagates down to your Item component. Since the previous onCheckedHandler property is not referentially equivalent to the previous one, it renders (and you see that console log for each item). Memoizing the component alone won't help because a property being passed to it is changing every time.
To get around that, you need to memoize the onCheckedHandler, try this:
const onCheckedHandler = useCallback((e, selectedText) => {
const target = e.target
setItems(items => {
const i = items.findIndex(i => i.text === selectedText);
let obj = items[i];
obj.checked = target.checked;
return [...items.slice(0, i), obj, ...items.slice(i + 1)];
})
}, [setItems])
The you can wrap your Item compoennt with React.memo, and it should work as expected. You'll also need to import the useCallback the same way you import useState

How to search in a json array, and return the result as I type in the input

So far I can only search an element of the array if I type the exact name present in my api, in this my api has an array with 20 positions and if I type exactly the name of the element I search it returns an array with 19 positions with undefined and 1 position with the array found, what I want to do and search while I type instead of searching only when I type the full name.
After my search I try to change the state of a component so that it is rendered only with the value fetched, but this does not happen, if anyone knows I am very grateful.
updated code
import data from "../sample_data/recipes.json";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchString: []
};
}
componentDidMount() {
this.setState({ searchString: data.results })
}
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var indexes = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) !== -1;
})
this.setState({ searchString : indexes });
}
render() {
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={this.state.searchString} />}
</div>
</div>
</div>
);
}
}
export default App;
I suppose that you want some kind of filtering while you're typing:
can you try this ? :
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var filteredItems = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) === -1;
})
this.setState({ searchString : filteredItems });
}
Explanation :
What is requested is to not display items that contains typed letters, to do that, you can use filter items with filter method and return only the items which doesn't have the typed letters in the title (using the indexOf method).
move setState outside map.
replace map with for loop
maybe try this?
var indexes = [];
data.results.forEach(item => {
if(item.title.indexOf(myObjTitle.title)) {
indexes.push(item);
}
});
this.setState({searchString : indexes});
As I understand your question, you are trying to search the element from json Array.I just added the new key to store filterString. Here is my solution
onChange(firstName){
if(firstName != undefined){
this.setState({
nameToFilter : firstName
})
}
}
//Inside the render method
I am using nameToFilter to filter the data.
render() {
let searchString = this.state.searchString
if(this.state.nameToFilter != undefined && this.state.nameToFilter.length>0)
{
searchString = this.state.searchString.filter(item => (
item.title == this.state.nameToFilter
));
}
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={searchString} />}
</div>
</div>
</div>
);
}
It seems you are trying to filter the matching results. In which case it's probably better to use filter instead of map.
onChange(value) {
let results = [];
// Empty string is `falsy`, avoid filtering when no value was given
if (value) {
/*
Pick only one option, remove the other two, or you will
process your data three times instead of once!
*/
// Case-sensitive, exact match
results = data.filter(item => item.title === value);
// Case-insensitive, exact match, value and title are the same when lowercased
results = data.filter(item => (
item.title.toLowerCase() === value.toLowerCase()
));
// Case-insensitive, partial match, value was found *within* title
results = data.filter(item => (
item.title.toLowerCase().indexOf(
value.toLowerCase()
) !== -1
));
}
this.setState({results});
}
Additionally, if you want a single result (the first one that matches), using find instead of filter is better, because it would stop searching after the first match instead of traversing the whole array.
Map
Returns a new array with as many entries as you previously had, but you may return anything you want as the entries, this is why you get so many undefined entries right now. (Because you only return an item when the title matches, hence you implicitly return undefined when not). Map loops through the whole array.
Filter
Returns a new array with as many entries matches your test function. If the function returns true, then the item is included, it it returns false, the item will be omitted. Filter loops through the whole array.
Find
Will only retrieve the first entry that matches your test function. It will stop looping once a match is found.
Bonus
You will most likely need to learn about debouncing, which you can do with lodash.debounce.
Debouncing is a method used to prevent a function from executing many times in a short interval (so you don't unnecessarily re-render)

Add only one selection out of group to array

I'm creating a football betting app, where I'd like to pick a winner/loser/draw from a match, and then store that in a list of selected bets.
What I have so far
For each match, you can select a winner of either team or a draw. The getSelection method is passed down as props to the onClick handler of each button.
The getSelection method then adds the value of the button click to an array, like so:
getSelection = (val: object) => {
this.setState(
{bets: [...this.state.bets, val]},
() => console.log(this.state.bets, "bets")
);
};
I want only to be able to make a single selection for each match, represented visually like so:
Codesandbox.
This solution is updating the Match Results based on the User's selection.
SINGLE MATCH WILL HAVE SINGLE RESULT, i.e RESULTS WILL OVERWRITE
Also to keep track of Match Number, I have used matchNumber as an index.
Your getSelection will look something like this.
getSelection = (val: object, matchNumber: number) => {
// 1. Make a shallow copy of the items
let bets = [...this.state.bets];
// 2. Make a shallow copy of the item you want to mutate
let bet = { ...bets[matchNumber] };
// 3. Replace the property you're intested in
bet = val;
// 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
bets[matchNumber] = bet;
// 5. Set the state to our new copy
this.setState({ bets }, () => console.log(this.state.bets));
Update the background:
<button
style={{ background: winner == home.name ? "lightblue" : "white" }}
onClick={() => this.props.getSelection(home, matchNumber)}
>
{home.name}
</button>
<button
style={{ background: winner == draw.name ? "lightblue" : "white" }}
onClick={() => this.props.getSelection(draw, matchNumber)}
>
{draw.name}
</button>
<button
style={{ background: winner == away.name ? "lightblue" : "white" }}
onClick={() => this.props.getSelection(away, matchNumber)}
>
{away.name}
</button>
Check this working solution. https://codesandbox.io/s/9lpnvx188y
Are you saying you should only be able to add a single possibility once?
Then this might work:
getSelection = (val: object) => {
this.setState( {
bets: [...this.state.bets, val].filter((value, index, self) => { return self.indexOf(value) === index; })
},
() => console.log(this.state.bets, "bets")
);
};
(That is, take the entire array, add the new value, filter so you only get the distinct values, and store it in bets).
ala: https://codesandbox.io/s/x2o5m95oxw

Categories

Resources