I'm making a simple React app where I am getting a list of cars through useEffect hook and then mapping through each one to display them on the screen. Then, I have one button whose purpose would be to sort the cars based on their horsepower. So, when clicking on that button the user would see only the cars whose horsepower is greater than for example 700.
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [items, setItems] = useState([])
const [yo, setYo] = useState(false)
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data)
})
}, [])
function reverse(){
items.reverse();
setItems([...items])
}
function sortHigherHP(){
let cars = items.filter(item => {
return item.horsepower >= 700;
}).map(i => {
return(
<div key={Math.floor(Math.random()*10000)} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Make: {i.make}</p>
<p>Horsepower: {i.horsepower} HS</p>
</div>
)
});
setItems(cars)
}
function hey(){
setYo(!yo)
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hey}>Sort</h3>
<div className={yo ? "show" : "hide"}>Sorting options
<button onClick={reverse}>Reverse Order</button>
<button onClick={sortHigherHP}>Higher Horsepower</button>
</div>
</div>
{items.slice(0, 50).map(a => {
return (
<div key={Math.floor(Math.random()*10000)} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Make: {a.make}</p>
<p>Horsepower: {a.horsepower} HS</p>
</div>
)
})}
</div>
);
}
The reverse event handler is obviously working since the reverse method just reverses the array, without changing the original array. But this filter method returns a new array and I have problems here on how to actually display data on the screen. Could the problem be in the .map method that comes right after .filter()?
Could the problem be in the .map method that comes right after .filter()?
Yes this is indeed the problem:
function sortHigherHP() {
let cars = items
.filter((item) => {
return item.horsepower >= 700;
})
.map((i) => {
return (
<div
key={Math.floor(Math.random() * 10000)}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px",
}}
>
<p>Make: {i.make}</p>
<p>Horsepower: {i.horsepower} HS</p>
</div>
);
});
setItems(cars);
}
You should remove the map in its entirety here:
function sortHigherHP() {
let cars = items.filter((item) => {
return item.horsepower >= 700;
});
setItems(cars);
}
You already have a map call that handles displaying your items:
{
items.slice(0, 50).map((a) => {
return (
<div
key={Math.floor(Math.random() * 10000)}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px",
}}
>
<p>Make: {a.make}</p>
<p>Horsepower: {a.horsepower} HS</p>
</div>
);
});
}
Because of the extra map call you're actually setting the items state to jsx instead of an array of objects, which makes the code above not work.
Codesandbox example
Related
i have a scenario where based on a number(say numberOfFlags) i want to render numberOfFlags times an radio button group.Each group has two radio buttons approve and reject as per screenshot attached how to get values of all inputs when they change?
An lastly i have to store result of all radio buttons (approve/reject) in an array and send to API
You need to use two parameters on onChange function. One is for current index and another is for Approve/Reject.
Like below code snippet
onchange = handleOnChage(index, isApproveClicked)
You can achive this in many different ways, but I would probably simple create a state with an array of values in the parent component and pass it to each and every item to toggle its own state depending action.
App.js
export function App() {
const [list, setList] = useState([false, false, false]);
const updateItem = (value, index) => {
let copyList = [...list];
copyList[index] = !value;
setList(copyList);
};
console.log(list)
return (
<div className="App">
{list && (
<>
{list.map((value, index) => (
<Item values={[value, index]} updateItem={updateItem} key={index+"_check"} />
))}
</>
)}
</div>
);
}
Item.js
export default function Item({ values, updateItem }) {
return (
<>
<input
onChange={() => updateItem(values[0], values[1])}
type="checkbox"
checked={values[0] ? "checked" : ""}
/>
</>
);
}
Presented below is one possible way to achieve the desired objective.
Code Snippet
const {useState} = React;
const Thingy = ({...props}) => {
// num-of-flags is obtained from props
const { numOfFlags: nf} = props || {};
// if it is null or not above 0, return "invalid" message to parent
if (!(nf && nf > 0)) return "invalid num-of-flags";
// state variable to store approve/reject status
const [statusObj, setStatusObj] = useState({});
// JSX to render the UI & handle events
return (
<div>
{([...Array(nf).keys()].map(grpIdx => (
<div className="grpBox">
Group num {grpIdx+1} <br/>
<input type='radio' name={grpIdx} id={grpIdx} value={'approve'}
onChange={() => setStatusObj({
...statusObj, [grpIdx]: 'approve',
})}
/>
<label for={grpIdx}>Approve</label>{" "}
<input type='radio' name={grpIdx} id={grpIdx} value={'reject'}
onChange={() => setStatusObj({
...statusObj, [grpIdx]: 'reject',
})}
/>
<label for={grpIdx}>Reject</label>
</div>
)))}<br/>
<button
onClick={() => {
// make API call here
// for verification, displaying an alert-message showing the status
const displayMsg = [...Array(nf).keys()].map(
gi => "Group num " + (+gi+1) + " : " + (gi in statusObj ? statusObj[gi] : '__')
).join(', ');
alert(`Approve-Reject status is: ${JSON.stringify(displayMsg)}`);
}}
>
Submit
</button>
</div>
);
};
ReactDOM.render(
<div>
<div className="demoTitle">DEMO</div>
<Thingy numOfFlags={5} />
</div>,
document.getElementById("rd")
);
.demoTitle {
margin-bottom: 5px;
font-size: 20px;
text-decoration: underline;
}
.grpBox {
margin: 5px; padding: 10px;
border: 2px solid purple;
width: max-content;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="rd" />
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
I am having difficulty using the Autocomplete component in #react-google-maps/api. Autocomplete itself works and will display options. Clicking an option populates the input box but I only return 'undefined' from the Places Api. I have avoided using something like PlacesAutocomplete because I prefer the built in result rendering from Google.
Edited: App.js file should be minimally functional with use of an appropriate API key.
import React, { useState, useRef} from 'react';
import { Autocomplete, useLoadScript } from '#react-google-maps/api';
const placesLibrary = ['places']
function App() {
const [searchResult, setSearchResult] = useState('')
const autocompleteRef = useRef();
const { isLoaded } = useLoadScript({
googleMapsApiKey: 'GOOGLE_API_KEY',
libraries: placesLibrary
});
const onLoad = () => {
const autocomplete = autocompleteRef.current
}
const onPlaceChanged = (place) => {
setSearchResult(place)
console.log(searchResult)
}
if(!isLoaded) {
return <div>Loading...</div>
};
return (
<div className="App">
<div id="searchColumn">
<h2>Tide Forecast Options</h2>
<Autocomplete
onPlaceChanged={(place) => onPlaceChanged(place)}
onLoad = {onLoad}>
<input
type="text"
placeholder="Search for Tide Information"
style={{
boxSizing: `border-box`,
border: `1px solid transparent`,
width: `240px`,
height: `32px`,
padding: `0 12px`,
borderRadius: `3px`,
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.3)`,
fontSize: `14px`,
outline: `none`,
textOverflow: `ellipses`,
}}
/>
</Autocomplete>
</div>
</div>
)}
export default App;
I was able to reproduce your code and found some unnecessary stuff you have included like the useRef, autocompleteRef but you could just clarify things in the comment. And also I did not find the way you used the onPlaceChanged and onLoad events on the #react-google-maps/api Library Docs. So I just based what I did there and removed some stuff you put.
Here's what the code looks like:
first is that onLoad() function for the <Autocomplete /> already had a built in parameter called autocomplete that returns the result of the autocomplete by using getPlace(). ref here. So what I did here is I changed the value of searchResult state with what the result of the autocomplete returns using setSearchResult(autocomplete) inside the onload() function.
function onLoad(autocomplete) {
setSearchResult(autocomplete);
}
Then on the onPlaceChanged() function, I used the new searchResult value to get the place details.
function onPlaceChanged() {
if (searchResult != null) {
//variable to store the result
const place = searchResult.getPlace();
//variable to store the name from place details result
const name = place.name;
//variable to store the status from place details result
const status = place.business_status;
//variable to store the formatted address from place details result
const formattedAddress = place.formatted_address;
// console.log(place);
//console log all results
console.log(`Name: ${name}`);
console.log(`Business Status: ${status}`);
console.log(`Formatted Address: ${formattedAddress}`);
} else {
alert("Please enter text");
}
}
You can remove the comment from console.log(place) if you want to check what's inside the variable place result and use that as a reference if you want to get other details from the autocomplete result.
Then I just changed your callbacks inside your onLoad and onPlaceChanged props to simply call on to the functions above.
return (
<div className="App">
<div id="searchColumn">
<h2>Tide Forecast Options</h2>
<Autocomplete onPlaceChanged={onPlaceChanged} onLoad={onLoad}>
<input
type="text"
placeholder="Search for Tide Information"
style={{
boxSizing: `border-box`,
border: `1px solid transparent`,
width: `240px`,
height: `32px`,
padding: `0 12px`,
borderRadius: `3px`,
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.3)`,
fontSize: `14px`,
outline: `none`,
textOverflow: `ellipses`
}}
/>
</Autocomplete>
</div>
</div>
);
With this, things seems to be working fine.
Here's a code sandbox if you want to try this out (Just use your own API key): https://codesandbox.io/s/react-google-maps-api-multiple-markers-infowindow-forked-dpueyy?file=/src/App.js
Hope this helps.
I've got this code from a tutorial video , but first of all I didn't get the purpose of clk function and how it is related to h1 tag and that trinary operator.
second , how can I use normal if-else instead of ternary operator and not only for adding class but changing its style too.
import React, {useState} from 'react';
import "./App.css";
function App(){
let [isRed,setRed] = useState(false);
function clk(){
setRed(true);
}
return(
<div>
<h1 className={isRed?"red":""}>Change My Color</h1>
<button onClick={clk}>ClickHere</button>
</div>
);
}
export default App;
You can do that by applying this
<h1 className={`${isRed ? "red" : ""}`}>Change My Color</h1>
Or
{
isRed ? (
<h1 className={"red"}>Change My Color</h1>
) : (
<h1 className={"other"}>Change My Color</h1>
)
}
Enclose your elements inside of a {} makes it interpreted as js code
`
{if(isRed) return < h1>...< /h1> else return < h1>...< /h1>}
`
should work.. Maybe you can use the same inside the class attribute, but it will be hard to read.
As for the click function, it is setting the value of isRed to true. This will create the reactive change to your style.
You can bind a memo to your <h1> element that gets calculated when that particular state changes. Please note that JSX is not JavaScript. It may appear similar, and you may be able to use 99% of the syntax, but there are differences.
You can learn more about memoized values here: React / Docs / Hooks / useMemo
const { useMemo, useState } = React;
const App = () => {
let [isRed, setRed] = useState(false);
let [isBlue, setBlue] = useState(false);
const onClickRed = (e) => setRed(!isRed); // onclick callback
const onClickBlue = (e) => setBlue(!isBlue); // onclick callback
const headerPropsRed = useMemo(() => {
console.log('Red updated!');
let props = {};
if (isRed) {
props = {
...props,
className: 'red',
style: {
...props.style,
fontStyle: 'italic'
}
}
}
return props;
}, [ isRed ]);
const headerPropsBlue = useMemo(() => {
console.log('Blue updated!');
let props = {};
if (isBlue) {
props = {
...props,
className: 'blue',
style: {
...props.style,
fontStyle: 'italic'
}
}
}
return props;
}, [ isBlue ]);
return (
<div>
<h1 {...headerPropsRed}>Change My Color</h1>
<button onClick={onClickRed}>Click Here</button>
<h1 {...headerPropsBlue}>Change My Color</h1>
<button onClick={onClickBlue}>Click Here</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('react'));
.as-console-wrapper { max-height: 3em !important; }
h1 { font-size: 1em; }
.red { background: red; }
.blue { background: blue; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can use ternary operator working like this:
if(condition) ? "True part here" : "else part here (false part)"
Use case example :
const id = 1;
if(id === 1) ? "You are on right place" : "Sorry please check"
You can't use if-else in inline jsx but there exists some workarounds and you can choose whichever you want.
variant 1:
if(isRed){
const header = <h1 className='red' style={{ backgroundColor: 'red' ... }}>Change My Color</h1>
} else {
const header = <h1 style={{ backgroundColor: 'green' ... }}>Change My Color</h1>
}
return (
<div>
{header}
<button onClick={clk}>ClickHere</button>
</div>
);
variant 2: (but still ternary opeartor used)
if(isRed){
} else {
const header =
}
return (
<div>
{isRed ? (
<h1 className='red' style={{ backgroundColor: 'red' ... }}>Change My Color</h1>
) : (
<h1 style={{ backgroundColor: 'green' ... }}>Change My Color</h1>
)}
<button onClick={clk}>ClickHere</button>
</div>
);
There is no other way to replace ternary operator with if-else statement
Im trying to make a button that changes state depending on the value of a property on an object.
here is the styled component
const Btn = styled.button`
border-radius: ${props => props.theme.radius};
padding:5px 10px;
background-color:${ sub.saved ? 'green' : 'red'}
&:hover{
cursor:pointer;
}
`
and here is the component that it is being used inside of
const DisplaySubs = ({ queryResults, setSavedFunction, saved }) => {
return (
<>
<Total>{queryResults.length} results found </Total>
<UL>
{queryResults.map((sub, index) => {
return (
<LI onClick={() => {
window.open(`https://www.reddit.com/${sub.title}`)
}
}>
<H4>{sub.title}</H4>
<P>{sub.description}</P>
<Btn onClick={(e) => {
e.stopPropagation()
sub.saved = !sub.saved
}}>Save</Btn>
</LI>
)
})}
</UL >
</>
)
}
Instead of creating a component like this, you can write a simple functional component like
const Btn = ( props ) => {
<div style={{
borderRadius: {props.theme.radius},
padding:"5px 10px",
backgroundColor: {props.sub.saved ? 'green' : 'red'}
}}>
{props.children}
</div>
}
You can call this in your Main Container by
<Btn props={props}>Button<Btn>
For a javascript project:
// you "add" the interface to button, so you can access this custom properties
export const Btn = styled.button`
padding: 5px 10px;
background-color: ${(props) => (props.isSaved ? "green" : "red")};
&:hover {
cursor: pointer;
}
`;
For a typescript project, you can use interface to set custom properties, like:
// Create an interface defining custom properties to button
interface BtnProps {
isSaved: boolean;
}
// you "add" the interface to button, so you can access this custom properties
export const Btn = styled.button<BtnProps>`
padding: 5px 10px;
background-color: ${(props) => (props.isSaved ? "green" : "red")};
&:hover {
cursor: pointer;
}
`;
and your button:
// set isSaved property to button component,
// so you will have access to it on styled-components.
<Btn isSaved={sub.saved}
onClick={(e) => {
e.stopPropagation()
sub.saved = !sub.saved
}}>Save</Btn>
Recently i had to write something in React, that required me to render different components in a modal. Being that i didn't want to repeat my self with different modals in the same parent component, i decided to reuse it, but wasn't sure how to do it "correctly". This is what i have done:
renderModalTitle = () => {
return this.state.currentModalAction === 'delete' ? `Are you sure you want to delete book "${this.state.currentBook.title}"?`
: this.state.currentBook ? `Edit book "${this.state.currentBook.title}"`
: 'Create new book'
}
renderModalBody = () => {
return this.state.currentModalAction === 'edit' ||
this.state.currentModalAction === 'new' ?
<BookForm book={this.state.currentBook} onSave={this.onBookSave}>
</BookForm>
: <ConfirmDelete onBookDeleteCancel={this.toggle} onBookDelete={()=>
{this.onBookDelete(this.state.currentBook.id)}} data=
{this.state.currentBook}></ConfirmDelete>
}
I know it's a bit hard to read, because the indentation in the code snippet is slightly messed up. But as you can see, i just have functions that return the relevant jsx, according to the "currentModalAction". Then in the modal:
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className}>
<ModalHeader className="color_main" toggle={this.toggle}>{this.renderModalTitle()}</ModalHeader>
<ModalBody>
{this.renderModalBody()}
</ModalBody>
<ModalFooter>
<Button className="square" color="default" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
So yes, i've achieved "reusability" of the modal, and didn't repeat my self, but it seems to me, that this might do more harm than good...Not vert readable, not very clear.
Is there some common approach towards this issue? Notice that i didn't use react-modal or something like that. It's just reactstrap.
I made some code representing your case.
Your can use a function prop like renderBodyComponent that will render your modal body.
class FlexibleModal extends React.Component {
render() {
if (!this.props.isOpen) {
return null;
}
return (
<div className="flexible-modal">
<div className="flexible-modal-header">
{this.props.headerTitle}
</div>
<div className="flexible-modal-body">
{this.props.renderBodyComponent()}
</div>
</div>
);
}
};
const BodyCase1 = () => (
<div>
Modal Body Case 1
</div>
);
const BodyCase2 = () => (
<div>
Modal Body Case 2
</div>
);
class App extends React.Component {
state = {
showModal: false,
case: 1,
}
toggleModal = () => {
this.setState({ showModal: !this.state.showModal });
}
toggleCase = () => {
const nextCase = this.state.case === 1 ? 2 : 1;
this.setState({ case: nextCase });
}
render() {
return (
<div>
<button
onClick={() => this.toggleModal()}
>
Toggle modal
</button>
<button
onClick={() => this.toggleCase()}
>
Toggle next case
</button>
<FlexibleModal
isOpen={this.state.showModal}
headerTitle="Customizable Modal Header Title"
renderBodyComponent={
this.state.case === 1
? () => (<BodyCase1 />)
: () => (<BodyCase2 />)
}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react'));
.flexible-modal {
margin: 15px;
border: 1px solid #ddd;
background: #fff;
}
.flexible-modal-header {
border-bottom: 1px solid #ddd;
padding: 10px;
background: #e7e7e7;
}
.flexible-modal-body {
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>