I have a React component that's receiving a data object returned from an API response.
I'm trying to write a function that accepts a field from that data response, checks an element inside of that field and iterates over it checking each object inside the array for the value of a specific alert.
If a value for a specific alert is found I need to render an Icon for that alert.
The data object looks like this:
location: {
...,
details: {
summary: [
{
type: 'calling',
icon: 'phone'
},
{
type: 'power',
icon: 'electric'
},
{
type: 'water',
icon: 'water-icon'
},
]
}
}
And here's the section where I'm trying to conditionally render the icons (this was my first pass and rudimentary attempt):
<div>
{location.alertDetails && (
<IconBox title={`Alerts`}>
<IconSection>
{location.details.summary.includes(type === calling) &&
<CallIcon />
}
{location.details.summary.includes(type === power) &&
<ElectricIcon />
}
{location.details.summary.includes(type === water) &&
<WaterIcon />
}
</IconSection>
</IconBox>
)}
</div>
You may store within components state the array of fetched types:
const [types, setTypes] = useState(location.details.summary.map(({type}) => type))
With that, you may simply render (or not) your icons conditionally:
<div>
{location.alertDetails && (
<IconBox title={`Alerts`}>
<IconSection>
{types.includes('calling') && <CallIcon />}
{types.includes('power') && <ElectricIcon />}
{types.includes('water') && <WaterIcon />}
</IconSection>
</IconBox>
)}
</div>
Here's the demo (with all of your components rendered as a <div>'s, since I dont have those):
const { render } = ReactDOM,
{ useState } = React
const apiData = {location:{details:{summary:[{type:'calling',icon:'phone'},{type:'power',icon:'electric'},{type:'water',icon:'water-icon'},]}}}
const IconTray = ({data}) => {
const [types, setTypes] = useState(data.location.details.summary.map(({type}) => type))
return (
<div>
{data.location.details && (
<div>
<div>
{types.includes('calling') && <div>I am a CallIcon</div>}
{types.includes('power') && <div>I am an ElectronIcon</div>}
{types.includes('water') && <div>I am a WaterIcon</div>}
</div>
</div>
)}
</div>
)
}
render (
<IconTray data={apiData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
You can use a map to easily iterate over these -
<div>
{location.alertDetails && location.details.summary.map(item =>{ item.icon && return (
<IconBox title={`Alerts`}>
<IconSection>
{location.details.summary.includes(type === calling) &&
<CallIcon />
}
{location.details.summary.includes(type === power) &&
<ElectricIcon />
}
{location.details.summary.includes(type === water) &&
<WaterIcon />
}
</IconSection>
</IconBox>
)})}
</div>
Related
I tried implementing browser router, but to no success. i'm having trouble with useParams hook, and just the router in general. Looked through multiple posts and i just wasn't able to get it working. I'll post the most barebones code below, hoping someone knows the solution. I removed the traces of the router, since it didn't work.
App.js is currently empty:
const App=()=> {
return (
<Main/>
);
}
Main.jsx is my main element, where components change. There isn't a page change per se, everything is in the main element. values get passed through props into main and written into state, so the useEffect can change visibility of components based on what you chose, first category, then recipe.:
const Main =()=> {
const [showElement, setShowElement] = useState("category");
const [selectedCategory, setSelectedCategory] = useState();
const [selectedRecipe, setSelectedRecipe] = useState();
useEffect(()=> {
if (selectedRecipe) {
setShowElement("recipe")
} else if (selectedCategory) {
setShowElement("recipeSelection")
}
window.scrollTo(0, 0)
}, [selectedCategory][selectedRecipe]);
return (
<>
<Header />
<main className="main">
<div>
<div>
{showElement === "category" &&
<CategoryWindow
passSelectedCategory={setSelectedCategory}
/>
}
</div>
<div>
{showElement === "recipeSelection" &&
<RecipeSelection
value={selectedCategory}
passSelectedRecipe={setSelectedRecipe}
/>
}
</div>
<div>
{showElement === "recipe" &&
<RecipeWindow
value={selectedRecipe}
/>
}
</div>
</div>
</main>
</>
)
}
This is the recipe picker component. For example when i click on curry, i'd like the url to show /food/curry. None od the names are hardcoded, everything comes from a javascript object:
const RecipeSelection =(props)=> {
const recipies = Recipies.filter(x=>x.type === props.value);
return (
<div className="selection-div">
<div className="selection-inner">
{recipies.map(selection =>
<>
<img src={require(`../images/${selection.id}.png`)}
className="selection-single"
key={selection.id}
alt={"picture of " + selection.id}
onClick={()=> props.passSelectedRecipe(selection.id)}
>
</img>
<div className="container-h3"
onClick={()=> props.passSelectedRecipe(selection.id)}
>
<h3 className="selection-h3">{selection.name}</h3>
</div>
</>
)}
</div>
</div>
)
}
I made a custom file input in my app. It's working like a charm but when the file is uploaded, the custom file input is still showing the name of the file, which is a problem.
I tried to pass a state from parent component in order to reset the name displayed but, for some reason, the child prop does not update with the parent state and I don't know why.
Here's the custom file input :
export default function CustomInput({
disabler,
setUpperLevelFile,
previousName,
typeOfFiles,
lastInput,
reset,
}) {
const [fileUpload, setFileUpload] = useState(null);
useEffect(() => {
if (lastInput) {
setUpperLevelFile && setUpperLevelFile(fileUpload, lastInput);
} else {
setUpperLevelFile && setUpperLevelFile(fileUpload);
}
}, [fileUpload]);
useEffect(() => {
reset && setFileUpload(null);
console.log("custom input use effect : ", reset);
}, [reset]);
return (
<label className="customInputLabel">
<CustomButton
buttonInnerText="browse"
/>
<p>
{(fileUpload &&
`${fileUpload.name}, (${sumParser(fileUpload.size)})`) ||
(previousName && previousName) ||
"chose a file"}
</p>
<input
type="file"
name="realInput"
className="innerFileInput"
accept={typeOfFiles && typeOfFiles}
disabled={disabler && !disabler}
style={{ display: "none" }}
onChange={(e) => {
setFileUpload(e.target.files[0]);
}}
/>
</label>
);
}
And here is some of the parent code :
export default function ImportFiles(props){
...
const [resetInputs, setResetInputs] = useState(false);
const returningInputs = () => {
let stockInputs = [];
for (let i = 0; i < filesCounter; i++) {
stockInputs.push(
<CustomInput
key={`custom input ${i}`}
setUpperLevelFile={handlingInputChange}
lastInput={i === filesCounter - 1}
reset={resetInputs}
/>
);
}
setFilesInputs(stockInputs);
};
const handlingPostingFiles = () => {
postingFiles(uploadFiles, setUploadStatus);
setResetInputs(true);
};
useEffect(() => {
console.log("edit packages use effect : ", resetInputs);
}, [resetInputs]);
...
return(
...
{filesInputs}
...
)
The console.log in parent component shows that the state is updated but the one in CustomInput doesn't trigger after first render. So it's not updated.
After realizing the process - there should be a clean 🧼 🧽 phase:
so add this line after submitting form:
setFileUpload(null)
in the function handlingPostingFiles or postingFiles.
There is also option to hide this section with that condition: !resetInputs:
<p>
{((fileUpload && !resetInputs) &&
`${fileUpload.name}, (${sumParser(fileUpload.size)})`) ||
(previousName && previousName) ||
"chose a file"}
</p>
I am mapping data within the returned render, but I am wanting to then perform a conditional on the data that has been mapped... Is it possible to do something like this?
export default function App() {
const prod = [
{'name': '1'},
{'name': '2'},
{'name': '3'}
];
return (
<div className="App">
{prod && (
<div>
{prod.map(p => (
// This is the point I get an error
{p.name === '1' && (
<h1>This is 1</h1>
)}
))}
</div>
)}
</div>
);
}
Currently I am getting an error:
Unexpected token, expected "," (16:14)
14 | <div>
15 | {prod.map(p => (
> 16 | {p.name === '1' && (
| ^
Arrow functions with the format x => () return the content of the brackets.
So you are returning what the compiler thinks is an object, as it's wrapped in curly brackets x => ({ ... }) You need to remove the braces:
prod.map(p => (
p.name === '1' && <h1>This is 1</h1>
)
Or explicitly return the value:
prod.map(p => {
return p.name === '1' && <h1>This is 1</h1>
}
If you want to map the data for any specific index then you can check this.
<div>
{prod.map(p => p.name === '1' && <h1>Conditionally Mapped</h1> || <h1>Normally mapped</h1>}
</div>
// similar to the previous one
<div>
{prod.map(p => {
if(p.name === '1') return <h1>Conditionally Mapped</h1>
else return <h1>Normally mapped</h1>
}
</div>
I'm trying to build a Flip card with ReactJS, that have inside 2 others components which are : Frontside and BackSide. These components should have children such as BackgroundCard or Sectioned Card. When I test the component I'm not getting anything on the screen and there is no errors in the console!
FlipContent.js
function FlipContent() {
const [setFront, setFrontState] = useState(true);
const [setBack, setBackState] = useState(false);
const [setFlipped, setFlippedState] = useState("");
function FlippingCard() {
setFrontState(setFront === true ? false : true);
setBackState(setBack === false ? true : false);
setFlippedState(setFlipped === "" ? "flipped" : "");
}
return (
<div className={`flip-content ${setFlipped}`} onClick={FlippingCard} >
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{setFront ? <FrontSide></FrontSide> : null}
{setBack ? <BackSide> </BackSide> : null}
</div>
</div>
);
}
And For the FrontSide/BackSide same this as this code
function FrontSide({ children }) {
return (
<div className="flip-content-front">
<div style={{ cursor: "pointer" }}>
{children}
</div>
</div>
);
}
and here how I'm trying to preview the component
function FlipPreview() {
return (
<Column>
<Row className={css(styles.title)} wrap flexGrow={1} horizontal="space-between" breakpoints={{ 768: 'column' }}>
Accordion <br></br>
</Row>
<FlipContent>
<FrontSide>
<CardBackgroundComponent title="Testing" image={image}></CardBackgroundComponent>
</FrontSide>
<BackSide>
<SectionedCardComponent
title="Notarum Black"
content="Powerful and reliable, this 15” HD laptop will not let you down. 256GB SSD storage, latest gen."
link=""
linkDescription="Add To Cart"
/>
</BackSide>
</FlipContent>
</Column>
);
}
I think you have not inserted something inside both component FrontSide, BackSide
<div className={`flip-content ${setFlipped}`} onClick={FlippingCard} >
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{setFront ? <FrontSide> It's front side </FrontSide> : null}
{setBack ? <BackSide> It's back-side </BackSide> : null}
</div>
</div>
So in your component you are not rendering children. So you need to update two things.
1) Taking the props in the FlipContent component as shown below
function FlipContent(props)
2) Use the props when rendering inside the component as shown below
{setFront ? <FrontSide>{props.children}</FrontSide> : null}
{setBack ? <BackSide>{props.children} </BackSide> : null}
the problem is in second step is it will load all the props of children , so you need to render only the specific component. See the below one
Update
There are multiple ways to solve this one will list one by one
solution one
By using the name prop of the children
function FlipContent(props) {
const [view, setView] = useState("FrontSide");
function FlippingCard() {
setView(view === "FrontSide" ? "BackSide" : "FrontSide");
}
const component = React.Children.map(props.children, child => {
if (view === child.type.name) {
return child;
}
});
return (
<div className={`flip-content`} onClick={FlippingCard}>
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{component}
</div>
</div>
);
}
Working codesandbox
Solution Two
Instead of adding statically the names can be driven from the prop, this can't handle same component multiple times
function FlipContent(props) {
const [view, setView] = useState(props.children[0].type.name);
const ref = useRef(0);
function FlippingCard() {
if (props.children.length - 1 === ref.current) {
ref.current = 0;
setView(props.children[0].type.name);
return;
}
setView(props.children[ref.current + 1].type.name);
ref.current += 1;
}
let component = <span />;
React.Children.forEach(props.children, child => {
if (view === child.type.name) {
component = child;
return;
}
});
return (
<div className={`flip-content`} onClick={FlippingCard}>
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{component}
</div>
</div>
);
}
Working codesandbox
Solution three
Rendering multiple components and in the same wrapper itself.
function FlipContent(props) {
const [component, setComponent] = useState(props.children[0]);
const ref = useRef(0);
function FlippingCard() {
if (props.children.length - 1 === ref.current) {
ref.current = 0;
setComponent(props.children[0]);
return;
}
setComponent(props.children[ref.current + 1]);
ref.current += 1;
}
return (
<div className={`flip-content`} onClick={FlippingCard}>
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{component}
</div>
</div>
);
}
Working codesandbox
I feel solution three is the simplest one and you have the scalable way.
I am working on a project and i want to display a hidden <div> below another <div> element using an event handler but when i click the icon that is meant to display the div, the whole page becomes blank
This is image I want:
This is what i get
I have tried to check through the internet for some places where i could get the solution. Well i found something similar to what i had done but the error still happens for me.
class PostItTeaser extends Component {
state = {
postIt: false,
moreIt: false,
}
togglePostIt = e => {
e ? e.preventDefault() : null
this.setState({ postIt: !this.state.postIt })
}
_toggle = e => {
e ? e.preventDefault() : null
this.setState({
moreIt: !this.state.moreIt,
})
}
Child = () => <div className="modal">Hello, World!</div>
render() {
let { postIt } = this.state
let { moreIt } = this.state
let {
type,
group,
disabled,
session: { id, username },
} = this.props
return (
<div>
<div
className="post_it inst"
style={{ marginBottom: type == 'group' && 10 }}
>
<img src={`/users/${id}/avatar.jpg`} alt="Your avatar" />
<div className="post_teaser">
<span
className="p_whats_new"
onClick={disabled ? null : this.togglePostIt}
>
What's new with you, #{username}? #cool
</span>
<span className="m_m_exp" data-tip="More" onClick={this._toggle}>
<MaterialIcon icon="expand_more" />
</span>
</div>
</div>
{moreIt && <Child />}
{postIt && (
<PostIt back={this.togglePostIt} type={type} group={group} />
)}
</div>
)
}
}
From skimming through the code I believe you need to bind the scope, since the function you're calling is using this.setState, it needs this to be the react component, not the event you're listening to:
onClick={this._toggle.bind(this)}
You can also bind the functions scope in the constructor. Or, a less memory performant & ugly way:
onClick={() => { this._toggle(); } }