I created a child component by react-select, but options don't show up in first click on selector.
For the second time each section is clicked, the options are displayed. I tried to use the AsyncSelect but again it did not work.
The Data is read from Local Storage, but I don't think there is a problem with this.
Sandbox:
https://codesandbox.io/s/strange-field-4elv3?file=/src/App.js
My data from local storage:
const feauters = [
{
"id": 5,
"title": "Type",
"options": [
{
"id": 231040,
"name": "cloth",
"property": 5
},
{
"id": 230081,
"name": "Synthetic materials",
"property": 5
}
]
},
{
"id": 646,
"title": "Shoe soles",
"options": [
{
"id": 231063,
"name": "Abrasion resistant",
"property": 646
},
{
"id": 231064,
"name": "Reduce the pressure",
"property": 646
}
]
},
]
Parent Component:
<MultiSelect features={features} />
My Component:
import React, {useEffect, useState} from 'react';
import {Form} from 'react-bootstrap';
import Select from 'react-select';
const MultiSelect = ({features}) => {
// Declare States
const [selectors, setSelectors] = useState([]);
// Handle Features
useEffect(() => {
const initialSelectors = features.map((item, index, array) => {
const options = item.options.map((subItem) => {
return {
value: `${subItem.property}-${subItem.id}`,
label: subItem.name,
};
});
return (
<React.Fragment key={`product-multiselect-${index}-${item.id}`}>
<Form.Label htmlFor={`product-multiselect-${index}-${item.id}`}>{item.title}</Form.Label>
<Select
id={`product-multiselect-${index}-${item.id}`}
className="mb-2"
classNamePrefix="select"
defaultInputValue="Select..."
placeholder="Select..."
noOptionsMessage={() => 'Not Found.'}
isMulti
isClearable
isRtl
isSearchable
name={item.title}
onChange={handleChangeInput}
options={options}
/>
</React.Fragment>
);
});
setSelectors(initialSelectors);
}, [features]);
// Handle Change Input
const handleChangeInput = (values) => {
console.log(values);
};
return selectors;
};
export default MultiSelect;
First of all as mentioned in the comments you shouldn't store the component inside the state. Related question
Secondary, options don't show up because of defaultInputValue props. If you remove it, the component would work as intended
Related
I am trying to use mui Autocomplete multi select component but can't make it work as per my requirements. I want it to have default selected values which will be passed as a props from Parent component and dropdown options which I will be getting via an API.
Problem 1 :
I am able to render my deafult values in input box which was passed as props but my selected values should not be there in options. Right now I can add duplicate values meaning even though I have few items selected by default in input box but still I could see them in dropdown. I have tried to read the docs it says The value must have reference equality with the option in order to be selected. but can't understand clearly. Is it because I am using 2 different states(arrays) for value and options?.
Problem 2 (Extra requirement)
In addition to the list of usersOptions that I will get from API to populate the dropdown option, I also need to add an option of All.
Whenever few items are already selected and then user clicks ALL in dropdown all the selected items should be removed and only All should be there in input box and when user tries to open the dropdown, all other values should be disabled.
When the user deselect ALL from the dropdown then only he/she should be able to select individual value(user)
Basically the user should be able to select only ALL or Individual user(value).
Link to CodeSanbox(https://codesandbox.io/s/naughty-elbakyan-hi16x5?file=/src/AutoComplete.js:0-1399)
import { useEffect, useState } from "react";
//This is the structure of data for both users & usersOptions
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
// fetch("https://x.com/api/getUsers")
// .then((response) => response.json())
// .then((data) => setUsersOptions(data));
//let's assume API returned this
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
const usersOptionsWithAll = [{ id: 0, name: "All" }, ...usersOptions];
setUsersOptions(usersOptionsWithAll);
}, []);
console.log("selectedUsers", selectedUsers);
console.log("usersOptions", usersOptions);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
setSelectedUsers(newValue);
}}
/>
);
};
export default AutoComplete;
[1]: https://i.stack.imgur.com/83V7d.jpg
1. You should use isOptionEqualToValue prop to determine if the option represents the given value.
2. It is preferable to use filterOptions prop to add 'All' option or any options you want to add.
import { Autocomplete, TextField, createFilterOptions } from "#mui/material";
import { useEffect, useState } from "react";
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
setUsersOptions(usersOptions); // Set options without adds
}, []);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
// Check if 'All' option is clicked
if (newValue.find(option => option.id === 0)) {
// Check if all options are selected
if (usersOptions.length === selectedUsers.length) {
setSelectedUsers([]);
} else {
setSelectedUsers(usersOptions);
}
} else {
setSelectedUsers(newValue);
}
}}
// Add These props
isOptionEqualToValue={(option, value) => option.id === value.id}
filterOptions={(options, params) => {
const filtered = createFilterOptions()(options, params);
return [{ id: 0, name: 'All' }, ...filtered];
}}
/>
);
};
export default AutoComplete;
Mui Autocomplete API
I have an array when I fetch from the API by using axios get request as follows:
useEffect(async () => {
console.log("here");
let accessToken = await AsyncStorage.getItem("accessToken");
const response = await axios
.get(API_URL + "/feed", {
headers: {
Authorization: "Bearer " + accessToken,
},
})
.then((response) => {
setFeedItems([]);
setFeedItems((feedItems) => [...feedItems, ...response.data]);
setIsLoading(false);
});
}, []);
I have a custom component which is Report.js and I want to send some information from this screen to that component by using the following code:
{isLoading == false && (
<FlatList
style={{ marginLeft: 10, marginRight: 10 }}
data={feedItems}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Report
name="mustafa"
username="mustafa123"
responsibleInstitution="responsible"
userId={item.userId}
category={item.category}
location={item.location}
institutionId={item.institutionId}
description={item.description}
upvotes={item.upvotes}
comments={item.comments}
/>
)}
></FlatList>
The shape of the data that is coming from the API is as follows:
[
{
"id": "6228a72cfc2ce87bb0b5f908",
"userId": "61cab704ee5f9a5cc3bd844c",
"institutionId": "61cabb2da10a9147a53e6480",
"solutionId": null,
"description": "Kayıp ilanı..",
"category": "Missing",
"comments": [
{
"id": "6228c0933ab2f166af0a9d23",
"userId": "61cab704ee5f9a5cc3bd844c",
"text": "Tamam kardeş anladık",
"date": "2022-03-09T14:58:27.091+00:00"
},
{
"id": "6228c98534572422056eb565",
"userId": "61cab704ee5f9a5cc3bd844c",
"text": "Tamam kardeş anladık 3",
"date": "2022-03-09T15:36:37.256+00:00"
}
],
"upvotes": [
"61cab704ee5f9a5cc3bd844c"
],
"location": null,
"report_image_link": null,
"file": null,
"date": "2022-03-09T13:10:04.273+00:00"
},
As you can see from the data, the 'comments' field has an array of objects with id, userId, text, and date fields. Whenever I run the code, I get the following error which is caused by the comments={item.comments} line.
The error: Objects are not valid as a React child (found: object with keys {id, userId, text, date}). If you meant to render a collection of children, use an array instead.
What I want to do is that, whenever user click a button in the Report.js component, I want to open up a modal and present the comments to the user on that component. Do you think I should change my way? How can I send the comments information to the Report component? If my approach is incorrect, what should I do?
Since comments is an array of objects, you cannot pass it as a child to a Text component. Now, it depends on how you want to visualize your data. Here is a possible solution using FlatList. You could replace the rendered component with whatever suits you the best.
const dummyData = [
{
"id": "6228c0933ab2f166af0a9d23",
"userId": "61cab704ee5f9a5cc3bd844c",
"text": "Tamam kardeş anladık",
"date": "2022-03-09T14:58:27.091+00:00"
},
{
"id": "6228c98534572422056eb565",
"userId": "61cab704ee5f9a5cc3bd844c",
"text": "Tamam kardeş anladık 3",
"date": "2022-03-09T15:36:37.256+00:00"
}
]
const App = () => {
const [data, setData] = useState(dummyData);
return (
<SafeAreaView style={{ flex: 1 }}>
<View>
<FlatList
data={data}
renderItem={({item}) => {
return <Text style={{margin: 20}}>`ID: ${item.id} userId: ${item.userId} text: ${item.text} date: ${item.date}`</Text>
}}
keyExtractor={(item, index) => index.toString()}
/>
</View>
</SafeAreaView>
);
};
I recursively mapped nested JSON, and console log output all elements in format: property => value correctly, but components do not render. Following is JSON:
{
"index": "dwarf",
"name": "Dwarf",
"speed": 25,
"ability_bonuses": [
{
"ability_score": {
"index": "con",
"name": "CON",
"url": "/api/ability-scores/con"
},
"bonus": 2
}
],
"alignment": "Most dwarves are lawful, believing firmly in the benefits of a well-ordered society. They tend toward good as well, with a strong sense of fair play and a belief that everyone deserves to share in the benefits of a just order.",
"age": "Dwarves mature at the same rate as humans, but they're considered young until they reach the age of 50. On average, they live about 350 years.",
"size": "Medium",
"size_description": "Dwarves stand between 4 and 5 feet tall and average about 150 pounds. Your size is Medium.",
"starting_proficiencies": [
{
"index": "battleaxes",
"name": "Battleaxes",
"url": "/api/proficiencies/battleaxes"
},
{
"index": "handaxes",
"name": "Handaxes",
"url": "/api/proficiencies/handaxes"
},
{
"index": "light-hammers",
"name": "Light hammers",
"url": "/api/proficiencies/light-hammers"
},
{
"index": "warhammers",
"name": "Warhammers",
"url": "/api/proficiencies/warhammers"
}
],
"starting_proficiency_options": {
"choose": 1,
"type": "proficiencies",
"from": [
{
"index": "smiths-tools",
"name": "Smith's tools",
"url": "/api/proficiencies/smiths-tools"
},
{
"index": "brewers-supplies",
"name": "Brewer's supplies",
"url": "/api/proficiencies/brewers-supplies"
},
{
"index": "masons-tools",
"name": "Mason's tools",
"url": "/api/proficiencies/masons-tools"
}
]
},
"languages": [
{
"index": "common",
"name": "Common",
"url": "/api/languages/common"
},
{
"index": "dwarvish",
"name": "Dwarvish",
"url": "/api/languages/dwarvish"
}
],
"language_desc": "You can speak, read, and write Common and Dwarvish. Dwarvish is full of hard consonants and guttural sounds, and those characteristics spill over into whatever other language a dwarf might speak.",
"traits": [
{
"index": "darkvision",
"name": "Darkvision",
"url": "/api/traits/darkvision"
},
{
"index": "dwarven-resilience",
"name": "Dwarven Resilience",
"url": "/api/traits/dwarven-resilience"
},
{
"index": "stonecunning",
"name": "Stonecunning",
"url": "/api/traits/stonecunning"
},
{
"index": "dwarven-combat-training",
"name": "Dwarven Combat Training",
"url": "/api/traits/dwarven-combat-training"
},
{
"index": "tool-proficiency",
"name": "Tool Proficiency",
"url": "/api/traits/tool-proficiency"
}
],
"subraces": [
{
"index": "hill-dwarf",
"name": "Hill Dwarf",
"url": "/api/subraces/hill-dwarf"
}
],
"url": "/api/races/dwarf"
}
Then this is code:
import React, {Component} from 'react'
import { Grid, Header, Label } from 'semantic-ui-react'
import TypeComponent from './type_component'
import TestComponent from './test_component'
class raceWindow extends Component {
constructor(props)
{
super(props)
this.state = {
data: {}
}
}
componentDidMount()
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
componentDidUpdate(prevProps)
{
if(this.props.hdAPI !== prevProps.hdAPI)
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
}
isType (attr, value)
{
if(Array.isArray(value))
{
value.map((v) => {
Object.entries(v).map(([a1,v1]) => this.isType(a1,v1))
})
}
else
{
if(typeof value === 'object')
{
Object.entries(value).map(([a,v]) => this.isType(a,v))
}
else
{
console.log(attr);
console.log(value);
return(<Grid.Column><Label>{attr}</Label>{value}</Grid.Column>);
}
}
};
render()
{
const { data} = this.state;
//I also tried to do the recursive map in a component, but it does not work either
/*
return(
<div>
<Grid container columns = {10}>
<TypeComponent attr = {""} value = {data} />
</Grid>
</div>
);*/
return(
<div>
<Grid container columns = {10}>
{this.isType("",data)}
</Grid>
</div>
);
}
}
export default raceWindow
When I tried to map it manually in the first layer, only first layer components rendered. Nested data in deeper layers do not render, though console output the deeper data correctly. So I assume react do not render deeper component. How should I deal with this?
Edit: Just note, I may be wrong, but I think every loop of isType() will go to this part of code at last:
else
{
console.log(attr);
console.log(value);
return(<Grid.Column><Label>{attr}</Label>{value}</Grid.Column>);
}
console also output all non-array, non-object value in the log, but return() in this block does not render.
First let's look at the main problems you have right now:
The if blocks inside your isType function do not return anything. You are running functions against the value argument, but then you're not doing anything with what those functions return. Fixing this can be as simple as changing value.map... to value = value.map..., but if you don't do something, then when you get to the return at the end, those if blocks will have no impact on the final result.
Inside the first if block, your value.map... function doesn't return anything since you have brackets around Object.entries.... You just need to return Object.entries..., or else remove the brackets.
If we fix those problems and simplify the if/else logic a bit, we end up getting something back in the JSX:
isType(attr, value) {
let returnValue = value;
if (Array.isArray(returnValue)) {
returnValue = value.map((v) => {
return Object.entries(v).map(([a1, v1]) => this.isType(a1, v1));
});
} else if (typeof returnValue === "object") {
returnValue = Object.entries(value).map(([a, v]) => this.isType(a, v));
}
return (
<Grid.Column>
<Label>{attr}</Label>
{returnValue}
</Grid.Column>
);
}
But you'll see when you get this far that the resulting DOM probably isn't what you want.
Instead, I recommend changing the approach a bit so that we first convert our JSON into a structure we can use and then map over it in our render function. This will hopefully also make it a little easier to reason about what is happening when and to modify your function to only add things to the final output that you actually care about.
That'd look something like this:
class RaceWindow extends Component {
constructor(props) {
super(props);
// your JSON; store in state if necessary
this.data = {};
// create a placeholder variable
this.finalDom = [];
// call `this.isType` to fill in that variable
// the result is a giant array of shape {attr: 'str', value: 'str'}
this.isType("", this.state.data);
}
isType(attr, value) {
if (typeof value === "string") {
this.finalDom.push({
attr,
value
});
}
if (Array.isArray(value)) {
this.finalDom.push({
attr,
value: value.map((v) => {
return Object.entries(v).map(([a1, v1]) => this.isType(a1, v1));
})
});
}
if (typeof value === "object") {
this.finalDom.push({
attr,
value: Object.entries(value).map(([a, v]) => this.isType(a, v))
});
}
}
render() {
return (
<div>
<div class="container">
{this.finalDom.map((obj) => (
<div class="item">
<div class="label">{obj.attr}</div>
{obj.value}
</div>
))}
</div>
</div>
);
}
}
CodeSandbox demo of the above.
Final note: Make sure you capitalize the first letter of your component, or else React won't recognize it as a component.
Following is working code:
import React, {Component} from 'react'
import { Grid, Header, Label } from 'semantic-ui-react'
import TypeComponent from './type_component'
import TestComponent from './test_component'
class raceWindow extends Component {
constructor(props)
{
super(props)
this.state = {
data: {}
}
}
componentDidMount()
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
componentDidUpdate(prevProps)
{
if(this.props.hdAPI !== prevProps.hdAPI)
{
fetch(this.props.hdAPI)
.then(response=>response.json())
.then(data => {this.setState({data: data})});
this.setState({hdAPI: this.props.hdAPI});
}
}
isType (attr, value, obj)
{
if(Array.isArray(value))
{
value.map((v) => {
Object.entries(v).map(([a1,v1]) => this.isType(a1,v1,obj))
})
}
else
{
if(typeof value === 'object')
{
Object.entries(value).map(([a,v]) => this.isType(a,v,obj))
}
else
{
var pair = {}
pair[attr] = value
obj.push(pair);
}
}
};
render()
{
const { data} = this.state;
var obj = [];
return(
<div>
<Grid container columns = {5}>
{this.isType("",data,obj)}
{
obj.map((arr, i) => {
return(
Object.entries(arr).map(([a,v]) => {
return(
<Grid.Column key = {i}><Label key = {i} color = 'orange'>{a}</Label>{v}</Grid.Column>
);
})
);
})
}
</Grid>
</div>
);
}
}
export default raceWindow
Thanks cjl750's advice, I use an variable to collect results from recursive function and it works. I also tried to let very if block has a return as his advice, but components still do not render. Not sure why.
For now, it seems jsx returned from deep layer in recursive function do not render.
Very new to React, so I might be approaching this the wrong way... I want my app to take input from a text input field, retrieve a JSON from the reddit API (the url is built from the text input), and then render data from the JSON, looping through each of the entries. I'm using useState to trigger the data render. I can successfully retrieve the data and output specific values, but I want to be able to have a loop that dynamically outputs the data into various HTML elements.
Here's what I have so far that allows me to output some specific values as an example:
import React, { useState } from 'react';
const App = () => {
const [retrievedData, setRetrievedData] = useState([])
const runSearch = async() => {
const searchInput = document.getElementById('searchInput').value
const searchUrl = 'https://www.reddit.com/r/' + searchInput + '/new/.json?limit=5'
const response = await fetch(searchUrl)
const redditResponse = await response.json()
setRetrievedData(<>
<p>{JSON.stringify(redditResponse.data.children[0].data.author)}</p>
<p>{JSON.stringify(redditResponse.data.children[0].data.title)}</p>
</>)
}
return (
<>
<section>
<input type="text" id='searchInput' placeholder='Enter a subreddit...'></input>
<button onClick={runSearch}>
Get Data
</button>
<div>{retrievedData}</div>
</section>
</>
);
};
export default App;
And here's an example of the JSON that is retrieved from the reddit API, just stripped down with only the example values I use in my code above:
{
"kind": "Listing",
"data": {
"modhash": "",
"dist": 5,
"children": [
{
"kind": "t3",
"data": {
"author": "author1",
"title": "title1"
}
},
{
"kind": "t3",
"data": {
"author": "author2",
"title": "title2"
}
},
{
"kind": "t3",
"data": {
"author": "author3",
"title": "title3"
}
},
{
"kind": "t3",
"data": {
"author": "author4",
"title": "title4"
}
},
{
"kind": "t3",
"data": {
"author": "author5",
"title": "title5"
}
}
],
"after": "t3_jnu0ik",
"before": null
}
}
I just need the final rendered output to be something like:
<h2>TITLE 1</h2>
<h4>AUTHOR 1</h4>
<p>SELFTEXT 1</p>
...and repeated for each post data that is retrieved.
I've seen a variety of different ways to render JSON data and many of them show either loops and/or the .map() method, but I can't ever seem to get those to work, and wonder if it's an issue with the useState. Perhaps there is some way I should be rendering the data some other way?
You don't need set jsx to state, you can directly iterate children data with map
Try this
const App = () => {
const [retrievedData, setRetrievedData] = useState([])
const runSearch = async() => {
const searchInput = document.getElementById('searchInput').value
const searchUrl = 'https://www.reddit.com/r/' + searchInput + '/new/.json?limit=5'
const response = await fetch(searchUrl)
const redditResponse = await response.json()
if (redditResponse.data.children && redditResponse.data.children.length) {
setRetrievedData(redditResponse.data.children)
}
}
return (
<>
<section>
<input type="text" id='searchInput' placeholder='Enter a subreddit...'></input>
<button onClick={runSearch}>
Get Data
</button>
<div>
{
retrievedData.map((children, index) => {
return (
<div key={children.data.author + index}>
<div>Kind: { children.kind }</div>
<div>Author: { children.data.author }</div>
<div>Title: { children.data.title }</div>
</div>
)
})
}
</div>
</section>
</>
);
};
I've been struggling with this all day. Really new to react so apologies in advanced.
I'm trying to use react-jsonschema-form to create form from json with react-color.
This is what I have now:
import React, {Component} from "react";
import {ChromePicker} from 'react-color';
import Form from "react-jsonschema-form";
const ColorPicker = (props) => {
return (
<ChromePicker
color={props.value || false}
value={props.value}
onChange={(event) => {
props.onChange(event.color)
}}
/>
)
}
const schema = {
"type": "object",
"properties": {
"base": {
"type": "object",
"title": "Global settings",
"properties": {
"line-height": {
"title": "Body line height",
"type": "number"
},
"background-color": {
"title": "Body background color",
"type": "string"
},
"font-color": {
"title": "Body background color",
"type": "string"
}
}
}
}
}
const uiSchema = {
base:{
"background-color":{
'ui:widget':ColorPicker
},
"font-color":{
'ui:widget':ColorPicker
}
}
}
export default class GeneratorForm extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(data) {
console.log(data)
}
render() {
return (
<Form schema={schema}
uiSchema={uiSchema}
//onBlur={this.handleSubmit}
onChange={this.handleChange}
//onSubmit={this.handleSubmit}
onError={log("errors")}/>
);
}
}
Simple form with two colorpickers. And the colorpicker works great, except in the handleChange, formData for that field is empty. It's like the value isn't assigned. I've searched for similar topics, somethings similar with datepicker3 and that gave me an idea to make it simple (had some crazy code, custom class component and so on).
So the question is, how to pass a value from color picker to form value?
Thanks in advance.
You should take a look at the Docs for react-color. You aren't handling the onChange event correctly. It should be:
const ColorPicker = (props) => {
return (
<ChromePicker
color={props.value || false}
value={props.value}
onChange={ color => {
props.onChange(color)
}}
/>
)
}
event.color doesn't exist. The onChange handler will get two arguments, color and event.