I'm looking to display some html in my React/Next.js web app based on conditional logic. I got the basics working but having issues showing the same html if multiple variable conditions are true. For example, the following code works fine.
{category === 'ford' &&
<div>Car</div>
}
{category === 'harley' &&
<div>Motorcycle</div>
}
I'm having issues showing multiple variables as true. The following code doesn't work but show the logic I'm after.
{category === 'ford' || category === 'toyota' &&
<div>Car</div>
}
//this code doesn't work.
I realise a simple answer is to separate operators for each separate condition, however i'm trying to avoid duplicating the html <div>Car</div> (as in my actual application contains large forms in this section).
You will need to wrap the OR-Condition in parentheses like so:
(category === 'ford' || category === 'toyota') && <div>Car</div>
you can also make use of Array includes method
I would make an array for e.g.
const cars = ["honda", "toyota", ....];
const motorcycle = ["euler", "ducati", ...];
{cars.includes(category) ? <div> cars </div> : <div> motorcycles </div> }
const isCarCategory = ["ford", "toyota"].includes(category);
const isMotorcycleCategory = ["harley"].includes(category);
return (
<div>
{isCarCategory && <div>Car</div>}
{isMotorcycleCategory && <div>Motorcycle</div>}
</div>
);
Just wrap your condition inside parenthesis.
Parenthesis must be used, if multiple conditions needs to be checked.
Check this link about Precedence And Associativity https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
{(category === 'ford' || category === 'toyota') &&
<div>Car</div>
}
you can wrap it all in a different function and use a switch statement (or arrays) to handle not managed category
like this
const renderVehicle = (category) => {
switch(category) {
case 'honda':
case 'ford':
return <div>Car</div>
case 'harley':
return <div>Motorcycle</div>
default:
return <div>Not found</div>
}
}
const renderVehicle2 = (category) => {
const cars = ['honda', 'ford']
const motorcycles = ['harley']
if(cars.includes(category)){
return <div>Car</div>
}
if(motorcycles.includes(category)){
return <div>Motorcycle</div>
}
return <div>Not found</div>
}
the simple answer is to wrap the condition in ()
{(category === 'ford' || category === 'toyota') &&
<div>Car</div>
}
Related
I have a redux hook responsible for server data that I want to pass into a set state after filtering it..
But with a dynamic condition.
// states
const [drilledData, setDrilledData] = useState([])
Current Code:
const drillDownData = (seriesIndex, dataPointIndex) => {
// here series index could be from 1-80 in that order
// dataPointIndex is a number and item.dataSet is also a number
// Refactor this code to remove if-else statements to something better
if (seriesIndex === 0) {
const filteredData = coscoData.filter((item) => item.dataSet === dataPointIndex)
setDrilledData(filteredData[0].shipments)
} else if (seriesIndex === 1) {
const filteredData = hapagData.filter((item) => item.dataSet === dataPointIndex)
setDrilledData(filteredData[0].shipments)
} else if (seriesIndex === 2) {
const filteredData = maerskData.filter((item) => item.dataSet === dataPointIndex)
setDrilledData(filteredData[0].shipments)
} else if (seriesIndex === 3) {
const filteredData = othersData.filter((item) => item.dataSet === dataPointIndex)
setDrilledData(filteredData[0].shipments)
} else {
setDrilledData([])
}
return null
}
Currently, the if-else statement is straightforward and works fine. But I have to repeat that if else statement 80 times. because I have to filter the data and with respect to series index and dataPointIndex and its related dataSet to set them conditionally
like data example
aclData,
admiralLineData,
anlData,
aplData,
arkasData,
data inside them is like that but dataSet can b more than one. so I'm using the filter to pick up matching dataSet to the pointIndex.
aclData = ['dataSet': 1, 'shipments' : [...{more objects}]]
So, I want to make that if else statement dynamic so I don't have to write up to 80 times to cover all the dataSets. How I can do that. ? I don't need to you refactor all code accurately, any idea or pseudo code will also be very helpful.
Is the relationship between the index number and the relevant data set static?
If so, could you create a util object that records those relationships and then use that to dynamically call the data set that you are trying to use? It would be less clunky than a giant if else (or switch statement), and reusable.
If the pattern for the above suggestion is too tricky to implement, a switch statement that calls a helper function, can at least reduce your code:
const filterMyData = (pertinentData, dataIndex) => {
const filteredData = pertinentData.filter((item) => item.dataSet ===
dataIndex)
setDrilledData(filteredData[0].shipments)
}
and then
switch (seriesIndex) {
case 0:
filterMyData(coscoData, dataToPointIndex);
break;
case 1:
filterMyData(hapagData, dataToPointIndex);
break;
....
}
I am fetching data from an api and I need to render a component based on an if statement and I cant seem to figure it out. A customer has an array of roles. Customer.items is an array of customer objects. This is the if statement I am trying but doesnt work:
{customers?.items?.length > 1 && !roles.includes("Super") && (...component
Basically I need to check if roles array has "Super" and customers.items only has one element then dont render the component.
Also if roles is "Super" and customer.items.length > 1 then still render the component
customers.items: [{id: 2, name: "G"}, {id: 3, name: "H"}]
roles: ["Super", "Admin"]
This will render the component in all cases except when customers.items has only one element and if the roles include 'Super'.
const hasSingleCustomer = customers?.items?.length === 1
const hasSuperRole = roles.includes('Super'))
{!(hasSingleCustomer && hasSuperRole) && <Component />}
You can also write it as {(!hasSingleCustomer || !hasSuperRole) && <Component />} if you prefer.
You can try this approach
{(customers.items.length > 1 && roles.includes("Super")) ? <If Success Component/> : <Failure Component>}
I have written as per your request, as I am checking if the roles array has "Super" in it, You can still manipulate the operation inside the brackets(), and we have to use ? and : to make sure the conditions work,
Happy Coding :)
My suggestion is to split the equation/ conditions into smaller variables and then use them to create a validity condition. This way, your code is more readable and easier to maintain
const length = customers.items.length
const isSuperUser = roles.includes('Super')
const isAdminUser = roles.includes('Admin')
const isAllowedForSuper = isSuperUser && length === 1
const isAllowedForAdmin = isAdminUser && length === 0
if (isAllowedForSuper || isAllowedForAdmin) {
return <Component {...props} />
}
return null
I'm building a cinema listings project. There is a block with information about each film and then the film times below it.
I have 2 dropdown menus - one to select a film, one to select a date. I'm using ternary operators to render the results but can't get it so that if a film doesn't have any showings on a particular day, the block of information about the film is hidden when that date is selected.
I'll just post an example for one of the films.
Here's the json file that the listing information is taken from -
[
{
"id": "film1",
"filmTitle": "Knives Out",
"paragraphText": "Lorem ipsum dolor sit amet",
"mon": ["12:00", "15:00", "19:00"],
"tue": ["13:10", "16:30", "19:00", "21:00"]
}
]
Here's part of the js file with one of the film listings -
constructor(props){
super(props);
this.state = {
filmListings: [],
selectedFilm: "allFilms",
selectedDate: "allDates"
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event){
const {name, value} = event.target
this.setState({
[name]: value
});
}
componentDidMount() {
const FilmListings = require("./components/booking/filmTimesData.json");
this.setState({ filmListings: FilmListings })
}
render() {
const filmsArray = require("./components/booking/filmTimesData.json");
const selectedFilm = this.state.selectedFilm
const selectedDate = this.state.selectedDate
return (
<form id="searchForm">
<div id="filmDateContainer">
<div className="searchOption">
<h2>Film:</h2>
<img src={FilmSearch} alt="film icon"/>
<select
name="selectedFilm"
value={this.state.selectedFilm}
onChange={this.handleChange}
className="dropdown"
>
<option value="allFilms">All Films</option>
<option value="film1">Knives Out</option>
<option value="film2">Judy and Punch</option>
<option value="film3">Harriet</option>
</select>
</div>
<h2 id="or">OR</h2>
<div className="searchOption">
<h2>Date:</h2>
<img src={DateSearch} alt="date icon"/>
<select
name="selectedDate"
value={this.state.selectedDate}
onChange={this.handleChange}
className="dropdown"
>
<option value="mon">Monday 2nd December</option>
<option value="tue">Tuesday 3rd December</option>
<option value="wed">Wednesday 4th December</option>
</select>
</div>
</div>
<div>
{(selectedFilm === "film1" || selectedFilm === "allFilms") ?
<FilmInfo filmTitle={filmsArray[0].filmTitle} paragraphText={filmsArray[0].paragraphText}/> : " "}
{(selectedDate === "mon" || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms") ?
<Mon day={filmsArray[0]}/> : " "
}
{(selectedDate === "tue" || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms") ?
<Tue day={filmsArray[0]}/> : " "
}
{(selectedDate === "wed" || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms") ?
<Wed day={filmsArray[0]}/> : " "
}
</div>
</form>
);
}
}
In this example, there's no showing of the film on a Wednesday so how can I get the info block for the film not to show when Wednesday is selected from the dropdown list?
I think that trying to limit it to one film item as opposed to looping over multiple ones has unfortunately made things more complicated, not less. It seems like you're working backwards from the conclusion that, "some films have a Wed showing, therefore we need to conditionally render a Wed component". In other words, starting with the difference between the data points rather than what they have in common.
We could write some convoluted conditional that checks whether a particular property exists, but it will be very brittle and you'll probably end up throwing it out anyway once you move on to mapping everything. It makes more sense to just be agnostic about which specific properties each film object has and allow the data to flow through your component more naturally.
Starting with your JSON file, group the showings data into a set of more discrete properties. You'll now be able to easily access and loop over the showings rather than trying to access each one individually by name.
[
{
"id": "film1",
"filmTitle": "Knives Out",
"paragraphText": "Lorem ipsum dolor sit amet",
"showings": [
{"date": "mon", "times": ["12:00", "15:00", "19:00"]},
{"date": "tue", "times": ["13:10", "16:30", "19:00", "21:00"]}
]
}
]
On to the component. Let's start by getting rid of the unnecessary lifecycle method, and some render variables. Since your data is coming from a static, local file, you can just import it at the top of the component and include it directly in the constructor. It only makes sense to use componentDidMount when processing data that is not immediately accessible on mount (e.g. it is coming from a remote resource, or waiting on some other component in the tree). I'm using import syntax here as it should be available to you in a boilerplate React environment.
import filmData from "./components/booking/filmTimesData.json";
class FilmListings extends React.Component {
constructor(props){
super(props);
this.state = {
filmListings: filmData,
selectedFilm: "allFilms",
selectedDate: "allDates"
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event){
const {name, value} = event.target
this.setState({
[name]: value
});
}
render() {
const { filmListings, selectedFilm, selectedDate } = this.state;
return (
...
);
}
}
Now the render function. I'm going to leave the filmDateContainer alone because it's essentially fine, although of course you'll want to map over the options from the filmListings instead of hardcoding, which should become clearer after this next bit.
We're going to replace the entire div after filmDateContainer with this mapping function, which first loops over each film object to make sure it does not conflict with selectedFilm. It then loops over that film object's showings to make sure each one in turn does not conflit with selectedDate. Essentially, we've created a staggered filter which nests the data by order of importance (crucially, as you may now notice, by the same order that it is structured in the JSON file).
{filmListings.map(film =>
selectedFilm === "allFilms" || selectedFilm === film.id ? (
<div key={film.id}>
<FilmInfo filmTitle={film.filmTitle} paragraphText={film.paragraphText}/>
{film.showings.map(showing =>
selectedDate === "allDates" || selectedDate === showing.date ? (
<Day showing={showing} key={film.id + showing.date}/>
) : null)}
</div>
) : null)}
Remember to assign appropriate, unique keys each time you render a mapping function so that React can keep track of everything. The nesting is also already quite dense so you might want to think about separating each map into its own variable which returns that little snippet of JSX it's responsible for, especially if you have any extra conditions to consider or properties to map.
If you have any questions about any of this drop a comment and I'll try my best to explain.
Edit:
I think the best way to handle the more complex conditional logic you are after is to write some selectors. This is a concept you'll get more familiar with when you move on to more complicated state management, but it can be used just as effectively here by adding some simple functions before your render return. As a bonus, it's also going to clean up the render return which is quite nice because it was already starting to look a bit ugly. By combining selectors you can start to reason about your state in a more logical, readable way, and define common operations which produce specific outcomes.
I also got rid of the ternaries and went for pure && evaluation since it's a bit cleaner.
render() {
const { filmListings, selectedFilm, selectedDate } = this.state;
const allFilms = selectedFilm === 'allFilms';
const allDates = selectedDate === 'allDates';
const validDate = date => allDates || selectedDate === date;
const filmHasDate = showings => showings.some(showing => validDate(showing.date));
const validFilm = (id, showings) => (allFilms || selectedFilm === id) && filmHasDate(showings);
return (
<form>
...
{filmListings.map(film => validFilm(film.id, film.showings) && (
<div key={film.id}>
<FilmInfo filmTitle={film.filmTitle} paragraphText={film.paragraphText}/>
{film.showings.map(showing => validDate(showing.date) && (
<Day showing={showing} key={film.id + showing.date}/>
))}
</div>
))}
</form>
);
}
One of the drawbacks to this approach is that you are doing all these calculations every time the component rerenders. Depending on the kind of application you have, how often the component rerenders, and how large the input array is, that could limit performance, but in the vast majority of cases its not going to be an issue.
The alternative would be to do these calculations once, only when handleChange is called, and store the result in a new state variable (e.g. filteredListings) which you could then map over directly in your render function. However, then you've got duplicate state on your hands with the same data in multiple places, which can be a headache to reason about and synchronise when your data set gets to be any considerable size. Just something to think about!
In your example, you could simply do:
{((selectedDate === "wed" && filmsArray[0][selectedDate]) || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms")
&&
<Wed day={filmsArray[0]}/>
}
It would check that your film object actually has a "wed" key and conditionally render the Wed component.
Note that I ditched the ? because if the result of
((selectedDate === "wed" && filmsArray[0][selectedDate]) || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms")
is false, the component would not be rendered. I just find it cleaner.
You could do this for each of your cases.
Your code would then look like:
<div>
{(selectedFilm === "film1" || selectedFilm === "allFilms") &&
<FilmInfo filmTitle={filmsArray[0].filmTitle} paragraphText={filmsArray[0].paragraphText}/>
}
{((selectedDate === "mon" && filmsArray[0][selectedDate]) || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms")
&&
<Mon day={filmsArray[0]}/>
}
{((selectedDate === "tue" && filmsArray[0][selectedDate]) || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms")
&&
<Tue day={filmsArray[0]}/>
}
{((selectedDate === "wed" && filmsArray[0][selectedDate]) || selectedDate === "allDates")
&& (selectedFilm === "film1" || selectedFilm === "allFilms")
&&
<Wed day={filmsArray[0]}/>
}
</div>
But I do think that you should re-architecture your solution to have a simpler and more robust way to display the desired component. Something in the line of #lawrencee-witt suggestion.
The final solution would be heavily influenced by the control you have over that JSON file.
I can create a small CodePen example if you want more info.
I have 2 user IDs and I would like to do different but very similar logic if I have only one or both. Is there a way to consolidate this code, as right now it looks ugly.
function getUserPermission(primaryId, secondaryId, role) {
let permission = userInfo.permissionList.filter( permission => {
//logical AND
if(primaryId && secondaryId){
// have both IDs, use both IDs
(permission.primaryId === primaryId && permission.secondaryId === secondaryId) && permission.role === role
}
//logical XOR
else if((primaryId && !secondaryId) || (!primaryId && secondaryId)) {
// have only 1 ID, use 1 ID
(permission.primaryId === primaryId || permission.secondaryId === secondaryId) && permission.role === role
}
})[0]
return permission
}
First, it seems like this logic doesn't handle when both ids are invalid. You'll want to handle that somehow (looks like both the other answers so far include something along those lines).
Next, since you're only returning the first matching permission, I would suggest using a solution that doesn't keep looping once you've found that first match. In that respect, Array.find() is far better than Array.filter() for this use case.
If I were to put the other answers together, it would be something like this:
function getUserPermission(primaryId, secondaryId, role) {
if (!primaryId && !secondaryId) return null // at least one must be populated, why loop at all?
return userInfo.permissionList.find(permission =>
permission.role === role
&& (!primaryId || primaryId === permission.primaryId) // if primary is populated, it needs to match
&& (!secondaryId || secondaryId === permission.secondaryId) // if secondary is populated, it needs to match
);
}
...but that doesn't handle the case where a perfectly valid ID happens to be 0. That could depend on your data, though. Perhaps you're always using string-based IDs, for example. It also checks for valid ids on every loop (something you may or may not be perfectly comfortable with)
With those potential issues in mind, I made some assumptions about your data/types and threw a more unorthodox approach together in an attempt to solve them:
const getUserPermission = (primaryId = -1, secondaryId = -1, role = '') => {
if (primaryId < 0 && secondaryId < 0) return null
const conditions = [ p => p.role === role ]
.concat(primaryId >= 0 ? [ p => p.primaryId === primaryId ] : [])
.concat(secondaryId >= 0 ? [ p => p.secondaryId === secondaryId ] : [])
return userInfo.permissionList.find(p => conditions.every(fn => fn(p)))
}
...this last one makes an array of functions, conditions, to check each permission against. The first one to match every condition should be returned (in theory, at least - I didn't test it)
What about something like this?
if(!primaryId && !secondaryId) {
throw Error("No ID was provided");
}
let permission = userInfo.permissionList.filter(p => p.role === role);
if(primaryId) {
permission = permission.filter(p => p.primaryId === primaryId);
}
if(secondaryId) {
permission = permission.filter(p => p.secondaryId === secondaryId);
}
return permission[0];
function getUserPermission(primaryId, secondaryId, role) {
return userInfo.permissionList.find(permission =>
permission.role === role
&& (!!primaryId || !!secondaryId)// at least one is populated
&& (!primaryId || primaryId === permission.primaryId)// if primary is populated, it needs to match
&& (!secondaryId || secondaryId === permission.secondaryId)// if secondary is populated, it needs to match
);
}
This is slightly different from your code in that if there is no match, it returns null, whereas yours throws an exception. If you mean to throw, you can always put the result in a variable and check for null.
What about this refactoring :
function getUserPermission(primaryId = undefined,secondaryId = undefined,role) {
primaryId === undefined && secondaryId === undefined && throw new Error("Ids are required");
const userPermission = userInfo.permissionList
.filter((permission) => role && permission.role === role)
.filter((permission) => primaryId && permission.primaryId === primaryId)
.filter(
(permission) => secondaryId && permission.secondaryId === secondaryId
);
return userPermission[0];
}
Im just looking for a bit of advice regarding React.js filtering. I am currently filtering ‘peopleList’ by ‘employeeName’ and this is working fine, im getting back exactly what I expect.
But I wanted to also filter by the ‘employeeID’ at the same time i.e. check if ‘employeeName’ or ‘employeeID’ contain an indexOf.. Is this possible or would I need to set up two filters for 'employeeName’ and 'employeeID’?
let people= this.state.peopleList.filter(
(person) => {
return person.record.employeeName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
// return person.record.employeeID.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
}
);
If your condition is either one OR the other, you can use the || operator
const { search, peopleList } = this.state
const searchStr = search.toLowerCase()
const people = peopleList.filter((person) =>
person.record.employeeName.toLowerCase().indexOf(searchStr) !== -1 ||
person.record.employeeId.indexOf(searchStr) !== -1
)