When using functional component, dropdown re-render every time I set a state and the value in the dropdown does not change.
Edit:
I am using semantic ui react library. (https://react.semantic-ui.com/collections/form/#shorthand-subcomponent-control)
const Table = () => {
const [tickers, setTickers] = useState("")
const test = [{
key: 1,
text: "0001",
value: "0001"
},{
key: 2,
text: "0002",
value: "0002"
}]
const DropdownSelection = () => {
return (
<Form.Select
fluid
search
multiple
name="ticker"
options={test}
onChange={(e, { value }) => setTickers(value)}
placeholder="Tickers"
/>
)
}
return(
<Form>
<DropdownSelection />
</Form>)
However, it works when I put the component inside the return function. Although dropdown still being re-rendered, but the value is in it.
const Table = () => {
const [tickers, setTickers] = useState("")
const test = [{
key: 1,
text: "0001",
value: "0001"
},{
key: 2,
text: "0002",
value: "0002"
}]
return(
<Form>
<Form.Select
fluid
search
multiple
name="ticker"
options={test}
onChange={(e, { value }) => setTickers(value)}
placeholder="Tickers"
/>
</Form>)
What could be the problem?
Related
I have an excersise app which I am trying to select exercises that are to be recorded for a workout. For what I have so far, I generate a list which can select a muscle category which then displays a list of excersises to be added to the workout. I would like the the options array of a specific muscle category to be generated from a seperate .js file of said category (AbsExerciseList.js)
//AbsExerciseList.js
const abList = [
{ id: '1', label: 'Crunches' },
{ id: '2', label: 'Leg Raises' }
]
export default abList
I then have the supOptions property - which is the list of exercises generated after the category is selected - appear. How would I take the object from AbsExerciseList.js and insert it to the subOptions object/array (specifically for the id: '1' , label: 'abs') element?
I would like to do the same for all other muscle categories as well.
//New WorkourtList.js
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, FlatList } from 'react-native';
import abList from './exercises/AbsExerciseList';
const MyDropDown = () => {
const [selectedOption, setSelectedOption] = useState(null);
const [showOptions, setShowOptions] = useState(false);
const [options, setOptions] = useState([
{
id: '1',
label: 'Abs',
subOptions: [
//Place abList from AbsExerciseList.js here
]
},
{
id: '2',
label: 'Biceps',
subOptions: [
{ id: '1', label: 'Preacher Curl' },
{ id: '2', label: 'EZ-Bar Curl' },
{ id: '3', label: 'Alternating Dumbell Curl' }
]
}
//... rest of muscle categories not listed
]);
const handleOptionSelect = (option) => {
setSelectedOption(option);
setShowOptions(false);
};
const renderOption = ({ item }) => (
<TouchableOpacity style={{ padding: 10 }} onPress={() => handleOptionSelect(item)}>
<Text>{item.label}</Text>
</TouchableOpacity>
);
const renderSubOption = ({ item }) => (
<TouchableOpacity style={{ padding: 10 }}>
<Text>{item.label}</Text>
</TouchableOpacity>
);
return (
<View>
<TouchableOpacity onPress={() => setShowOptions(!showOptions)}>
<Text>{selectedOption ? selectedOption.label : 'Select a Category'}</Text>
</TouchableOpacity>
{showOptions && (
<FlatList
data={options}
renderItem={renderOption}
keyExtractor={(item) => item.id}
/>
)}
{selectedOption && (
<FlatList
data={selectedOption.subOptions}
renderItem={renderSubOption}
keyExtractor={(item) => item.id}
/>
)}
</View>
);
};
export default MyDropDown;
I have tried using the map function within the useState() however i am met with, "Warning: Each child in a list should have a unique "key" prop."
I am not sure if I need to create a seperate function outside of useState() or use a different React hook.
If you save all sub Options seperate, then it could look like this:
const abList = [
{ id: "1", label: "Crunches" },
{ id: "2", label: "Leg Raises" }
];
const bicepsList = [
{ id: "1", label: "Preacher Curl" },
{ id: "2", label: "EZ-Bar Curl" },
{ id: "3", label: "Alternating Dumbell Curl" }
];
Then in your functional component, since you are not updating your initial "options" state, you can just alter your "selectedOption" state and append sub Otions into respective array.
Just alter the select handler like this:
const handleOptionSelect = (option) => {
switch (option.id) {
case "1": {
setSelectedOption({
...option,
subOptions: option.subOptions.concat(abList)
});
break;
}
case "2": {
setSelectedOption({
...option,
subOptions: option.subOptions.concat(bicepsList)
});
break;
}
default: {
setSelectedOption({
...option,
subOptions: []
});
break;
}
}
setShowOptions(false);
};
Benefit of option.subOptions.concat(anyList) is you can have default exercises from "options" state already set and append more exercies.
In React project I've certain list of data populated in grid component. The grid has columns of varied data types like text, input field, link etc. In one such column an input field is mapped which has its respective default values.
Now my intention is to change name of that particular input field of a particular record which navigates to another page which, has a button when clicked changes the name on the grid, but, the name of all records are changed. I need to change name of only that single record which is clicked and navigated based on its 'id'. Please refer to the code below.
const newCompData = [
{
id: 1,
comp: "McDonalds",
feedback: "Best Food Chain",
name: "Mike John",
est: "YYYY/MM",
store: "Burger Store"
},
{
id: 2,
comp: "KFC",
feedback: "Best Chicken Products",
store: "Chicken Food",
name: "Steve Williams",
est: "YYYY/MM"
},
{
id: 3,
comp: "Dominos",
feedback: "Best Pizza Store",
store: "Pizza Store",
name: "Mark Rays",
est: "YYYY/MM"
},
{
id: 4,
comp: "Star Bucks",
feedback: "Best Coffee Store",
store: "Coffee Store",
name: "Patrick Right",
est: "YYYY/MM"
},
{
id: 5,
comp: "Burger King",
feedback: "Best Burgers",
store: "Burger Store",
name: "Williams Wills",
est: "YYYY/MM"
},
{
id: 6,
comp: "Lays",
feedback: "Best Chips Factory",
store: "Chips Store",
name: "Sam Andrews",
est: "YYYY/MM"
}
];
const [dataAll, setDataAll] = useState([]);
useEffect(() => {
const newData = newCompData?.map((data) => {
return [
{ id: data.id },
data.comp,
data.store,
data.name,
data.est,
data.feedback
];
});
setDataAll(newData);
}, []);
return (
<div className="App">
<Table newData={dataAll} />
</div>
);
};
As seen above.... a list of records are mapped into the table component
Here is the table component, where all the data is mapped
const Table = ({ newData }) => {
useEffect(() => {
setGridData({
data: newData,
page_info: {
total_pages: 5,
current_page: 1
}
});
}, [newData]);
let GridConfig = {};
GridConfig = TableConfig;
const [gridConfigData, setGridConfigData] = useState(GridConfig);
const [gridData, setGridData] = useState(newData);
return (
<>
<Grid GridConfig={gridConfigData} GridData={gridData} />
</>
);
};
Following is the Grid component in table format (only showing the required part)
const Grid = (props) => {
let colConfig = props.GridConfig.column_config;
let gridData = props.GridData?.data;
const { newValue } = useContext(GlobalContext);
const navigate = useNavigate();
return (
....
....
....
{colConfig[cIndex].data_type === "input_text" &&
!colConfig[cIndex].cell_click_callback && (
<input
type="text"
defaultValue={
newValue != undefined ? newValue : colData
}
/>
)}
....
....
....
)
}
export default Grid
This is the page when clicked on record is navigated here.
const Test = () => {
const location = useLocation();
const navigate = useNavigate();
const newId = location.state?.id;
const { setNewValue } = useContext(GlobalContext);
const handleClick = () => {
navigate("/");
// When clicked on this setValue shows the same value across all records
setNewValue("John Spencer");
};
return (
<>
<h2>Test Page</h2>
<p>ID: {newId}</p>
<button onClick={handleClick}>Go Back</button>
</>
);
};
I want to show the value for input text which was clicked on Test page. Only specific record should be updated. What is the best solution to tackle this issue?
Please refer to the Codesandbox link: https://codesandbox.io/s/elated-varahamihira-xpjtdb
I'm using Material-Table in a React app.
I enabled filtering, and after, I used Material-Table's filterComponent.
I have problems with retrieving the value from Column1FilterComponent custom component or pass down a function which can trigger a state change, and after it could trigger a fetch.
I placed my setFetchState(...) function to the point I'd like it to be.
Here is an example:
https://codesandbox.io/s/material-table-playground-forked-simple-q05vhf
Here are my questions:
How to get value from a filterComponent?
How to pass a function to filterComponent?
export function SomethingTable(props) {
const { fetchState, setFetchState } = props;
const [randomData, setRandomData] = useState([
{ filename: "1", another: "two" },
{ filename: "2", another: "three" },
{ filename: "3", another: "four" },
{ filename: "4", another: "five" },
{ filename: "5", another: "six"
}
]);
return (
<MaterialTable
title="Something"
columns={[
{
title: "Column1",
field: "filename",
type: "numeric",
width: "10%",
filterComponent: (props) => <Column1FilterComponent {...props} />
},
{ title: "Column2", field: "another" }
]}
data={randomData}
/*onFilterChange={(filters) => {
console.log("filters", filters);
}}*/
options={{
filtering: true
}}
/>
);
}
function Column1FilterComponent(props) {
const [value, setValue] = useState("");
return (
<TextField
id="area-text"
value={value}
onChange={(event) => {
setValue(event.target.value);
//My set function:
// setFetchState(event.target.value);
props.onFilterChanged(props.columnDef.tableData.id, event.target.value);
}}
/>
);
}
EDIT: Your solution worked but now my dropdown doesn't show what is selected unless its selected twice, like this:
Here I selected Health Care, but the dropdown still says "Select Industry"
The here I selected Health Care for a second time and it now shows that it is selected, this is happening with all of the dropdown options
I have a dropdown menu that I can select an option from, which saves the option to a variable. I want to use this variable to change the fetch route used in my useEffect hook so that the data found is filtered to a specific industry. How would I do this? Is there an easier way to filter the mapped data rather than changing the fetch address?
This is my code:
import React, {useState, useEffect} from "react";
import { AgGridReact } from "ag-grid-react";
import { Dropdown } from "semantic-ui-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-balham.css";
// Data Table
export default function Table() {
const[rowData, setRowData] = useState([]);
const columns = [
{ headerName: "Name", field: "name", sortable: true},
{ headerName: "Symbol", field: "symbol", sortable: true},
{ headerName: "Industry", field: "industry", sortable: true},
];
const searchIndustry = (event, {value}) => {
let industryChoice = value;
// I want to use this variable
console.log(industryChoice);
}
const DropdownSearchSelection = () => (
<Dropdown
placeholder="Select Industry"
search
selection
options={industryOptions}
onChange={searchIndustry}
/>
);
useEffect(() => {
// To change this address
fetch(`http://131.181.190.87:3000/stocks/symbols${industryChoice}`)
.then(res => res.json())
.then(data =>
data.map(stock => {
return {
name: stock.name,
symbol: stock.symbol,
industry: stock.industry,
};
})
)
.then(stock => setRowData(stock));
}, []);
const industryOptions = [
{ key: "as", value: "", text: "View All Industries" },
{ key: "dc", value: "?industry=consumer%20discretionary", text: "Consumer Discretionary" },
{ key: "ds", value: "?industry=consumer%20staples", text: "Consumer Staples" },
{ key: "en", value: "?industry=energy", text: "Energy" },
{ key: "fi", value: "?industry=einancials", text: "Financials" },
{ key: "hc", value: "?industry=health%20care", text: "Health Care" },
{ key: "in", value: "?industry=industrials", text: "Industrials" },
{ key: "it", value: "?industry=information%20technology", text: "Information Technology" },
{ key: "ma", value: "?industry=materials", text: "Materials" },
{ key: "re", value: "?industry=real%20estate", text: "Real Estate" },
{ key: "ts", value: "?industry=telecommunication%20services", text: "Telecommunication Services" },
{ key: "ut", value: "?industry=utilities", text: "Utilities" },
];
return (
<div className="filter">
<div>
<input type="text" id="searchBox" placeholder="Filter..."/>
<DropdownSearchSelection />
<br/>
</div>
<div className="ag-theme-balham" >
<AgGridReact
columnDefs={columns}
rowData={rowData}
pagination={true}
paginationPageSize={11}
/>
</div>
</div>
);
}
Thanks for the help! :)
First, you will need to store the selected option from Dropdown into your component's state.
const[industryChoice, setIndustryChoice] = useState();
And on your searchIndustry method,
const searchIndustry = (event, {value}) => {
setIndustryChoice(value);
}
And then, you add searchIndustry as part of the dependency array, which will fetch the data based on the industryChoice state, and this will be triggered whenever the value of industryChoice is updated.
useEffect(() => {
// To change this address
fetch(`http://131.181.190.87:3000/stocks/symbols${industryChoice}`)
.then(res => res.json())
.then(data =>
data.map(stock => {
return {
name: stock.name,
symbol: stock.symbol,
industry: stock.industry,
};
})
)
.then(stock => setRowData(stock));
}, [industryChoice]);
As for the additional issue you have raised, you should bind the value of the industryChoice state to the Dropdown.
<Dropdown
placeholder="Select Industry"
search
selection
options={industryOptions}
onChange={searchIndustry}
value={industryChoice}
/>
so I have the following component that is a dropdown list created using react-select.
import React from 'react'
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
];
class MealsFilters extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
};
}
handleChange = (selectedOption) => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
}
render() {
const { selectedOption } = this.state;
return (
<div className="container my-3">
<div className="row">
<div className="col-lg-4 col-md-6 col-sm-8">
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
</div>
</div>
</div>
)
}
}
export default MealsFilters;
the options variable is the default one from the docs. I actually need to replace its values by each meal category available.
To do so, as you can see, I need to create an array of objects with a value and a label.
this component accesses meal categories through props called meals that are like so:
console.log(this.props.meals);
=> [{
id: 0,
name: spaghettis,
category: italian,
price: 5.99},
{
id: 1,
name: hamburger,
category: american,
price: 7.99},
{
etc.
}, {}]
How can I take advantage of this.props.meals to get my options array of objects ?
EDIT: multiple meals can have the same category, and I need each category to only appear once in the options.
Map over your this.props.meals array, and create the needed options array,
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={this.props.meal.map(item=>({value: item.id, label: item.name}))}
/>
You could do something like this:
options={this.props.meals.map(
({id, name})=>({value:id,label:name})
)}
You could also use redux connect to create a container that will map the data to dropdown values for you
You can merge the data by category in the following way:
var items = [
{
id: 0,
name: 'spaghettis',
category: 'italian',
price: 5.99,
},
{
id: 1,
name: 'hamburger',
category: 'american',
price: 7.99,
},
{
id: 2,
name: 'other hamburger',
category: 'american',
price: 7.99,
},
];
console.log(
[
...items.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value })),
);
In the component it'll look like this:
options={[
...this.props.meals.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value }))}
You only need the "name" property so when you map through meals, simply retrieve it. Then upper case the first letter.
const meals = [{
id: 0,
name: "spaghettis",
category: "italian",
price: 5.99
},
{
id: 1,
name: "hamburger",
category: "american",
price: 7.99
}
]
const result = meals.map(({name}) => ({
label: `${name[0].toUpperCase()}${name.slice(1)}`,
value: name
}))
console.log(result);
You can use getOptionLabel and getOptionValue props.
<Select
options={this.props.meals},
getOptionLabel={m => m.name}
getOptionValue={m => m.id} />
https://react-select.com/props
getOptionLabel generic = (option) => string
Resolves option data to a string to be displayed as the label by components
getOptionValue generic = (option) => string
Resolves option data to a string to compare options and specify value attributes