Refactor dropdown component in React - javascript

I have to use multiple dropdowns from semantic-ui-react in my project. They need to have different props. It looks like this
<div className="wrapper">
<img className="icon" src={iconA} alt="iconA"></img>
<h1>A</h1>
<Dropdown
className="dropdown"
search
selection
options={optionsA}
placeholder="A"
defaultValue="A"
onChange={handleAChange}
/>
</div>
<div className="wrapper">
<img className="icon" src={iconB} alt="iconB"></img>
<h1>B</h1>
<Dropdown
className="dropdown"
search
selection
options={optionsB}
placeholder="B"
defaultValue="B"
onChange={handleBChange}
/>
</div>
I want to refactor this and create a single component for this by pasing different props. Please guide me on how this can be refactored in the best way possible.

First, create your custom dropDown component and extract props using object destructuring, you can give deafult values to props there itself, but better use PropTypes for that.
const CustomDropDown = (props) => {
const {
className,
search,
selection,
options,
placeholder,
defaultValue,
onChange
} = props;
return (
<div className="wrapper">
<img className="icon" src={iconA} alt="iconA"></img>
<h1>A</h1>
<Dropdown
className={classname}
search={search}
selection={selection}
options={optionsA}
placeholder={placeholder}
defaultValue={defaultValue}
onChange={onChange}
/>
</div>
)
}
Now, call the component like this,
<CustomDropDown
className="dropdown"
search
selection
options={optionsA}
placeholder="A"
defaultValue="A"
onChange={handleAChange}
/>

You can do it as follows:
const DropDownWraper = ({
header,
options,
onChange,
iconProps,
placeholde,
defaultValue
}) =>
<div className="wrapper">
<img
className="icon"
src={ iconProps.src }
alt={ iconProps.alt } />
<h1>{ header }</h1>
<Dropdown
search
selection
options={ options }
className="dropdown"
onChange={ onChange }
placeholder={ placeholde }
defaultValue={ defaultValue } />
</div>

Related

I am working on the currency converter webapp with the fixer api

I have successfully fetched all the values. But i want to populate all of the values {currencyOptions} into the "select" option which is in the another component. How can I do that .
const [currencyOptions, setCurrencyOptions] = useState([]);
console.log(currencyOptions);
<div className="App">
<h1>Convert Currency</h1>
<CurrencyRow currencyOptions={currencyOptions} />
<SiConvertio className="convert" />
<CurrencyRow currencyOptions={currencyOptions} />
</div>
Here is the currency option component which I
const CurrencyRow = () => {
return (
//
<Container>
<Row>
<Input type="number" className="input" />
<select>
<option value="abx">abc</option>
</select>
</Row>
</Container>
);
};
You want to populate your dropdown with your currencyOptions state. Assuming your state has all the items and it's a list you want to return an option element for each item when rendering your component, something like:
currencyOptions.map((currencyOption, index) => (
<option key={index} value={currencyOption}>{currencyOption}</option>
));
Also try to use an id as the element key instead of the index if you have got one.

How to render a list of icons, always incrementing one?

I'm still a beginner in JavaScript and ReactJS. I'm doing a project to improve my knowledge.
I'm rendering a list where the user can filter results by star, as shown in the image below:
The way I created this list is not at all smart:
import React from "react";
import "./styles.css";
import Checkbox from "#material-ui/core/Checkbox";
import StarBorderIcon from "#material-ui/icons/StarBorder";
import { useStyles } from "./styles";
export default function App() {
const classes = useStyles();
return (
<div className="App">
<h1>Start</h1>
<div className={classes.root}>
<Checkbox />
<StarBorderIcon />
</div>
<div className={classes.root}>
<Checkbox />
<StarBorderIcon />
<StarBorderIcon />
</div>
<div className={classes.root}>
<Checkbox />
<StarBorderIcon />
<StarBorderIcon />
<StarBorderIcon />
</div>
<div className={classes.root}>
<Checkbox />
<StarBorderIcon />
<StarBorderIcon />
<StarBorderIcon />
<StarBorderIcon />
</div>
<div className={classes.root}>
<Checkbox />
<StarBorderIcon />
<StarBorderIcon />
<StarBorderIcon />
<StarBorderIcon />
<StarBorderIcon />
</div>
</div>
);
}
Can you tell me how to render this list, up to a total of 5 stars (as shown in the image), always adding one star to my list?
I put my code into codesandbox
Thank you very much in advance.
You can create and map over an array of specified length in order to create the individual sections and stars.
I'm using new Array(number).fill(null) since I want to create an array of a certain length and map over it. The .fill(null) is to get rid of the special empty values in an array constructed like this so that they are mapable.
You could have the logic all in one place, or you could convert pieces of the logic to separate components to simplify your main component.
Essentially in this we have 2 nested loops to render the checkboxes and the appropriate stars. The same is true whether you move the sub loop into it's own component (the Stars component in this example).
import React from "react";
import "./styles.css";
import Checkbox from "#material-ui/core/Checkbox";
import StarBorderIcon from "#material-ui/icons/StarBorder";
import { useStyles } from "./styles";
const array = new Array(5).fill(null);
export default function App() {
const classes = useStyles();
return (
<div className="App">
<h1>Start</h1>
{array.map((_val, idx) => (
<div key={idx} className={classes.root}>
<Checkbox />
{new Array(idx + 1).fill(null).map((_val, idx2) => (
<StarBorderIcon key={idx2} />
))}
</div>
))}
{/* Move some logic to a separate component */}
{array.map((_val, idx) => (
<div key={idx} className={classes.root}>
<Checkbox />
<Stars number={idx + 1} />
</div>
))}
</div>
);
}
function Stars({ number }) {
return new Array(number)
.fill(null)
.map((_val, idx) => <StarBorderIcon key={idx} />);
}
https://codesandbox.io/s/render-stars-icon-forked-wxtx4?file=/src/App.js

Problem on React, onClick take the last var and note the choosen one

const CharacterList = () => {
const [change, setChange] = React.useState(false);
const QuickSilver= () => setChange(!change);
const SuperMan= () => setChange(!change);
return (
<div id="character" className="list">
<img id="icons" className="one" src={QuickSilverIc} onClick={QuickSilver} />
{change && (
<img className="card" src={QuickSilverStat} />
)}
<img id="icons" className="two" src={SuperManIc} onClick={SuperMan} />
{change && (
<img className="card" src={SuperManStat} />
)}
So, when i click on the QuickSilver img that bring me the img with src={SuperManStat} and not {QuickSilverStat}
If anyone can help me it will be very nice !
ps: yeah i've cut the react components to show you only what i want ofc ive done averything correctly, like import export etc...
what you need to do is create to different useStates one for quicksilver and the other one for superman, or create an object with this shape
const [hero, SetHero] = useState({superman: false, quicksilver:false});
and then in your onClickhandler you can pass the name and update the state to right hero
{change ? (
<>
<img id="icons" className="one" src={QuickSilverIc} onClick={QuickSilver} />
<img className="card" src={QuickSilverStat} />
</>
) : (
<>
<img id="icons" className="two" src={SuperManIc} onClick={SuperMan} />
<img className="card" src={SuperManStat} />
</>
)}
As I understand, you want to show only one of them. To achieve this, you should wrap both images of each one together, and show only one of them on each change value (true or false):
{change && (
<div>
<img id="icons" className="one" src={QuickSilverIc} onClick={QuickSilver} />
<img className="card" src={QuickSilverStat} />
</div>
)}
{!change && (
<div>
<img id="icons" className="two" src={SuperManIc} onClick={SuperMan} />
<img className="card" src={SuperManStat} />
</div>
)}

Is passing a piece of data from child component to parent component an anti pattern in react?

consider the following example...
I have a component called ChemVisualisation.jsx it is as follows
const ChemVisualization = props => {
const { getChemists, chemists } = props;
useEffect(() => {
getChemists();
}, [getChemists]);
// useState hook is used here
// handleChange sets the name property
// i check for chemists based on name property here and pass it as props to
// SearchableDropdown component where it renders the list
const getChemistId = id => {
console.log(id);
// showing undefined
};
return(
<SearchableDropdown
getName={ChemistAnalytics}
onChange={e => handleChange(e)}
name="name"
value={name}
onSubmit={e => handleSubmit(e)}
entities={result}
handleClick={getChemistId}
/>
);
}
SearchableDropdown.jsx
const SearchableDropdown = props => {
// destructure props here
return(
<div className="control has-icons-left w-3/5 ">
<input
type="text"
placeholder="search"
value={value}
name={name}
onChange={onChange}
className="input"
autoComplete="off"
/>
<span className="icon is-left">
<FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>
</span>
</div>
{entities && (
<div className="dropdown">
<div className="dropdown-content">
{entities.map(r => (
<div
className="dropdown-item text-xl hover:bg-gray-400 w-full"
key={r._id}
onClick={r => handleClick(r._id)}
>
{r.chem_name}
</div>
))}
</div>
</div>
)}
);
}
When I click the drop down item, I'm not getting the id in its parent component.
My question is how do I get the id of the drop down item that I clicked?
Is passing the data from child component to its parent an anti-pattern?
It's normal for child components to call functions their parents provide to them in response to events. The parent then rerenders as necessary with the new information. This keeps the state information fairly "high." More in Lifting State Up in the documentation.
In your example with
<div className="dropdown-content">
{entities.map(r => (
<div
className="dropdown-item text-xl hover:bg-gray-400 w-full"
key={r._id}
onClick={r => handleClick(r._id)}
>
{r.chem_name}
</div>
))}
</div>
the problem is that you're shadowing r (the parameter of the map callback) with a different r (the parameter of the onClick). The first argument the click handler is called with is an event object. If you don't want it, just don't accept the parameter:
<div className="dropdown-content">
{entities.map(r => (
<div
className="dropdown-item text-xl hover:bg-gray-400 w-full"
key={r._id}
onClick={() => handleClick(r._id)}
>
{r.chem_name}
</div>
))}
</div>
The only change there is replacing r with () in onClick={() => handleClick(r._id)}.
onClick={e => handleClick(r._id)}
Use e instead of r.

show last searched items below search form

I have a search form which is developed using redux form. I have used router to route to the form. After i submit the data from search form and revert back to the same form, i want to show a list of data that had been searched before whenever user clicks on search places input box. How can i do so?
Like in the image
Here is my code
const Banner = (props) => (
<Router>
<div className="container banner">
<ServiceType />
<div className="row">
<Match exactly pattern="/" location={props.location} component={Apartamentos} />
<Match pattern="/apartamentos" component={Apartamentos} />
<Match pattern="/coche" component={Coche} />
<Match pattern="/experiencias" component={Experiencias} />
</div>
</div>
</Router>
);
const renderGeoSuggestField = ({
input,
location
}) => (
<Geosuggest
fixtures={fixtures}
initialValue={input.value.label}
inputClassName="form-control destino"
onChange={(value) => input.onChange(value)}
onSuggestSelect={(value) => input.onChange(value)}
radius="20"
/>
);
const renderDateRangePicker = ({
input,
focusedInput,
onFocusChange,
}) => (
<DateRangePicker
onDatesChange={(start, end) => input.onChange(start, end)}
startDate={(input.value && input.value.startDate) || null}
endDate={(input.value && input.value.endDate) || null}
minimumNights={0}
/>
);
class ServiceType extends Component {
render() {
return(
div className="col-xs-12 col-sm-12 col-md-4 serviceImg">
<Link to="/apartamentos">
<img
src={imageUrl}
alt="apartamentos"
className="img-responsive"
/>
<h4>APARTAMENTOS</h4>
</Link>
</div>
);
}
}
class Apartamentos extends Component {
render() {
const { focusedInput } = this.state;
return (
<div className="form-box text-center">
<div className="container">
<form className="form-inline">
<div className="form-group">
<Field
name='geoSuggest'
component={renderGeoSuggestField}
onChange={(value) => this.onChange(value)}
onSuggestSelect={(suggest) => this.onSuggestSelect(suggest)}
location={new google.maps.LatLng(53.558572, 9.9278215)}
/>
</div>
<div className="form-group">
<Field
name="daterange"
onFocusChange={this.onFocusChange}
focusedInput={focusedInput}
component={renderDateRangePicker}
/>
</div>
<div className="form-group">
<Field
name="persona"
component="select"
>
<option>1 persona</option>
<option>2 personas</option>
</Field>
</div>
<button type="submit" className="btn btn-default buscar">BUSCAR</button>
</form>
</div>
</div>
);
}
}
const ApartmentForm = reduxForm({
form: 'ApartmentForm',
destroyOnUnmount: false,
})(Apartamentos);
What you should do is maintain is a redux state variable called say previousSearches which is initialized as an empty array. Everytime you click Submit push the form data into this previousSearches array. So when you click on the input button next just display all information from the previousSearches array (which is a redux variable and can be accessed as a prop).
Something like this I guess
case 'ADD_PREVIOUS_SEARCH':
return Object.assign({}, state, {
previousSearches: state.previousSearches.push(action.search)
})
Then you can just access previousSearches by this.props.previousSearches

Categories

Resources