How to add class inside the dropdown-option into react-dropdown? - javascript

I Install the react-dropdown https://www.npmjs.com/package/react-dropdown?activeTab=readme.
I want to add my class directly to dropdown-option
App.js
import './App.css';
import Dropdown from 'react-dropdown';
import 'react-dropdown/style.css';
import { Option } from 'react-dropdown';
const reporTypes = useSelector((state: RootState) => state.report.options.types);
function App() {
return (
<div className="App">
<Dropdown
onChange={handleTypeChange}
options={reporTypes} value={selectedType} className='mh-select-container' controlClassName='mh-dropdown mh-dropdown-rounded' menuClassName='mh-dropdown-content' />
</div>
);
}
export default App;
How can I add this?

It's in the documentation
//option 1: for static options
const reporTypes = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two', className: 'myOptionClassName' },
]
//option 2: mapping from selector
const reporTypes = useSelector((state: RootState) => state.report.options.types?.map(type => ({ ...type, className: 'myOptionClassName' }));
// Option 3: mapping use useMemo
const reporTypes = useSelector((state: RootState) => state.report.options.types);
const mappedReportTypes = useMemo(() => {
return reporTypes?.map(type => ({ ...type, className: 'myOptionClassName' }))
}, [reporTypes ])
function App() {
return (
<div className="App">
<Dropdown options={reporTypes} placeholder="Select an option" className='mh-class' controlClassName='myControlClassName' menuClassName='myMenuClassName' arrowClassName='myarrow'/>
</div>
);
}
But I would suggest using the library with more rating as it could have lesser bugs and will handle more use cases

Related

An an extra Add Rule button to react-querybuilder

I am using react-querybuilder what I need is another Add Rule button to be add next to the original one and want to add differnt set of fields and operators when using the new button. Here is some part of my code:
import { HBButton, HBIcon } from '#hasty-bazar/core'
import { FC } from 'react'
import { useIntl } from 'react-intl'
import queryBuilderMessages from '../HBQueryBuilder.messages'
interface AddRuleActionProps {
handleOnClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
}
const AddGroupAction: FC<AddRuleActionProps> = ({ handleOnClick }) => {
const { formatMessage } = useIntl()
return (
<>
<HBButton
onClick={handleOnClick}
size="small"
leftIcon={<HBIcon type="plus" />}
sx={{ marginRight: 2, minWidth: 50 }}
>
{formatMessage(queryBuilderMessages.rule)}
</HBButton>
// >>> ANOTHER HBButton with different implementation to be added here
</>
)
}
export default AddGroupAction
Adding a new answer based on your feedback and because this one is very different from the other. I'm about to release v5.0 of react-querybuilder that has the feature I mentioned in the first paragraph of the other answer. This makes achieving the desired result much more straightforward and also eliminates the need for external state management (i.e. Redux).
TL;DR: working codesandbox example here (uses react-querybuilder#5.0.0-alpha.2).
React Query Builder only takes one fields prop, but you can organize the fields into an array of option groups instead of a flat array. I set the operators property on each field to the default operators, filtered appropriately for the type of field (text vs numeric).
import { Field, OptionGroup } from 'react-querybuilder';
import { nameOperators, numberOperators } from './operators';
export const fields: OptionGroup<Field>[] = [
{
label: 'Names',
options: [
{ name: 'firstName', label: 'First Name', operators: nameOperators },
{ name: 'lastName', label: 'Last Name', operators: nameOperators },
],
},
{
label: 'Numbers',
options: [
{ name: 'height', label: 'Height', operators: numberOperators },
{ name: 'weight', label: 'Weight', operators: numberOperators },
],
},
];
Next I set up a custom field selector component to only allow fields that are part of the same option group. So if a "name" field is chosen, the user can only select other "name" fields.
const FilteredFieldSelector = (props: FieldSelectorProps) => {
const filteredFields = fields.find((optGroup) =>
optGroup.options.map((og) => og.name).includes(props.value!)
)!.options;
return <ValueSelector {...{ ...props, options: filteredFields }} />;
};
This custom Add Rule button renders a separate button for each option group that calls the handleOnClick prop with the option group's label as context.
const AddRuleButtons = (props: ActionWithRulesAndAddersProps) => (
<>
{fields
.map((og) => og.label)
.map((lbl) => (
<button onClick={(e) => props.handleOnClick(e, lbl)}>
+Rule ({lbl})
</button>
))}
</>
);
The context is then passed to the onAddRule callback, which determines what field to assign based on the context value.
const onAddRule = (
rule: RuleType,
_pP: number[],
_q: RuleGroupType,
context: string
) => ({
...rule,
context,
field: fields.find((optGroup) => optGroup.label === context)!.options[0].name,
});
Put it all together in the QueryBuilder props, and voilà:
export default function App() {
const [query, setQuery] = useState(initialQuery);
return (
<div>
<QueryBuilder
fields={fields}
query={query}
onQueryChange={(q) => setQuery(q)}
controlElements={{
addRuleAction: AddRuleButtons,
fieldSelector: FilteredFieldSelector,
}}
onAddRule={onAddRule}
/>
<pre>{formatQuery(query, 'json')}</pre>
</div>
);
}
Update: see my other answer
This is a little tricky because the onAddRule callback function only accepts the rule to be added (which is always the default rule), and the parent path. If we could pass custom data into it this question would be much easier to answer.
The best way I can think to do it today is to externalize the query update methods out of the QueryBuilder component and manage them yourself (for the most part). In the example below, I've used Redux Toolkit (overkill for this use case but it's what I'm familiar with) to manage the query and replaced the Add Rule button with a custom component that renders two buttons, one to add a new rule for First Name and one to add a new rule for Last Name.
Working CodeSandbox example.
The redux store:
import { configureStore, createSlice, PayloadAction } from '#reduxjs/toolkit';
import { RuleGroupType } from 'react-querybuilder';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
interface State {
query: RuleGroupType;
}
export const getQuery = (state: State) => state.query;
const initialState: State = {
query: {
combinator: 'and',
rules: [],
},
};
const querySlice = createSlice({
name: 'query',
initialState,
reducers: {
setQuery(state: State, action: PayloadAction<RuleGroupWithAggregation>) {
state.query = action.payload;
},
},
});
const { reducer } = querySlice;
export const { setQuery } = querySlice.actions;
export const store = configureStore({ reducer });
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
The App component:
import {
ActionWithRulesProps,
add,
Field,
formatQuery,
QueryBuilder,
} from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.scss';
import { getQuery, setQuery, useAppDispatch, useAppSelector } from './store';
const fields: Field[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
];
const AddRuleButtons = (props: ActionWithRulesProps) => {
const dispatch = useAppDispatch();
const query = useAppSelector(getQuery);
const onClickFirst = () =>
dispatch(
setQuery(
add(
query,
{ field: 'firstName', operator: '=', value: 'First' },
props.path
)
)
);
const onClickLast = () =>
dispatch(
setQuery(
add(
query,
{ field: 'lastName', operator: '=', value: 'Last' },
props.path
)
)
);
return (
<>
<button onClick={onClickFirst}>+Rule (First Name)</button>
<button onClick={onClickLast}>+Rule (Last Name)</button>
</>
);
};
export default function App() {
const dispatch = useAppDispatch();
const query = useAppSelector(getQuery);
return (
<div>
<QueryBuilder
fields={fields}
query={query}
onQueryChange={(q) => dispatch(setQuery(q))}
controlElements={{
addRuleAction: AddRuleButtons,
}}
/>
<pre>{formatQuery(query, 'json')}</pre>
</div>
);
}

update value of an object in an array - react hooks

I want to increment the counter value of an item on click, I've tried to find the solution in the docs and I watched tutorials but I can't find the solution.
FruitCounter.js
import { Fragment, useState } from "react"
import Fruit from "./Fruit"
const data = [
{ id: 1, name: "🍋", counter: 0 },
{ id: 2, name: "🍒", counter: 0 },
]
const FruitCounter = () => {
const [fruits, setFruits] = useState(data)
const clickHandler = (fruit) => {
// Increment 'counter value of clicked item by 1'
}
return (
<Fragment>
{fruits.map((fruit) => {
return (
<Fruit
key={fruit.id}
{...fruit}
clickHandler={() => clickHandler(fruit)}
/>
)
})}
</Fragment>
)
}
export default FruitCounter
Fruit.js
import React from "react"
const Fruit = ({ counter, name, clickHandler }) => {
return (
<button type="button" className="fruit" onClick={clickHandler}>
<p>{counter}</p>
<h2>{name}</h2>
</button>
)
}
export default Fruit
You can try this
const clickHandler = (fruit) => {
setFruits(
fruits.map((x) => {
if (x.id === fruit.id)
return {
...x,
counter: x.counter + 1,
};
return x;
})
);
};

how to merge selectedOptions in react-select

I use react-select and I'm new.I have a component called Example
const colourOptions = [
{ value: '1', label: '1', color: '#00B8D9' },
{ value: '2', label: '2', color: '#0052CC' },
{ value: '3', label: '3', color: '#5243AA' },
];
class Example extends React.Component {
state = {
selectedOption: null,
}
render() {
const { selectedOption, onHandleChange } = this.props;
return (
<Select
onChange={onHandleChange}
options={colourOptions}
/>
);
}
}
export default Example;
In another file we have a functional Component
export default function UserProfile() {
const [selectedOption, setSelectedOption] = useState({});
const handleChange = (selectedOption) => {
setSelectedOption(selectedOption)
console.log(selectedOption)
};
return (
<div>
<Example onHandleChange={handleChange} selectedOption={selectedOption}/>
<Example onHandleChange={handleChange} selectedOption={selectedOption}/>
<Example onHandleChange={handleChange} selectedOption={selectedOption}/>
</div>
);
}
By changing every Example, the value of the previous selectedOption is removed.
how to put (merge) all selectedOptions inside one object ?
You will need to maintain a separate option values for all Example instances.
Like this:
export default function UserProfile() {
const [selectedOption, setSelectedOption] = useState({ example1: '', example2: '', example3: '' });
const handleChange = (key, selectedOption) => {
setSelectedOption(prev => ({...prev, [key]: selectedOption}));
console.log(selectedOption)
};
return (
<div>
<Example1 onHandleChange={(value) => handleChange('example1', value)} selectedOption={selectedOption.example1}/>
<Example2 onHandleChange={(value) => handleChange('example3', value)} selectedOption={selectedOption.example2}/>
<Example3 onHandleChange={(value) => handleChange('example3', value)} selectedOption={selectedOption.example3}/>
</div>
);
}
EDIT
Working code is here:
https://codesandbox.io/s/eloquent-mclaren-8s2z2?file=/src/UserProfile.js
Also quick note - the state updates are asynchronous so when you do console.log right after setting state, you may see the log printing old state.
Read more here if you like
You can collect all the selected options into one Array. Please find the link to working solution
https://codesandbox.io/s/react-example-xk3bw?fontsize=14&hidenavigation=1&theme=dark
import React, { useState,useEffect } from "react";
import Example1 from "./Example";
import Example2 from "./Example";
import Example3 from "./Example";
export function UserProfile() {
const [selectedOption, setSelectedOption] = useState({});
const [allselectedOption, setAllSelectedOption] = useState([]);
useEffect(() => {
console.log(allselectedOption);
},[allselectedOption]);
const handleChange = selectedOption => {
setSelectedOption(selectedOption);
let newSelectedOptions = [...allselectedOption,selectedOption]
setAllSelectedOption(newSelectedOptions)
console.log(newSelectedOptions);
};
return (
<div>
<Example1 onHandleChange={handleChange} selectedOption={selectedOption} />
<Example2 onHandleChange={handleChange} selectedOption={selectedOption} />
<Example3 onHandleChange={handleChange} selectedOption={selectedOption} />
</div>
);
}

In what condition it will re-render while using react custom hooks

I tried a sample in using react hook, make it a custom hook.
The problem is the simple hook useCount() goes fine, but the hook useCarHighlight() intending to switch highlight line would not cause re-render.
I see it is the same of the two, is anything wrong I should attention for about this?
I made a sandbox here: https://codesandbox.io/s/typescript-j2xtf
Some code below:
// index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import useCarHighlight, { Car } from "./useCarHighlight";
import useCount from "./useCount";
const myCars: Car[] = [
{ model: "C300", brand: "benz", price: 29000, ac: "auto ac" },
{ model: "Qin", brand: "byd", price: 9000 }
];
const App = () => {
const { cars, setHighlight } = useCarHighlight(myCars, "Qin");
const { count, increase, decrease } = useCount(10);
console.log(
`re-render at ${new Date().toLocaleTimeString()},
Current highlight: ${
cars.find(c => c.highlight)?.model
}`
);
return (
<div>
<ul>
{cars.map(car => {
const { model, highlight, brand, price, ac = "no ac" } = car;
return (
<li
key={model}
style={{ color: highlight ? "red" : "grey" }}
>{`[${brand}] ${model}: $ ${price}, ${ac}`}</li>
);
})}
</ul>
<button onClick={() => setHighlight("C300")}>highlight C300</button>
<button onClick={() => setHighlight("Qin")}>highlight Qin</button>
<hr />
<h1>{`Count: ${count}`}</h1>
<button onClick={() => increase()}>+</button>
<button onClick={() => decrease()}>-</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
// useCarHighlight.ts
import { useState } from "react";
export type Car = {
model: string;
brand: string;
price: number;
ac?: "auto ac" | "manual ac";
};
export default function(
initialCars: Car[],
initialSelectedModel: string
): {
cars: Array<Car & { highlight: boolean }>;
setHighlight: (selMod: string) => void;
} {
const carsHighlight = initialCars.map(car => ({
...car,
highlight: initialSelectedModel === car.model
}));
const [cars, setCars] = useState(carsHighlight);
const setHighlight = (selMod: string) => {
cars.forEach(car => {
car.highlight = car.model === selMod;
});
setCars(cars);
};
return {
cars,
setHighlight
};
}
// useCount.ts
import { useState } from "react";
export default function useCount(initialCount: number) {
const [state, setState] = useState(initialCount);
const increase = () => setState(state + 1);
const decrease = () => setState(state - 1);
return {
count: state,
increase,
decrease
};
}
Unlike class components, mutating state of hooks does not queue a re-render, when using hooks you have to update your state in an immutable way.
Also, when calculating the next state based on the previous state it is recommended to use a functional update and read the previous state from the first argument of the function.
const setHighlight = (selMod: string) => {
setCars(prevState =>
prevState.map(car => ({
...car,
highlight: car.model === selMod
}))
);
};
Here is a good resource about immutable update patterns
Dont use forEach in setHighlight, use map instead
const setHighlight = (selMod: string) => {
const newCars = cars.map(car => ({
...car,
highlight: car.model === selMod
}));
setCars(newCars);
};
Use map instead of forEach as the address of car object isn't getting changed when you update highlight property in car.
const setHighlight = (selMod: string) => {
let carsTemp = cars.map(car => ({
...car,
highlight : car.model === selMod
}));
setCars(carsTemp);};

React dispatch is not defined remove action

i have a problem with my actionsFormatter.
When I click on the DELETE button, I get the error:
Uncaught ReferenceError: dispatch is not defined at onClick
How could I fix this problem?
import { removeEnvironnement } from '../../actions/environnement';
const EnvironnementList = (props) => (
<BootstrapTable
keyField='id'
data={ props.store.environnements }
columns={ columns }
selectRow={selectRow}
pagination={ paginationFactory() }
filter={ filterFactory() }
striped hover condensed
/>
);
const actionsFormatter = (cell, row) => {
const id=row.id
return (
<button className="btn btn-danger"
onClick={() => {
dispatch(removeEnvironnement({ id }));}}
>Delete</button>
);
};
const columns = [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'nom',
text: 'Nom',
filter: textFilter()
}, {
dataField: 'actions',
text: 'Action',
formatter: actionsFormatter
} ];
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
bgColor: '#00BFFF'
};
const mapStateToProps = (state) => {
return {
store: state
};
}
export default connect(mapStateToProps)(EnvironnementList);
Here is my code to do the remove :
Should I remove the dispatch part?
const _removeEnvironnement = ({ id } = {}) => ({
type: 'REMOVE_ENVIRONNEMENT',
id
});
export const removeEnvironnement = ({ id } = {}) => {
return (dispatch) => {
return axios.delete(`environnements/${id}`).then(() => {
dispatch(_removeEnvironnement({ id }));
})
}
};
What is dispatch in your actionsFormatter? It is defined neither on actionsFormatter scope nor on out of actionsFormatter scope. That's the problem and that's the javascript interpreter talking you about.
One of the possible fix is to import you redux store
store.js
export const store = createStore(...)
EnvironmentList.js
import { store } from './path/to/store.js'
// ...
const actionsFormatter = (cell, row) => {
const { dispatch } = store
const id = row.id
// ...
};
This way you'll get dispatch available in actionsFormatter body.
Another way is to provide mapped method via connect -> EnvironmentList -> actionsFormatter chain. Do what Arnaud Christ suggested in his reply and then refactor the code:
const EnvironmentList = (props) => (
<BootstrapTable
keyField='id'
data={ props.store.environnements }
columns={ columns(props.removeEnvironment) }
selectRow={selectRow}
pagination={ paginationFactory() }
filter={ filterFactory() }
striped hover condensed
/>
);
const actionsFormatter = (removeEnvironment) => (cell, row) => {
const id=row.id
return (
<button className="btn btn-danger"
onClick={() => {
removeEnvironment({ id });
}}
>Delete</button>
);
};
const columns = (removeEnvironment) => [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'nom',
text: 'Nom',
filter: textFilter()
}, {
dataField: 'actions',
text: 'Action',
formatter: actionsFormatter(removeEnvironment)
} ];
So, the connected EnvironmentList got necessary removeEnvironment method on it's props. Then we passes it to columns creator, which passed it to actionsFormatter creator.
You have to link your component with the dispatch method.
As you are already using react-redux to connect your component to your Redux store, you can easily do that through mapping dispatch to props.
Just add the following:
const mapStateToProps = (state) => {
return {
store: state
};
}
const mapDispatchToProps = dispatch => {
return {
removeEnvironnement: id => {
dispatch(removeEnvironnement({ id }));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EnvironnementList);
And then in your onClick handler, just call this.props.removeEnvironnement(id)

Categories

Resources