I have this code:
import React from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const optionsExample = [
{
code: "CLP",
country: "CLP",
minimumAmount: 10000
},
{
code: "USD",
country: "US",
minimumAmount: 25000
}
];
const handleChange = (newValue, actionMeta) => {
console.log("change newValue", newValue); // object changes but it's not selected the right one
};
const CustomControl = () => (
<Select
defaultValue={optionsExample[0]}
formatOptionLabel={({ code }) => code}
options={optionsExample}
onChange={handleChange}
/>
);
ReactDOM.render(<CustomControl />, document.getElementById("root"));
It doesn't work ok because react-select expect that the value of each object on the data array be named value. How can I pass data like in the example? I need that data has those properties, I can't change country to value.
Actually, I used the props formatOptionLabel to change the default label field from the object to code but I don't know how to do the same but for the value.
Demo with my code, with the problem:
https://codesandbox.io/s/react-select-formatoptionlabel-forked-deuw1?file=/index.js:0-756
This is working because each object has it's value field and that's what is expected by react-select
https://codesandbox.io/s/react-select-formatoptionlabel-forked-d9bdj
If you can't move the mountain, then walk around it.
use inline .map to convert your array of objects
const CustomControl = () => (
<Select
defaultValue={optionsExample[0]}
formatOptionLabel={({ code }) => code}
options={optionsExample.map(x => ({ value: x.country, label: x.code })}
onChange={handleChange}
/>
);
or better still use useMemo to create a new array, if your array changes
const selectOptions = useMemo(() => optionsExample.map(x => ({ value: x.country, label: x.code }),[optionsExample])
<Select
defaultValue={selectOptions[0]}
formatOptionLabel={({ code }) => code}
options={selectOptions}
onChange={handleChange}
/>
Related
I'm trying to make a dynamic form where the form input fields is rendered from data returned by an API.
Since atom needs to have a unique key, I tried wrapping it inside a function, but every time I update the field value or the component re-mounts (try changing tabs), I get a warning saying:
I made a small running example here https://codesandbox.io/s/zealous-night-e0h4jt?file=/src/App.tsx (same code as below):
import React, { useEffect, useState } from "react";
import { atom, RecoilRoot, useRecoilState } from "recoil";
import "./styles.css";
const textState = (key: string, defaultValue: string = "") =>
atom({
key,
default: defaultValue
});
const TextInput = ({ id, defaultValue }: any) => {
const [text, setText] = useRecoilState(textState(id, defaultValue));
const onChange = (event: any) => {
setText(event.target.value);
};
useEffect(() => {
return () => console.log("TextInput unmount");
}, []);
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
};
export default function App() {
const [tabIndex, setTabIndex] = useState(0);
// This would normally be a fetch request made by graphql or inside useEffect
const fields = [
{ id: "foo", type: "text", value: "bar" },
{ id: "hello", type: "text", value: "world" }
];
return (
<div className="App">
<RecoilRoot>
<form>
<button type="button" onClick={() => setTabIndex(0)}>
Tab 1
</button>
<button type="button" onClick={() => setTabIndex(1)}>
Tab 2
</button>
{tabIndex === 0 ? (
<div>
<h1>Fields</h1>
{fields.map((field) => {
if (field.type === "text") {
return (
<TextInput
key={field.id}
id={field.id}
defaultValue={field.value}
/>
);
}
})}
</div>
) : (
<div>
<h1>Tab 2</h1>Just checking if state is persisted when TextInput
is unmounted
</div>
)}
</form>
</RecoilRoot>
</div>
);
}
Is this even possible with recoil. I mean it seems to work but I can't ignore the warnings.
This answer shows how you can manually manage multiple instances of atoms using memoization.
However, if your defaultValue for each usage instance won't change, then Recoil already provides a utility which can take care of this creation and memoization for you: atomFamily. I'll quote some relevant info from the previous link (but read it all to understand fully):
... You could implement this yourself via a memoization pattern. But, Recoil provides this pattern for you with the atomFamily utility. An Atom Family represents a collection of atoms. When you call atomFamily it will return a function which provides the RecoilState atom based on the parameters you pass in.
The atomFamily essentially provides a map from the parameter to an atom. You only need to provide a single key for the atomFamily and it will generate a unique key for each underlying atom. These atom keys can be used for persistence, and so must be stable across application executions. The parameters may also be generated at different callsites and we want equivalent parameters to use the same underlying atom. Therefore, value-equality is used instead of reference-equality for atomFamily parameters. This imposes restrictions on the types which can be used for the parameter. atomFamily accepts primitive types, or arrays or objects which can contain arrays, objects, or primitive types.
Here's a working example showing how you can use your id and defaultValue (a unique combination of values as a tuple) as a parameter when using an instance of atomFamily state for each input:
TS Playground
body { font-family: sans-serif; }
input[type="text"] { font-size: 1rem; padding: 0.5rem; }
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/recoil#0.6.1/umd/recoil.min.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.7/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import ReactDOM from 'react-dom';
// import type {ReactElement} from 'react';
// import {atomFamily, RecoilRoot, useRecoilState} from 'recoil';
// This Stack Overflow snippet demo uses UMD modules instead of the above import statments
const {atomFamily, RecoilRoot, useRecoilState} = Recoil;
const textInputState = atomFamily<string, [id: string, defaultValue?: string]>({
key: 'textInput',
default: ([, defaultValue]) => defaultValue ?? '',
});
type TextInputProps = {
id: string;
defaultValue?: string;
};
function TextInput ({defaultValue = '', id}: TextInputProps): ReactElement {
const [value, setValue] = useRecoilState(textInputState([id, defaultValue]));
return (
<div>
<input
type="text"
onChange={ev => setValue(ev.target.value)}
placeholder={defaultValue}
{...{value}}
/>
</div>
);
}
function App (): ReactElement {
const fields = [
{ id: 'foo', type: 'text', value: 'bar' },
{ id: 'hello', type: 'text', value: 'world' },
];
return (
<RecoilRoot>
<h1>Custom defaults using atomFamily</h1>
{fields.map(({id, value: defaultValue}) => (
<TextInput key={id} {...{defaultValue, id}} />
))}
</RecoilRoot>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
I think the problem is from textState(id, defaultValue). Every time you trigger re-rendering for TextInput, that function will be called again to create a new atom with the same key.
To avoid that situation, you can create a global variable to track which atom added. For example
let atoms = {}
const textState = (key: string, defaultValue: string = "") => {
//if the current key is not added, should add a new atom to `atoms`
if(!atoms[key]) {
atoms[key] = atom({
key,
default: defaultValue
})
}
//reuse the existing atom which is added before with the same key
return atoms[key];
}
I am using this module
import { MultiSelect } from "react-multi-select-component"
and creating form by this
const [selected_to2, setSelected_to2] = useState([]);
<MultiSelect
options={options2}
value={selected_to}
onChange={setSelected_to}
labelledBy="Select2"
/>
then I creating multiselect component with default value using this
selected_to2.push({
value: item?.Code,
label: item?.Name
})
but returning value with 4 time called in the multiselect form, any proper way to set default value of multiselect then also add and remove in multiselect item?
React state must updated by set function. Edit the value directly will not work.
So you need do
setSelected_to2(s => [...s, {
value: item?.Code,
label: item?.Name
}])
First of all you are updating state is a wrong way
Wrong way
selected_to2.push({
value: item?.Code,
label: item?.Name
})
Right way:
setSelected_to2([{
value: item?.Code,
label: item?.Name
}
])
Here's a quick example that I did might help you, remember your default value must be an array of selected values since it's a multiselect component.
JSX
import { useState } from "react";
import { MultiSelect } from "react-multi-select-component";
import "./App.css";
const options = [
{ label: "The Godfather", value: 1 },
{ label: "Pulp Fiction", value: 2 },
];
const App = ({ editMode = true }) => {
const [value, setValue] = useState(editMode ? [options[1]] : []);
return (
<div className="App">
<MultiSelect
options={options}
value={value}
onChange={(selectedValues) => setValue(selectedValues)}
labelledBy="Select2"
/>
</div>
);
};
export default App;
I am having a simple form in React, which looks like:
const [placeOptions] = useState([
{ value: 'USA', label: 'USA' },
{ value: 'MEX', label: 'Mexico' },
]);
const [name, setName] = useState('');
const [place, setPlace] = useState('USA');
....
<input onChange={event => setName(event.target.value)} type="text"/>
<select onChange={event => setPlace(event.target.value)}>
{placeOptions.map(item => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
<CustomButton id="custom-btn" props={[name, place]} />
The above Custom button is just rendering once and is taking the default null and 'USA' value. It should Ideally send props to every event change, possibly refreshing the component once event is triggered. I am unable to determine how do I refresh a component on event change and pass the correct state to the props.
Edit: The below is the CustomButton.tsx file:
export function CustomButton({ props, id }: { props?:any, id?:string}) {
var name = props ? props[0] : '';
var place = props ? props[1] : '';
useEffect(() => {
renderButton(id);
}
return(
<React.Fragment>
<div id={id}></div>
</React.Fragment>
);
async function renderButton(id: string) {
... // Some logic involving the props passed
}
}
Edit 2:
This the code sandbox: https://codesandbox.io/s/amazing-dust-315dk?file=/src/App.js
All I want is to change the props too and dynamically render the custom button.
The problem is how you define the name and the place variable in CustomButton Component.
Variables of javascript defined like var let, and const will not trigger re-renders in React Button. States and Props only can trigger re-renders in React Components.
So if you do something like this in the parent file:
// All same code excepet
<CutomButtom id="cutom-btn" name={name} place={place} />
You can get name and place directly from props and use them as it is like:
export function CustomButton({ name, place, id }: { name: string, place: string, id:string}){
// NO need for defining name and place now, just use them directly...
}
Another improvement you can make is to define PropsType separately:
export interface CustomButtonProps {
id: string;
name:string;
place:string;
}
export function CustomButton({name, place, id}:CustomButtonProps){
}
I am building a rest countries app in react and I'm trying to filter by region through a list. I would like to use a dropdown menu. I have tried using react select, creating a new component, this is my code
const DropdownMenu = (props) => {
const options = [
'', 'Africa', 'America', 'Asia', 'Europe', 'Oceania'
];
const defaultOption = options[0];
const handleFilterInput = (event) => {
let value = event.target.options;
props.handleRegionSearch(value);
};
return(
<div>
<Select options={options} onChange={handleFilterInput} value={defaultOption} placeholder="Select a region"/>
</div>
)
}
export default DropdownMenu;
My problem is, I can not get the value to change to the option selected, therefore it's impossible for me to filter the list I took from the api.
Does anyone have a possible solution for this?
Thanks
You are setting the value wrong
value={defaultOption}
This default option is never changing.
What you can do is catch the current value from onChange() and set the value somewhere (preferably in state) and then use that stored value as the value of dropdown component
Something like this.
const options = [
'', 'Africa', 'America', 'Asia', 'Europe', 'Oceania'
];
const [selectedValue , setSelectedValue ] = useState(options[0]);
const handleFilterInput = (event) => {
let value = event.target.options;
setSelectedValue(value);
props.handleRegionSearch(value);
};
return(
<div>
<Select options={options} onChange={handleFilterInput} value={selectedValue} placeholder="Select a region"/>
</div>
)
Thanks! In the end, I used a react-select component which did solve a this issue on it's own.
I use "react final forms" and "react select".
I've organized an interface and functionality works, but I have one thing which I don't like.
React select requires options in next format:
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
and the value also should be set as { value: 'chocolate', label: 'Chocolate' }.
But for me is strange to have in my model of data (and send to the server also) the value like this - { value: 'chocolate', label: 'Chocolate' }, because I need only 'chocolate'.
Easy way is format the object into a single value after the form will be saved and format back from single value to the object before the rendering of the element. I know how to do it outside of form, but in this case I should solve this problem again and again for each select element separately.
What I would like to do:
Find a way how to set value of the react select as single value, like 'chocolate' instead of object.
OR
Create a wrapper for react select component and format value there when it sets (it's easy) and when the form get the value from this field (this I don't know how to do).
I will appreciate any help.
Method 1
With strings:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const data = ["1", "2"];
function SingleStringReactSelect() {
const [option, setOption] = useState();
return (
<section>
<Select
onChange={option => setOption(option)}
value={[option]}
getOptionLabel={label => label}
getOptionValue={value => value}
closeMenuOSelect={false}
options={data}
/>
</section>
);
}
Method 2:
Created example:
https://codesandbox.io/s/react-codesandboxer-example-z57ke
You can use map in options and with Wrapper like SingleValueReactSelect
import React, { Fragment, useState } from "react";
import Select from "react-select";
const data = ["chocolate", "strawberry", "vanilla"];
export function SingleValueReactSelect(props) {
const [selectedItem, setSelectedItem] = useState();
return (
<Select
{...props}
value={selectedItem}
onChange={item => {
setSelectedItem(item);
props.onChange(item.value);
}}
options={props.options.map(item => ({ label: item, value: item }))}
/>
);
}
export default function AppDemo() {
return (
<Fragment>
<p>SingleValueReactSelect Demo</p>
<SingleValueReactSelect
isClearable
isSearchable
options={data}
onChange={item => {
alert(item);
}}
/>
</Fragment>
);
}
You can transform/filter the object data once you are sending the form data to server or another component.
Setting the value of the react-select as a single value will not show it as selected in the React-Select.