Prevent change of dropdown options when underlying data changes - javascript

With the code below:
<!DOCTYPE html>
<html>
<body>
<head>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
</body>
<script type="text/babel">
const api = {
data: [
{
id: 1, party: 'Zuckerberg', news: [
{id: 1, headline: 'Zuckerberg news1'},
{id: 2, headline: 'Zuckerberg news2'},
]
},
{
id: 2, party: 'Musk', news: [
{id: 1, headline: 'Musk news1'},
{id: 2, headline: 'Musk news2'},
]
},
]
}
const App = () => {
const [data, setData] = React.useState([])
React.useEffect(() => setData(api.data), [])
const handleSelectChange = (e) => {
const name = e.target.value
setData(prev => prev.filter(datum => datum.party === name))
}
return (
<div>
<select
onChange={handleSelectChange}>
{data.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{data.map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
</html>
how can I prevent the reduction of dropdown options upon dropdown change, preferably without changing the api.data structure?
I basically need this dropdown to be refreshed only when api.data returns new data.
Is it possibly while keeping only single state (data)?

One way to handle this scenario by define state as an object like I mentioned in my comment. This might help you. It's working for me.
const MyComponent = () => {
const [data, setData] = React.useState({})
React.useEffect(() => setData({dropdownValues: api.data, filteredValues: api.data}), [])
const handleSelectChange = (e) => {
const name = e.target.value
const filteredValues = data.dropdownValues.filter(datum => datum.party === name);
setData({...data, filteredValues})
}
return (
<div>
<select
onChange={handleSelectChange}>
{data.dropdownValues && data.dropdownValues.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{data.filteredValues && data.filteredValues.map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}

I can't think of a way to do it without using another state variable. What's the reason for only wanting one variable?
You could use an object state variable as Qubaish said but that can be much more difficult to maintain than just using a second state variable.
If you can get away with using two state variables use the following:
const App = () => {
const [data, setData] = React.useState([]);
const [options, setOptions] = useState([]);
React.useEffect(() => {
setData(api.data);
setOptions(api.data);
}, []);
const handleSelectChange = (e) => {
let name = e.target.value;
setData(options.filter((datum) => datum.party === name));
};
return (
<div>
<select onChange={handleSelectChange}>
{options.map((datum) => (
<option key={datum.id}>{datum.party}</option>
))}
</select>
{data.map((datum) =>
datum.news.map((newsItem) => (
<div key={newsItem.id}>{newsItem.headline}</div>
))
)}
</div>
);
};
It just stores a copy of the potential options in the options state and then stores the data to be displayed in "data".

I've modified your code below.
Main points:
removed useEffect - the API is static so doesn't need to be re-allocated
useState only monitors the filter (filtering of the API data only happens when the state changes so there isn't a need to store this - but you could useMemo if you wanted and make it dependent on the data)
The options are derived from api.data
<!DOCTYPE html>
<html>
<body>
<head>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
</body>
<script type="text/babel">
const api = {
data: [
{
id: 1, party: 'Zuckerberg', news: [
{id: 1, headline: 'Zuckerberg news1'},
{id: 2, headline: 'Zuckerberg news2'},
]
},
{
id: 2, party: 'Musk', news: [
{id: 1, headline: 'Musk news1'},
{id: 2, headline: 'Musk news2'},
]
},
]
}
const App = () => {
const [data, setData] = React.useState(api.data.length?api.data[0].party:undefined)
const handleSelectChange = (e) => {
setData(e.target.value)
}
return (
<div>
<select
onChange={handleSelectChange}>
{api.data.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{api.data.filter(datum=>datum.party===data).map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
</html>

Related

Toggle button value based on state values in React.js

I'm displaying different cars and a button to add or remove the selections the user has made. How do I get the buttons to change state individually? As of now, it changes the state of all the buttons to one value.
const cars = [
{ name: "Benz", selected: false },
{ name: "Jeep", selected: false },
{ name: "BMW", selected: false }
];
export default function App() {
const isJeepSelected = true;
const isBenzSelected = true;
return (
<div className="App">
{cars.map((values, index) => (
<div key={index}>
<Item
isBenzSelected={isBenzSelected}
isJeepSelected={isJeepSelected}
{...values}
/>
</div>
))}
</div>
);
}
const Item = ({ name, isBenzSelected, isJeepSelected }) => {
const [toggle, setToggle] = useState(false);
const handleChange = () => {
setToggle(!toggle);
};
if (isBenzSelected) {
cars.find((val) => val.name === "Benz").selected = true;
}
console.log("cars --> ", cars);
console.log("isBenzSelected ", isBenzSelected);
console.log("isJeepSelected ", isJeepSelected);
return (
<>
<span>{name}</span>
<span>
<button onClick={handleChange}>
{!toggle && !isBenzSelected ? "Add" : "Remove"}
</button>
</span>
</>
);
};
I created a working example using Code Sandbox. Could anyone please help?
There's too much hardcoding here. What if you had 300 cars? You'd have to write 300 boolean useState hook calls, and it still wouldn't be dynamic if you had an arbitrary API payload (the usual case).
Try to think about how to generalize your logic rather than hardcoding values like "Benz" and Jeep. Those concepts are too closely-tied to the arbitrary data contents.
cars seems like it should be state since you're mutating it from React.
Here's an alternate approach:
const App = () => {
const [cars, setCars] = React.useState([
{name: "Benz", selected: false},
{name: "Jeep", selected: false},
{name: "BMW", selected: false},
]);
const handleSelect = i => {
setCars(prevCars => prevCars.map((e, j) =>
({...e, selected: i === j ? !e.selected : e.selected})
));
};
return (
<div className="App">
{cars.map((e, i) => (
<div key={e.name}>
<Item {...e} handleSelect={() => handleSelect(i)} />
</div>
))}
</div>
);
};
const Item = ({name, selected, handleSelect}) => (
<React.Fragment>
<span>{name}</span>
<span>
<button onClick={handleSelect}>
{selected ? "Remove" : "Add"}
</button>
</span>
</React.Fragment>
);
ReactDOM.createRoot(document.querySelector("#app"))
.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Consider generating unique ids for elements rather than using indices or assuming the name is unique. crypto.randomUUID() is a handy way to do this.

removing object from array of objects - react

Why this does not remove item from the list but only for console.log? As you can see i update list in the function that i assign later to the button.
let DATA = [
{
id: 1,
name: "item1"
},
{
id: 2,
name: "item2"
}
];
const App = () => {
const deleteItem = (id) => {
DATA = DATA.filter((item) => item.id !== id);
console.log(DATA);
};
return (
<div className="App">
{DATA.map((item) => (
<p key={item.id} onClick={() => deleteItem(item.id)}>
{item.name}
</p>
))}
</div>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root")
).render(<App/>);
<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>
It does remove the item from the list. But there's nothing telling the component to re-render the UI.
What you're looking for is state in React. Updating state triggers a component re-render:
const App = () => {
// store the data in state
const [data, setData] = React.useState([
{
id: 1,
name: "item1"
},
{
id: 2,
name: "item2"
}
]);
const deleteItem = (id) => {
// update state with the new data
setData(data.filter((item) => item.id !== id));
};
return (
<div className="App">
{data.map((item) => (
<p key={item.id} onClick={() => deleteItem(item.id)}>
{item.name}
</p>
))}
</div>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root")
).render(<App/>);
<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>
It deletes it but react does not re-render. You have to use a setState method to trigger the re-render, you could put a copy of DATA in a state variable.

Component data was gone after re rendering, even though Component was react.memo already

I have two components.
First is called: BucketTabs
Second is called:BucketForms
To have a better idea. Below pictures illustrate it.
When I switching tab, different form will be showed below.
Q: Whenever I switch from one tab to other tab, and then switch back, the content in the previous BucketForms will be gone. But, gone data are supposed to be stored into a state of that BucketForms.
In fact, I've memo the BucketForms already, so I've expected the content(data) would not be gone.
What's the problem and how could I prevent the data to be gone after switching tab.
My BucketTabs:
import { BucketForms } from '~components/BucketForms/BuckForms'
export const BucketTabs: React.FC = () => {
const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
return [
{
title: '1',
renderContent: () => <BucketForms key="1" bucketCategory="1" />,
},
{
title: '2',
renderContent: () => <BucketForms key="2" bucketCategory="2" />,
},
]
}, [])
return (
<div className="row">
<div className="col">
<ContentTabs items={tabs} kind="tabs" />
</div>
</div>
)
}
BucketForms
function PropsAreEqual(prev, next) {
const result = prev.bucketCategory === next.bucketCategory;
return result;
}
interface IData {
portfolioValue?: number
}
export const BucketForms: React.FC<IProps> = React.memo(props => {
const { bucketCategory } = props
const [data, setData] = useState<IData>({
})
const view = ({
portfolioValue,
}: IData) => {
return (
<>
<div className="row portfolio">
<FormNumericInput
key="input-portfolio-value"
name="portfolioValue"
required
value={portfolioValue}
/>
</div>
</>
)
}
return (
<Form
onChange={e => {
setData({ ...data, ...e, })
}}
>
{view(data)}
</Form>
)
}, PropsAreEqual)

React JS Mapping Array and Unpacking Specific Values

I am trying to map over specific values in an array of objects.
I have collected data from my Backend API and this is the response:
// An array of objects
console.log(response.data) =
[
{name:"Bryan",age:"25",sport:"basketball"},
{name:"Justin",age:"30",sport:"soccer"},
{name:"Mark",age:"28",sport:"basketball"},
{name:"Cowell",age:"27",sport:"soccer"},
]
I put this data into a state ("data") using "useState()" from React and used a useEffect to unpack the data upon rendering.
const [data, setData] = useState([])
// some function to store response.data in state
setData(response.data)
I want to map these values onto my Browser in the following way such that Soccer players are displayed in the first div and Basketball players in the second div:
(tried several ways but they resulted in parsing errors)
function App() {
const [data, showData] = useState([])
return (
<div>
{data.map(info => {
<div>
<h1> Sport: soccer </h1>
<h5> {info.name} </h5>
</div>
<div>
<h1> Sport: basketball</h1>
<h5> {info.name} </h5>
</div>
}
)}
</div>
)
}
I am trying to group the names within the same div block (same sport) and not 2 different div blocks for each sport.
You need to return the elements from the map function and also remove the part where you hardcode basketball.
{data.map((info, idx) => (
<div key={idx}>
<h1> Sport: {info.sport} </h1>
<h5> {info.name} </h5>
</div>
))}
const groupBy = (array, getGroupByKey) => {
return (
(array &&
array.reduce((grouped, obj) => {
const groupByKey = getGroupByKey(obj);
if (!grouped[groupByKey]) {
grouped[groupByKey] = [];
}
grouped[groupByKey].push(obj);
return grouped;
}, {})) ||
{}
);
};
const App = (props) => {
var [data, setData] = React.useState([
{ name: 'Bryan', age: '25', sport: 'basketball' },
{ name: 'Justin', age: '30', sport: 'soccer' },
{ name: 'Mark', age: '28', sport: 'basketball' },
{ name: 'Cowell', age: '27', sport: 'soccer' },
]);
const players = groupBy(data, (player) => player.sport);
const sportKeys = Object.keys(players);
return (
<div>
{sportKeys.map((info, idx) => (
<div key={idx}>
<h1> Sport: {info} </h1>
{players[info].map((player, i) => (
<h5 key={i}>{player.name}</h5>
))}
</div>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

fetch data in select react

There is a form with selects, in the first one a film from a series of Star Wars is selected. And the other two should display spaceships from the selected movie. I don't really understand how to write the condition correctly, if you do it on if, then a lot of code turns out, I'm sure there is a more rational way.
Component code:
class StarshipsCompare extends Component {
swapiService = new SwapiService();
state = {
filmList: [],
};
componentDidMount() {
this.swapiService.getAllFilms().then((filmList) => {
this.setState({ filmList });
});
}
renderItems(arr) {
return arr.map(({ id, title }) => {
return (
<option value={title} key={id}>
{title}
</option>
);
});
}
render() {
const { filmList } = this.state;
const items = this.renderItems(filmList);
return (
<div>
<form>
<p>Выберите фильм корабли из которого хотите сравнить:</p>
<select className="custom-select">{items}</select>
<div className="row p-5">
<div className="col">
<p>Выберите корабль для сравнения:</p>
<select className="custom-select"></select>
</div>
<div className="col">
<p>Выберите корабль для сравнения:</p>
<select className="custom-select"></select>
</div>
</div>
</form>
</div>
);
}
}
And fetch data:
export default class SwapiService {
_apiBase = "https://swapi.dev/api";
async getResource(url) {
const res = await fetch(`${this._apiBase}${url}`);
if (!res.ok) {
throw new Error(`Could not fetch ${url}, status: ${res.status}`);
}
return await res.json();
}
getAllFilms = async () => {
const res = await this.getResource("/films/");
return res.results.map(this._transformFilms);
};
}
This is a simple example of how I would do it. The usage of the && is an elegant replacement for if statements in JSX.
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<div id="app_root"></div>
<script type="text/babel">
// Constants representing API Data.
const MAIN_SELECT_OPTIONS = ["A", "B", "C"];
const SUB_SELECT_OPTIONS = {
A: ["A1", "A2", "A3"],
B: ["B1", "B2", "B3"],
C: ["C1", "C2", "C3"]
};
const SelectState = () => {
const [mainSelect, setMainSelect] = React.useState("");
const [subSelect, setSubSelect] = React.useState("");
const [subSelectOptions, setSubSelectOptions] = React.useState([]);
const onMainSelectChange = (event) => {
setMainSelect(event.target.value);
setSubSelect("");
if (event.target.value) {
setSubSelectOptions(SUB_SELECT_OPTIONS[event.target.value]);
} else {
setSubSelectOptions([]);
}
};
const onSubSelectChange = (event) => {
setSubSelect(event.target.value);
};
return (
<div>
<h1> Main Select </h1>
<select onChange={onMainSelectChange} value={mainSelect}>
<option value="">Select Main</option>
{MAIN_SELECT_OPTIONS.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<h1> Sub Select </h1>
<select onChange={onSubSelectChange} value={subSelect}>
{!subSelect && <option>Select a State</option>}
{subSelectOptions.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
);
};
ReactDOM.render(<SelectState />, document.getElementById("app_root"));
</script>

Categories

Resources