I've tried even manually setting the default values like in the documentation but no dice. I'm not sure if it's a styling issue or what. So below I posted what I have along with a screenshot.
<Select
components={animatedComponents}
getOptionLabel={convertToLabel}
getOptionValue={option => option.resource_name}
isMulti
onChange={changeEvent}
options={users}
theme={theme => ({
...theme,
borderRadius: 0
})}
defaultValue={(props.value || []).map(convertToValue)}
value={(props.value || []).map(convertToValue)}
/>
convertToValue function
const convertToValue = props => {
return {
label: `${props.name} ${props.family_name}`,
value: props.resource_name
};
};
convertToLabel function
const convertToLabel = props => {
return `${props.name} ${props.family_name}`;
};
changeEvent function
const changeEvent = (selectedOption, i) => {
let option = {
name: "reviewers",
value: selectedOption
};
update({ target: option });
};
users & props objects
users:
[
{
resource_name: "facebook_user1",
name: "Joe",
family_name: "Dirt"
},
{
resource_name: "facebook_user2",
name: "Trident",
family_name: "White"
}
]
props:
{
field: "placeholder",
fieldType: "placeholderType"
value:[
{
resource_name: "facebook_user1",
name: "Joe",
family_name: "Dirt"
},
{
resource_name: "facebook_user2",
name: "Trident",
family_name: "White"
}
]
}
What I see on my screen.
It is extremely difficult to tell exactly what your issue is, without seeing the actual JSX of your select render. Here are a few issues I see, looking at your question, and some hard guesses at what might be happening.
You should show us the full JSX render of your Select implementation
You never show us what your defaultValue prop looks like, but
remember that value is expected to be equal to one of your
options, not just an option 'value'
Your label and option getter
methods signature should be getOptionLabel = (option) => string and
getOptionValue = (option) => string. You've used props, which
might conflict with parent scope, in your instance.
You probably want
your convertToValue method signature to line up with those as well.
Your onChange event method signature doesn't line up with
React-Select, and may be causing you pain. See my answer to this
recent question for help on this.
Related
My task is to do create a form that will get some fields from STRAPI api.
A form will be component, that will be passed to different pages.
So in each page there will be same form but with different fields based on Strapi.
Form structure will look like this:
const apiCall = {
dropdown: [
// there can be 0 objects or infinite objects, based on strapi api call
{
question: "how are you?",
answers: [
{name: "Good"},
{name: "Bad"}
// infinite number of answers
]
},
{
question: "how are you?",
answers: [
{name: "Good"},
{name: "Bad"}
// infinite number of answers
]
}
]
}
Then in Formik i need to do something like this:
// DROPDOWNS
{apiCall.dropdown?.map(({ answers, question, id}) => (
<div key={id} className="mb-5">
<h2 className="h3 mb-2">
{question}
</h2>
<FormSelect
key={id}
name={} //??? IDK
label={t('validation.oneOfOptions')}
options={
answers?.map(a => (
{value: a.name, label: a.name}
))
}
className={formFieldClasses}
/>
</div>
How should i initialize formik values and validate data? I am using Formik component.
Then i need to send data to another api call where we are going to send emails with answers. But IDK how can i send dynamic data when i dont know data that will come from apiCall?
Normally i will do something like this:
const initialValues: LostFormValues = {
category: CategoryValues.WALLET,
secCategory: SecCategoryValues.TRAIN,
priority: PriorityValues.HIGH,
cityFrom: null,
cityTo: null,
date: today,
ticketNumber: user?.user?.accountCode || '',
firstName: user?.user?.firstName || '',
surname: user?.user?.surname || '',
email: user?.user?.email || '',
phoneNumber: user?.user?.phoneNumber || '',
description: '',
files: [],
agreeWithTerms: false,
};
and then:
<Formik
enableReinitialize
initialValues={initialValues}
validationSchema={yup.object().shape({
category: yupFieldEnum(CategoryValues),
secCategory: yupFieldEnum(SecCategoryValues),
date: yupField.date,
ticketNumber: yup.string().nullable(), // not required
priority: yupField.string,
cityFrom: yupField.autoComplete,
cityTo: yupField.autoComplete,
firstName: yupField.string,
surname: yupField.string,
email: yupField.email,
description: yupField.string,
agreeWithTerms: yupField.checkbox,
})}
onSubmit={async (values) => {
const isSent = await sendToExternalService(values);
}}
>
But now i cant just write variables and then send them.. i need to write them dynamicaly
so this quite a big question, and a lot of unnecessary info, not sure I'm getting correct all the points however based on api you want to validate answers so if you have field answers you should have field answer, basically what i would do:
const apiCall = [{..},{..}]
const ParentComponent = () => {
const [answers, setAnswers] = useState({})
return ( <>
{apiCall.dropdown.map(item => (
<SelectComponent item={item} setAnswers={setAnswers} key={..}/>
)}
}
const SelectComponent = ({item}) => (
const formik = useFormik({
initialValues: {
answer: null,
}
validationSchema: Yup.object().shape({
answer: Yup.string().oneOf(item.answers.map(({name})=> name).nullable()}),
onSubmit: (values) => setAnswers(answers => (
{...answers, [item.question]:values.answer})
);
})
return (
<>...
<FormSelect name='answer'
value={formik.values.answer} options={
item.answers.map(a => (
{value: a.name, label: a.name}
))
}
/>
<Button onClick={() => formik.onSubmit()}>Ok</Button>
</>)
)
So now you keep your answers as key value pairs in parent component and you validate exact question, based on what in the api. Note there is many ways to go from here depending on how you would pass data to api...
Not sure if it would work out of the box if you dynamically change apiCall on fly but this should be also possible to handle
As stated in the title I'm trying to change the state of an object nested in an array of objects. I just can not get this to work. I've added a sample of what I'm trying to do in a codesandbox.
I'm using a Material-UI component call ToggleButton (link to demo ToggleButton). I want to change the state to toggle the buttons in the group. I was able to get this working for a create function but can not get it working for my update function.
Trying to change the values of the object in the array is just not working for me. Below are some things I've tried to no success. I want to change the IsTrue: so I can toggle the button to display the users selection.
setAppetizer(true);
setRecipeObjectState({
...recipeObjectState,
Category: [
...recipeObjectState.Category,
{
IsTrue: appetizer,
category: "Appetizer",
},
],
});
This just adds more buttons to my button group.
setAppetizer(true);
setRecipeObjectState((prevState) => ({
...prevState,
Category: [
{
IsTrue: appetizer,
category: "Appetizer",
},
],
}));
I'm just lost now at this point. I just want to also state that this is my first React project that is not just a sandbox for learning the framework and I'm also a jr. developer trying to learn. I have search stackoverflow and nothing has helped me out. I hope I have included enough information for someone to help out.
Your state and app appear to be very convoluted, but the general idea when updating nested array state is to shallowly copy the state at each level where an update is being made. Use array::map to map the Category property to a new array object reference and when the category matches toggle the IsTrue "selected" property.
setRecipeObjectState((prevState) => ({
...prevState,
Category: prevState.Category.map((category) =>
category.category === newCategory // <-- newCategory value from toggled button
? {
...category,
IsTrue: !category.IsTrue
}
: category
)
}));
Since your "selected" calculation is selected={Boolean(item.IsTrue)} you'll want to ensure your IsTrue element values are actually togglable, i.e. just store the boolean value right in the array.
const recipeObject = {
AuthorId: authorId,
BookAuthor: bookAuthor,
BookTitle: bookTitle,
Calories: parseInt(calories),
Category: [
{
IsTrue: false,
category: "Appetizer"
},
{
IsTrue: false,
category: "Breakfast"
},
{
IsTrue: false,
category: "Soup / Salad"
},
{
IsTrue: false,
category: "Vegetarian"
},
{
IsTrue: true,
category: "Meat (Beef, Pork, Chicken)"
},
{
IsTrue: false,
category: "Fish"
},
{
IsTrue: false,
category: "Dessert"
}
],
Description: description,
DurationInMinCook: parseInt(durationInMinCook),
DurationInMinPrep: parseInt(durationInMinPrep),
ImageUrl: imageUrl,
Ingredients: addedIngredients, // array
Instructions: addedInstructions, // array
IsRecipe: true,
Likes: 0,
RecipeId: selectedRecipeId,
ServingSize: parseInt(servingSize),
Title: title,
YouTubeUrl: youTubeUrl
};
You mutating the same reference, you need to render a copy or the component won't render (shallow comparison):
Try updating state like this.
const oldState = recipeObjectState;
oldState.Category = [
{
IsTrue: appetizer,
category: "Appetizer"
}
];
setRecipeObjectState(oldState);
I didn't try your component because it's huge.
Updating a single object property in an array of objects seems to be a very common use-case. Here is generally how you do that. Suppose id is a unique identifier of each object and that we want to toggle selected:
import React, { useState } from "react";
import "./styles.css";
import faker from "faker";
const array = [];
for (let id = 1; id < 10; id++) {
array.push({
id,
name: faker.name.firstName(),
age: faker.random.number(100),
selected: false
});
}
export default function App() {
const [list, setList] = useState(array);
const onClick = (id) => (event) => {
setList((list) =>
list.map((item) =>
item.id === id ? { ...item, selected: !item.selected } : item
)
);
};
return (
<div className="App">
Click on a item:
<ul>
{list.map(({ id, name, age, selected }) => (
<li key={id} onClick={onClick(id)} className="item">
{name} {age}
{selected ? " ✅" : null}
</li>
))}
</ul>
</div>
);
}
https://codesandbox.io/s/xenodochial-river-9sq6g?file=/src/App.js:0-825
I am using react-select for my select dropdown. The issue I am having is that there is no empty option to reset the dropdown value if the user changes their mind.
Currently I am taking the options and manually adding an empty string, but I feel there must be something already in the library to handle this? I cannot find anything in the docs.
My code looks like the below, and there is a code sandbox here.
import React from "react";
import Select from "react-select";
const App = () => {
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
return <Dropdown options={options} />;
}
const Dropdown = ({ options }) => {
const optionsWithEmptyOption = [{ value: "", label: "" }, ...options];
return <Select options={optionsWithEmptyOption} />;
};
Plase check this out
https://codesandbox.io/s/zow1c?module=/example.js
import React, { Component } from 'react';
import CreatableSelect from 'react-select/creatable';
import { colourOptions } from './docs/data';
export default class CreatableSingle extends Component<*, State> {
handleChange = (newValue: any, actionMeta: any) => {
console.group('Value Changed');
console.log(newValue);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
};
handleInputChange = (inputValue: any, actionMeta: any) => {
console.group('Input Changed');
console.log(inputValue);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
};
render() {
return (
<CreatableSelect
isClearable
onChange={this.handleChange}
onInputChange={this.handleInputChange}
options={colourOptions}
/>
);
}
}
Empty Unicode
I add line to options, and write between the apostrophes empty unicode like this: ⠀⠀⠀⠀⠀⠀⠀⠀ .. you can mark it but dont see it.
const options = [
{ value: "", label: "⠀" },
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
And I change this:
return <Select options={options} />;
what about const optionsWithEmptyOption = [{ value: null, label: "Select..." }, ...options];
I'm not really good with explanations, but #NicoHaase is right, so here it goes...
as far as I know, you must give a value to value null (if nothing) or string ... same for the label, #1 because of the user UX and second so react-select knows what to display. But if you really need to leave it in black, and you can try to modify in the styles, in order to have the same height as the other options.
I'm working with admin-on-rest and I'm creating <Edit> component. In <Edit> I have <RadioButtonInputGroup> with defined defaultValue:
const defaultValues = {
user_to_app_to_role_by_user_id: 5
};
...
const roleChoices = [
{ id: 5, name: 'User' },
{ id: 4, name: 'Admin' }
];
...
<Edit title="Edit user">
<SimpleForm defaultValue={defaultValues}>
<RadioButtonGroupInput
label="Role"
source="user_to_app_to_role_by_user_id"
choices={roleChoices}
/>
...
But now I want to et up default value like value that I want to get from this.props, like this:
const defaultValues = {
user_to_app_to_role_by_user_id: this.getRoleByProps()
};
...
getRoleByProps() {
let role;
if(this.props.user && this.props.user.roles) {
return this.props.user.roles[0].role_id
}
else {
return 5;
}
}
In process of debugging I see, that when properties come in, my defaultValues object update. But what's problem is that component <RadioButtonGroupInput> doesn't update. It's value as before.
I already tried to inpur default velues in state and write <SimpleForm defaultValue={this.state.defaultValues} but it doesn't help.
Maybe you can give some advice for me what I can do in thi situation.
i am planning to build a generic filter like Gbif Have.
My question is how to approach this problem.
I like to use ReactJs for this project.
What other technology i need to look into along with React and redux in order to design such a generic filter.
I try to design this filter using React and redux only.
In my approach, i try to maintain the query parameter inside the state variable of the get_data method, in which i am fetching the data from the server. As somebody click on any filter button, then i pass custom event from that filter component along with query parameter and handle this event in get_data method. In get_data method again i am saving this value in get_data state parameter and again getting the new filtered data.
Now the Problem with above approach is that as the number of parameter increases it become very difficult to maintain.
my get_data constructor look like this.
constructor(props){
super(props);
this.state={
params:{
max:10,
offset:0,
taxon:[],
sGroup:[],
classification:undefined,
userGroupList:[],
isFlagged:undefined,
speciesName:undefined,
isMediaFilter:undefined,
sort:"lastRevised",
webaddress:""
},
title:[],
groupName:[],
userGroupName:[],
view:1
}
this.props.fetchObservations(this.state.params)
this.loadMore=this.loadMore.bind(this);
};
The way i am getting data from filter component is something like this.
this is my handleInput method which fire onSelect method from one of the filter.
handleInput(value,groupName){
this.setState({
active:true
})
this.props.ClearObservationPage();
var event = new CustomEvent("sGroup-filter",{ "detail":{
sGroup:value,
groupName:groupName
}
});
document.dispatchEvent(event);
};
the way i am handling this event in my get_data component is look something like this.
sGroupFilterEventListner(e){
const params=this.state.params;
if(!params.sGroup){
params.sGroup=[];
}
console.log("params.sGroup",params.taxon)
params.sGroup.push(e.detail.sGroup)
params.sGroup=_.uniqBy(params.sGroup)
const groupName=this.state.groupName;
var titleobject={};
titleobject.sGroup=e.detail.sGroup;
titleobject.groupName=e.detail.groupName;
groupName.push(titleobject);
let newgroupname=_.uniqBy(groupName,"sGroup")
params.classification=params.classification;
let isFlagged=params.isFlagged;
let speciesName=params.speciesName;
let MediaFilter=params.isMediaFilter;
let taxonparams=params.taxon;
taxonparams= taxonparams.join(",");
let sGroupParams=params.sGroup;
sGroupParams=sGroupParams.join(",");
let userGroupParams=params.userGroupList;
userGroupParams=userGroupParams.join(",");
let newparams={
max:10,
sGroup:sGroupParams,
classification:params.classification,
offset:0,
taxon:taxonparams,
userGroupList:userGroupParams,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
}
this.props.fetchObservations(newparams);
this.setState({
params:{
max:10,
sGroup:params.sGroup,
classification:params.classification,
offset:0,
taxon:params.taxon,
userGroupList:params.userGroupList,
isFlagged:isFlagged,
speciesName:speciesName,
isMediaFilter:MediaFilter,
sort:params.sort
},
groupName:newgroupname
})
}
I registered and unRegistered the sGroupFilterEventListner in my componentDidMount and componentunmount method.
Presently i am also not considering the case where if somebody type in url bar, the filter panel change automatically.
Please consider all the above scenario and suggest me a generic way to do the same. thanks.
My Current Filter Panle look like this
Here's a quick example (React only, no Redux) I whipped up with a dynamic number of filters (defined in the filters array, but naturally you can acquire that from wherever).
const filters = [
{ id: "name", title: "Name", type: "string" },
{
id: "color",
title: "Color",
type: "choice",
choices: ["blue", "orange"],
},
{
id: "height",
title: "Height",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
{
id: "width",
title: "Width",
type: "choice",
choices: ["tiny", "small", "big", "huge"],
},
];
const filterComponents = {
string: ({ filter, onChange, value }) => (
<input
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
/>
),
choice: ({ filter, onChange, value }) => (
<select
value={value || ""}
onInput={e => onChange(filter.id, e.target.value)}
size={1 + filter.choices.length}
>
<option value="">(none)</option>
{filter.choices.map(c => (
<option value={c} key={c}>
{c}
</option>
))}
</select>
),
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { filters: {} };
this.onChangeFilter = this.onChangeFilter.bind(this);
}
onChangeFilter(filterId, value) {
const newFilterState = Object.assign({}, this.state.filters, {
[filterId]: value || undefined,
});
this.setState({ filters: newFilterState });
}
renderFilter(f) {
const Component = filterComponents[f.type];
return (
<div key={f.id}>
<b>{f.title}</b>
<Component
filter={f}
value={this.state.filters[f.id]}
onChange={this.onChangeFilter}
/>
</div>
);
}
render() {
return (
<table>
<tbody>
<tr>
<td>{filters.map(f => this.renderFilter(f))}</td>
<td>Filters: {JSON.stringify(this.state.filters)}</td>
</tr>
</tbody>
</table>
);
}
}
ReactDOM.render(<App />, document.querySelector("main"));
body {
font: 12pt sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
(originally on https://codepen.io/akx/pen/JyemQQ?editors=0010)
Hope this helps you along.