I have a view that can be called both for creating new entry and updating an existing one.
When Im creating a brend new post, eveything works as expected.
When editing an existing post, the select box for regions is filled AND the current region is selected.
However, for the cities, the select box contains all the cities that belong to the region. But at the same time, the current city is not selected.
Both in the console and in the html-span I can see that the id is correct. But inside the option nothing happens.
EDIT
Full code that works for testing.
In package.json:
...
"react": "18.0.0",
"react-dom": "18.0.0",
"react-router-dom": "6.3.0",
"react-scripts": "4.0.0"
In App.js:
<Route path="test" element={<Test />} />
In Test.js
import React, {useState, useEffect} from "react";
export const Test = () => {
const [name, setName] = useState('');
const [city, setCity] = useState('');
const [region, setRegion] = useState('');
const [regions, setRegions] = useState([]);
const [cities, setCities] = useState([]);
const [fetchingCities, setFetchingCities] = useState(false);
const getCities = async () => {
return [
{_id: 1, region: 'region1', cities: [{id: '6293609d86af877f09bb9bc1', name: 'city1'},{id: '6293609d86af877f09bb9bc2', name: 'city2'}]}
,{_id: 2, region: 'region2', cities: [{id: '6293609d86af877f09bb9bc3', name: 'city3'},{id: '6293609d86af877f09bb9bc4', name: 'city4'}]}
]
}
const getAllCities = async () => {
const all = await getCities();
setRegions(all);
const form = {
region: 1,
city: '6293609d86af877f09bb9bc1',
name: 'testname'
};
if(form){
setName(form.name);
const cts = all.filter(e => e._id === Number(form.region));
if(cts && cts.length && cts[0].cities){
setCities(cts[0].cities);
setRegion(form.region);
setCity(form.city);
console.log('form.city',form.city);
}
}
}
const citySelected = (value) => {
console.log("value",value);
setCity(value);
}
const cityChanged = (value) => {
console.log("cityChanged",value);
const cts = regions.filter(e => e._id === Number(value));
console.log("cts",cts);
if(cts && cts.length && cts[0].cities){
setCities(cts[0].cities);
setRegion(value);
}
}
useEffect(() => {
if(!fetchingCities){
setFetchingCities(true);
getAllCities();
}
}, []);
return (
<>
<div>
<div>
<input type="text" defaultValue={name} onChange={e => setName(e.target.value)}/>
</div>
<div>
{
regions && regions.length > 0 &&
<select defaultValue={region} onChange={e => cityChanged(e.target.value)}>
<option value="">
Select
</option>
{regions.map((region, index) => (
<option key={index} value={region._id}>
{region.region}
</option>
))}
</select>
}
</div>
<div>
<select defaultValue={city} onChange={e => citySelected(e.target.value)}>
<option value="">
Select
</option>
{cities && cities.length > 0 && cities.map((city, index) => (
<option key={index} value={city.id}>
{city.name}
</option>
))}
</select>
</div>
<span>city:{city}</span>
</div>
</>
);
};
You can add a useEffect which sets the value of city every time cities changes.
useEffect(() => {
setCity(cities[0]?.id)
//setCity(undefined) or this if you don't want a city to be preselected.
}, [cities])
You can also add a selected option to verify make sure the selected city is the one shown in the dropdown.
{cities && cities.length > 0 && (
cities.map((cityOption: any, index: any) => (
<option
selected={cityOption.id === city}
key={index}
value={cityOption.id}
>
{cityOption.name}
</option>
))
)}
Turns out the solution is to add the "value" attribute:
<select onChange={e => citySelected(e.target.value)} value={city}>
Related
I've created an table dynamically where we can create an user list. and I stored the users's list in the array of objects called
'data'. Now I want to filter the users by typing in the search bar
input and render the users by selecting the dropdown value which are
the table column headings 'username, role, status'.
const [search, setSearch] = useState("");
const [value, setValue] = useState('');
const dropDownChange = (e) => {
setValue(e.target.value);
};
const searchRows = (rows) => {
if(value === 'username' ){ //I want to select the dropdown for particular
filter results
return rows.filter((row) => row.username.toLowerCase().indexOf(search) > -1)
else if(value == value.includes('Role')){ // want to select the column name.
return rows.filter((row) => row.role.toLowerCase().indexOf(search) > -1)
} else if(value == value.includes('status')){ // want to select the column name.
return rows.filter((row) => row.role.toLowerCase().indexOf(search) > -1)
}
}
}/// Above code is not working
{ searchRows(data)
// .filter((item) => item.username.toLowerCase().includes(search))
.map((user) => {
return (
......mapping the users data......
/
/Drop down code...
<select value={value} onChange={dropDownChange} name="filter" id="filter">
<option value="username">Username</option>
<option value="role">Role</option>
<option value="status">Status</option>
</select>
// Search Bar Code....
<input
type="text"
id="search"
placeholder="Search users... "
onChange={(e) => setSearch(e.target.value)}
/>
I want to change the filter results according to the drop down.
I think this is what you are trying to achieve. If I understand correctly, you want to filter by role with <select> and filter by name with <input type="text">.
const {useEffect, useState} = React;
const arrayOfObjs = [
{id: 1, name: 'Tiffany', role: 'JS'},
{id: 2, name: 'George', role: 'JS'},
{id: 3, name: 'Tom', role: 'PHP'},
]
const Example = ({title}) => {
const [data, setData] = useState(arrayOfObjs);
const [dropDownFilter, setDropDownFilter] = useState('initial');
const [searchfilter, setSearchFilter] = useState('initial');
useEffect(() => {
if(dropDownFilter === 'initial') return;
if(dropDownFilter === ''){
setData(arrayOfObjs);
return;
}
setData(arrayOfObjs.filter(item => item.role === dropDownFilter))
}, [dropDownFilter]);
useEffect(() => {
if(searchfilter === 'initial') return;
if(searchfilter === ''){
setData(arrayOfObjs.filter(item => item.role === dropDownFilter));
return;
}
setData(arrayOfObjs.filter(item => item.role === dropDownFilter && item.name.toLowerCase().includes(searchfilter.toLowerCase())))
}, [searchfilter]);
return (
<div>
<select onChange={e => setDropDownFilter(e.target.value)}>
<option value="">Select Filter</option>
<option value="JS">Role JS</option>
<option value="PHP">Role PHP</option>
</select>
<input onKeyUp={e => setSearchFilter(e.target.value)} type="text" />
<div>
{data.map(item =>
<div key={item.id}>
{item.name} | Role: {item.role}
</div>
)}
</div>
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Example title="Test Snippet" />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
There are 4 select Components with dependant dropdown menu.But when I select an option its not displaying in the input field although my 'selectedPlanets' state is updating just right.
Here is my code -
import React, { useState } from "react";
import "../css/Destination.css";
function Destination(props) {
const [selectedPlanets, SetselectedPlanets] = useState([
null,
null,
null,
null,
]);
const OnSelectPlanet = async (e, key) => {
const clonedSelectedPlanets = JSON.parse(JSON.stringify(selectedPlanets));
clonedSelectedPlanets[key] = e.target.value;
SetselectedPlanets(clonedSelectedPlanets);
};
const CustomSelectComponents = ({ value, options, OnSelect}) => {
return (
<select value={value} onChange={OnSelect}>
<option> -- Select a Planet -- </option>
{options.map((option) => {
return <option key = {option.name} value={option.name} >{option.name}</option>;
})}
</select>
);
};
const OptionsToRender = (Alloptions, AllselectedOptions, index) => {
console.log(AllselectedOptions);
const optionstoRender =
AllselectedOptions[index] != null
? Alloptions.filter(
(option) =>
!AllselectedOptions.some(
(selectedOption) =>
option && selectedOption && option.name === selectedOption
)
)
: Alloptions;
return optionstoRender;
};
return (
<>
<div className="Parent_Card">
{selectedPlanets.map((planet, index) => {
const options = OptionsToRender(props.planets, selectedPlanets, index);
return (
<>
{console.log(index)}
<CustomSelectComponents
value={
selectedPlanets[index] != null ? selectedPlanets[index] : ""
}
options={options}
OnSelect={(e) => OnSelectPlanet(e, index)}
key={index}
/>
</>
);
})}
</div>
</>
);
}
export default Destination;
I tried debugging it and figured that its maybe because of how and when my component is rendering.But I dont know why and hence not able to find the solution.
My expected result is when I am choosing an option it shows in the input field.
Your code could benefit from a few different approaches to building your 4 different select components:
the use of controlled components
(https://reactjs.org/docs/forms.html#controlled-components)
separating the state for each of the different selects
refactoring the <CustomSelectComponent /> to be a component that only accepts props
Here is an example of those approaches in practice that might provide some direction on getting these selects operating as expected!
import React, { useState } from 'react';
import '../css/Destination.css';
// custom select component
const CustomSelectComponent = ({ onChange, options, value }) => (
<select onChange={onChange} value={value}>
<option> -- Select a Planet -- </option>
{options.map(option => (
<option key={option.name} value={option.name}>
{option.name}
</option>
))}
</select>
);
const Destination = () => {
// mock props
const props = { planets: [{ name: 'Pluto' }, { name: 'Earth' }] };
// separated state
const [selectOneValue, setSelectOneValue] = useState('');
const [selectTwoValue, setSelectTwoValue] = useState('');
const [selectThreeValue, setSelectThreeValue] = useState('');
const [selectFourValue, setSelectFourValue] = useState('');
return (
<div className="Parent_Card">
{/* each custom select component is now controlled by it's own state */}
<CustomSelectComponent
onChange={e => setSelectOneValue(e.target.value)}
options={props.planets}
value={selectOneValue}
/>
<CustomSelectComponent
onChange={e => setSelectTwoValue(e.target.value)}
options={props.planets}
value={selectTwoValue}
/>
<CustomSelectComponent
onChange={e => setSelectThreeValue(e.target.value)}
options={props.planets}
value={selectThreeValue}
/>
<CustomSelectComponent
onChange={e => setSelectFourValue(e.target.value)}
options={props.planets}
value={selectFourValue}
/>
</div>
);
};
export default Destination;
I am using Ant Design 4 to make a dynamic form like this: there is an "Add form item" button, when you click it, you can add a select box to your form. After selecting one option from that select box, there will be some inputs with an initial value based on the selected value data. My problem is these inputs fields only render one time when selecting one value from the select box for the first time. I have logged the "item" data inside infoData[0]?.map((item, index), it returns with the right value but the Form Inputs do not update their initialValue. Here is my code:
import React, { useState, useCallback } from "react";
import { Button, Form, Select, Input } from "antd";
const FormPage = () => {
const [userData, setUserData] = useState([]);
const [optionList, setOptionList] = useState([
{
name: "Option 1",
value: "o-1",
info: [
{ name: "Option 1 Info 1", point: 11 },
{ name: "Option 1 Info 2", point: 12 },
],
},
{
name: "Option 2",
value: "o-2",
info: [
{ name: "Option 2 Info 1", point: 13 },
{ name: "Option 2 Info 2", point: 14 },
],
},
]);
const onSubmitForm = (values: object) => {
console.log(values);
};
const addUserData = () => {
const newData = {
id: Math.random().toString(36).substring(7),
userName: Math.random().toString(36).substring(7),
};
setUserData([...userData, newData]);
};
const selectedOption = (val) => {
const selectedData = optionList.filter((item) => item.value === val);
return selectedData[0].info;
};
return (
<Form onFinish={onSubmitForm}>
<p>
<Button onClick={addUserData}>Add Form Item</Button>
</p>
{userData.length > 0
? userData.map((item, index) => (
<FormPageItem
key={index}
data={item}
index={index}
addUserData={addUserData}
optionList={optionList}
selectedOption={selectedOption}
/>
))
: ""}
<Button type="primary">Submit</Button>
</Form>
);
};
const FormPageItem = (props) => {
const [infoData, setInfoData] = useState([]);
const handleSelectOption = (value) => {
const selected = props.selectedOption(value);
console.log("selected", selected);
const newData = [...infoData];
newData[0] = selected;
console.log(newData);
setInfoData(newData);
};
const renderInput = useCallback(() => {
if (infoData.length > 0) {
return (
<>
{infoData[0]?.map((item, index) => (
<Form.Item
name={[`option-${props.data.id}`, `user-info-${index}`]}
style={{ marginBottom: 16 }}
initialValue={item.name}
key={index}
>
<Input />
</Form.Item>
))}
</>
);
}
return "";
}, [infoData]);
return (
<div>
<Form.Item
name={[`option-${props.data.id}`, "options"]}
label="Option List"
>
<Select showArrow={false} onChange={handleSelectOption}>
{props.optionList.map((item) => (
<Select.Option value={item.value} key={item.value}>
{item.name}
</Select.Option>
))}
</Select>
{renderInput()}
</Form.Item>
</div>
);
};
export default FormPage;
Please see the documents here ant design form documents:
Note that initialValues cannot be updated by setState dynamically, you should use setFieldsValue in that situation.
In your case, you can just replace renderInput function with simple render function, and even no need of onChange function for the first Select component.
const FormPageItem = (props) => {
const [infoData, setInfoData] = useState([]);
return (
<div>
<Form.Item
name={[`option-${props.data.id}`, "options"]}
label="Option List"
>
<Select showArrow={false} >
{props.optionList.map((item) => (
<Select.Option value={item.value} key={item.value}>
{item.name}
</Select.Option>
))}
</Select>
{/* {renderInput()} */}
</Form.Item>
<Form.Item
dependencies={[[`option-${props.data.id}`,"options"]]}
>
{({getFieldValue})=>{
const v = getFieldValue([`option-${props.data.id}`,"options"])
if(!v){return null}
const selected = props.selectedOption(v);
return (
<>
{selected?.map((item, index) => (
<Form.Item
name={[`option-${props.data.id}`, `user-info-${index}`]}
style={{ marginBottom: 16 }}
initialValue={item.name}
key={index}
>
<Input />
</Form.Item>
))}
</>
)
}}
</Form.Item>
</div>
);
};
From "orders" component, sending a order id to "update" component. Then trying to update "the status" of the order containing the id.
Logging the id value in the console works, but not setting a state with it.
"Update" component:
const UpdateStatus = (props) => {
const location = useLocation();
const [orderId, setOrderId] = useState(null);
const [status, setStatus] = useState("pending");
useEffect(() => {
setOrderId(location.state.id); // errors here
console.log(location.state.id) // but gives a valid id
}, [location]);
const handleChange = e => {
setStatus(e.target.value);
console.log(e.target.value)
}
const history = useHistory();
const handleClick = () => {
if (orderId){
axios.patch(`http://localhost:5000/orders/change-status/${orderId}`, {status: status}, {withCredentials: true})
.then((res) => {
console.log(res);
history.push("/get-orders");
})
}
}
return (
<div>
<h2> Update Order Status </h2>
<form>
<label>Change status to: </label>
<select name="status" id="order-status" onChange={handleChange}>
<option value="pending">Pending</option>
<option value="accepted">Accepted</option>
<option value="delivered">Delivered</option>
</select>
<br/><br/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
</div>
);
}
"Orders" component:
const handleClick = orderId => {
history.push({
pathname: '/update-status',
state: { id: orderId }
});
}
Try something like:
useEffect(() => {
if(location?.state?.id)
setOrderId(location.state.id);
}, [location?.state?.id]);
Try this:
useEffect(() => {
setOrderId(location.state?.id);
..........
}, [location]);
When I make a selection from the dropdown I saved the selected value to type then when I click the button I add an object to drums, I map over thee drums and based on the type I want to render the component with the same name.
Sandbox here
import React, { useState } from "react";
import uuid from "react-uuid";
import "./styles.css";
const Snare = () => {
return <div>Snare</div>;
};
const Gong = () => {
return <div>Gong</div>;
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => (
<Drum.type /> // Why cant I use `.type`?
))}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
In your case Drum.type is not a component but a string, you need to maintain a map of the string to component and then render it
const map = {
Snare: Snare,
Gong: Gong
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => {
const Component = map[Drum.type];
return <Component key={index}/>;
})}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([
...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
Working demo
That's because the type is a string.
You could create a mapping to solve this and use React.createElement().
Something like:
const mapping = {
'Snare': Snare,
'Gong': Gong
}
{ drums.map(({ type }, index) => (
React.createElement(mapping[type], { key: index })
))
}