I want to dynamically add options to a bootstrap-select drop down selector. Options that are added manually work fine, the dynamic ones don't. Below "float 1" is an option in the selector, but the others that a provided by the API don't. If I remove "selectpicker" all options show up.
import { useState, useEffect } from "react";
const SelectFloats = () => {
const [selectedFloats, setFloat] = useState([]);
const [floats, setFloats] = useState([]);
useEffect(() => {
requestFloats();
}, []);
//Get list of floats for selector
async function requestFloats() {
const res = await fetch(`http://127.0.0.1:8000/ajax/get_deployments_list`, {
mode: "cors",
});
const json = await res.json();
console.log(json);
setFloats(json.deployments);
}
return (
<div>
<select id="deployment_selector" value={selectedFloats} className="selectpicker" data-live-search="true" multiple>
<option value="Float 1">Float 1</option>
{floats.map((float) => (
<option value={float.PLATFORM_NUMBER} key={float.PLATFORM_NUMBER}>
{float.LABEL}
</option>
))}
</select>
</div>
);
};
export default SelectFloats;
Related
I have one issue with fetching the data using the useQuery. Please have a look at the code below:
FetchUser hook:
const useFetchUsers = ({ selectedSchool, selectedYear }) => {
return useQuery(['Users', selectedSchool, selectedYear], async () => {
const URL = getSchoolURL({ selectedSchool, selectedYear })
const response = await fetch(URL)
const data = await response.json()
return {
count: data.count,
users: data.users
}
}, {
enabled: !!(selectedSchool && selectedYear),
onSuccess: () => {
console.log('success')
},
onError: () => {
console.log('errors')
}
})
}
Users component:
const Users = () => {
const {
isLoading,
data,
isError
} = useFetchUsers({ selectedSchool: '', selectedYear: '' })
const updateUsersData = ({ selectedSchool, selectedYear }) => {
// Here, I have to write logic to fetch Users data as per
// selected organization and selectedYear
}
return (
<div className='app'>
<Schools updateUsersData={updateUsersData}/>
{
/**
* Rendering components
* <Component-1/>
* <Component-2/>
* <Component-3/>
* <Component-4/>
* <Component-5/>
*
*/
}
</div>
)
}
School component:
const Schools = () => {
const [school, setSchool] = useState('')
const handleChange = (e) => {
const selectedSchool = e.target.value
setSchool(selectedSchool)
if (selectedSchool) {
// we have other logic to select selected Year
// but here sake for the example, I'm using this value
// hardcoded
updateUsersData({ selectedSchool, selectedYear: '2021' })
}
}
return (
<select
value={school}
onChange={handleChange}
name='school'
id='school'>
<option
value={''}
key={'Select School'}>
Select School
</option>
<option value={'school-1'}>school-1</option>
<option value={'school-2'}>school-2</option>
<option value={'school-3'}>school-3</option>
<option value={'school-4'}>school-4</option>
<option value={'school-5'}>school-5</option>
</select>
)
}
Some notes:
School component: Here, we are rendering the school names and when the user selects any school data, we are calling updateUsersData method and from this method, we have to call again the useFetchUsers hook with updated params but it is not working.
I don't want to take additional states i.e selectedSchool and selectedYear on Users component because of unnecessary component rendering.
Problem: How to again call useFetchUsers hook with updated params from updateUsersData method?
I need to add sorting to fetched data (ascending/descending).
I get all the data from API endpoint. I map every object in that array to be displayed in separate component card. But once I choose to sort data from Descending name I get a quick change of components were they are sorted from Z to A but it just instantly converts back to initial fetched state (from A to Z).
Could you please tell me where the problem is? I don't know why but it feels like sorted array doesn't get saved in state "data" which I use to map all the cards.
import { useState } from 'react';
import { useEffect } from 'react';
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from 'uuid';
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState('default');
useEffect(() => {
fetchData();
sortData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
setData(data);
};
function sortData() {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
return (
<div className='content'>
<header className='content__header'>
<h1>Header placeholder</h1>
</header>
<div className='wrapper'>
<div className='wrapper__sort-buttons'>
<select
defaultValue='default'
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value='default'>
Sort by
</option>
<option value='ascending'>Ascending</option>
<option value='descending'>Descending</option>
</select>
</div>
<ul className='wrapper__list'>
{data.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
This is what I get just for a quick moment:
And then it just goes back to initial state:
It appears the way you're using useEffect is causing your component to refetch the data each time you change the sort type. This could be causing a race condition due to multiple places updating your data state at different times.
I would move the sorting logic into a useMemo and only fetch the data in useEffect on initial load:
import { useEffect, useMemo, useState } from "react";
import './styles/main.scss';
import Card from './components/Card/Card';
import { v4 as uuidv4 } from "uuid";
function App() {
const [data, setData] = useState([]);
const [sortType, setSortType] = useState("default");
// Move sort logic here...
const sortedData = useMemo(() => {
let result = data;
if (sortType === "descending") {
result = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === "ascending") {
result = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
return result;
}, [data, sortType]);
// Only fetch data once on component mount...
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch(
"https://restcountries.com/v2/all?fields=name,region,area"
);
const data = await response.json();
setData(data);
};
return (
<div className="content">
<header className="content__header">
<h1>Header placeholder</h1>
</header>
<div className="wrapper">
<div className="wrapper__sort-buttons">
<select
defaultValue="default"
onChange={(e) => setSortType(e.target.value)}
>
<option disabled value="default">
Sort by
</option>
<option value="ascending">Ascending</option>
<option value="descending">Descending</option>
</select>
</div>
<ul className="wrapper__list">
{/* Use sortedData here instead of data... */}
{sortedData.map((country) => {
country.key = uuidv4();
return (
<li key={country.key}>
<Card
name={country.name}
region={country.region}
area={country.area}
/>
</li>
);
})}
</ul>
</div>
</div>
);
}
export default App;
Here's a basic example in a Codesandbox (I commented out your styles/card component): https://codesandbox.io/s/goofy-tdd-8lio9?file=/src/App.js
This might be happening for the reason that set state function is asynchronous in nature and the order in which setData is being called is different than you expect.
So, for the initial call with sortType 'default', you are not noticing any change as you are returning the data as it is. But once you change it to 'descending', setData() from sortData() is called earlier than that from fetchData() so as you have already data in your state, you see a change in data in UI for few moments, but then setData() from the function fetchData is called and replaces your data with the one you got from the API call which is unsorted or in ascending order.
POSSIBLE SOLUTION
DON'T set the state inside fetchData method, rather just set it once inside the sortData method, as you are needing it anyhow.
So your code will look something like this:
// we will call sortData inside fetchData so remove it from here
useEffect(() => {
fetchData();
}, [sortType]);
const fetchData = async () => {
const response = await fetch(
'https://restcountries.com/v2/all?fields=name,region,area'
);
const data = await response.json();
// using API response data as an input to sortData function
sortData(data)
};
// using data from parameter instead of state
function sortData(data) {
let sortedData;
if (sortType === 'descending') {
sortedData = [...data].sort((a, b) => {
return b.name.localeCompare(a.name);
});
} else if (sortType === 'ascending') {
sortedData = [...data].sort((a, b) => {
return a.name.localeCompare(b.name);
});
} else {
return data;
}
setData(sortedData);
}
IMPROVEMENT
Your API call is not depending upon the SORTING ORDER, so you don't need to call the API again and again, just call it once, and then sort the data on the value changed from dropdown.
// call the API on initial load only
useEffect(() => {
fetchData();
}, []);
// and on sortType change you can handle it like this:
useEffect(() => {
sortData(data);
}, [sortType]);
// and using this approach you can use the exact same code for both functions implementation that you posted in your question above.
I am trying to implement select-option in React using custom hooks and encountered an issue while trying to set a default value in select option. From the fetched data in UI, that comes from web API, I was able to show selected data based on category(in my case it's cuisine). But when I select default value to show All data, state doesn't update.
Another problem is about the duplicated values in select option. I need to have unique values as option values. I was thinking about to get unique values this way
<option key={restaurant.id}>{[...new Set(restaurant.cuisine)]}</option>
But this removes duplicated characters,but not the duplicated values.
Code below.
Hooks/useRestaurants component
import React, { useState, useEffect } from "react";
const useRestaurants = (cuisine) => {
const [allRestaurants, setAllRestaurants] = useState([]);
useEffect(() => {
fetch("https://redi-final-restaurants.herokuapp.com/restaurants")
.then((res) => res.json())
.then((result) => setAllRestaurants(result.results))
.catch((e) => console.log("error"));
}, []);
useEffect(() => {
if (cuisine === "All") {
const filterRestaurants = [...allRestaurants].filter((restaurant) => // here is my try
restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())//code here doesn't work
);
setAllRestaurants(filterRestaurants);
} else {
const filterRestaurants = [...allRestaurants].filter((restaurant) =>
restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())
);
setAllRestaurants(filterRestaurants);
}
}, [cuisine]);
return [allRestaurants];
};
export default useRestaurants;
App.js component
import React, { useState } from "react";
import useRestaurants from "./useRestaurants";
import Form from "./Form";
import Restaurant from "./Restaurant";
import "./styles.css";
export default function App() {
const [cuisine, setCuisine] = useState("All");
const [allRestaurants] = useRestaurants(cuisine);
const onChangeHandler = (e) => {
setCuisine(e.target.value);
};
return (
<div className="App">
<Form
onChangeHandler={onChangeHandler}
allRestaurants={allRestaurants}
cuisine={cuisine}
setCuisine={setCuisine}
/>
{allRestaurants &&
allRestaurants.map((restaurant) => (
<Restaurant restaurant={restaurant} key={restaurant.id} />
))}
</div>
);
}
And Form.js component
import React from "react";
const Form = ({ allRestaurants, cuisine, onChangeHandler }) => {
return (
<select onChange={onChangeHandler} value={cuisine}>
<option value={cuisine}>All</option>
{allRestaurants.map((restaurant) => (
<option key={restaurant.id}>{restaurant.cuisine}</option>
))}
</select>
);
};
export default Form;
Any help will be appreciated.
The useEffect in useRestaurants that is performing the filtering is missing allRestaurants from the dependency array. This means that the initial value (an empty array) will always be used within that useEffect. Thus, changing the cuisine will set allRestaurants to an empty array. However, you can't add allRestaurants to the dependency array and set it from within the effect. That will cause it to loop infinitely. The solution is to not use an effect - just create the filtered result and return it either as a separate value or in place of allRestaurants
// useRestaurants.js
import { useState, useMemo, useEffect } from "react";
const useRestaurants = (cuisine) => {
const [allRestaurants, setAllRestaurants] = useState([]);
useEffect(() => {
fetch("https://redi-final-restaurants.herokuapp.com/restaurants")
.then((res) => res.json())
.then((result) => setAllRestaurants(result.results))
.catch((e) => console.log("error"));
}, []);
const filteredRestaurants = useMemo(() => {
return cuisine === "All"
? allRestaurants
: allRestaurants.filter((restaurant) =>
restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())
);
}, [cuisine, allRestaurants]);
return [allRestaurants, filteredRestaurants];
};
export default useRestaurants;
To fix the duplicate cuisine values you need to create the Set and then filter over that result. Your form is still filtering over all allRestaurants and {[...new Set(restaurant.cuisine)]} is just creating an array with a single value.
// Form.js
import React from "react";
const Form = ({ allRestaurants, cuisine, onChangeHandler }) => {
const cuisines = Array.from(new Set(allRestaurants.map((r) => r.cuisine)));
return (
<select onChange={onChangeHandler} value={cuisine}>
<option value='All'}>All</option>
{cuisines.map((cuisine) => (
<option id={cuisine}>{cuisine}</option>
))}
</select>
);
};
export default Form;
Remember to loop over the filtered restaurants in App.js
...
const [allRestaurants, filteredRestaurants] = useRestaurants(cuisine);
...
return (
...
{filteredRestaurants &&
filteredRestaurants.map((restaurant) => (
<Restaurant restaurant={restaurant} key={restaurant.id} />
))}
)
I Implement multiple language on my website, using different json file for translation.
I want to import different json based on user select.
I use context Api to use Json file But
Initial context doesn't change when calling userLanguageChange function,
import {
dictionaryList,
languageOptions,
} from "../translations/LanguageSelector";
import React, { createContext, useState, useEffect } from "react";
export const LanguageBisContext = createContext({
userLanguage: "it",
dictionary: dictionaryList.it,
});
export function LanguageBisProvider({ children }) {
const defaultLanguage = window.localStorage.getItem("rcml-lang");
const [dictionary, setDictionary] = useState(dictionaryList[defaultLanguage]);
const [userLanguage, setUserLanguage] = useState(defaultLanguage);
const provider = {
userLanguage,
dictionary: dictionaryList[userLanguage],
userLanguageChange: (selected) => {
console.log("questa รจ la lingua selezionata:" + selected);
const newLanguage = languageOptions[selected] ? selected : "en";
setUserLanguage(newLanguage);
console.log(dictionaryList[newLanguage]);
/* window.localStorage.setItem("rcml-lang", newLanguage); */
},
};
return (
<LanguageBisContext.Provider value={provider}>
{children}
</LanguageBisContext.Provider>
);
}
then I use this component to change language
export default function LanguageSelector() {
const { userLanguage, userLanguageChange } = useContext(LanguageBisContext);
console.log(userLanguage);
// set selected language by calling context method
const handleLanguageChange = (e) => {
console.log(e.target.value);
userLanguageChange(e.target.value);
};
return (
<select onChange={handleLanguageChange} value={userLanguage}>
{Object.entries(languageOptions).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
);
}
hello this is my first question, I wanted to create a dynamically changing link api based on e.target with select. I need the option value to be added to api as a number. I tried to use parseInt but unsuccessfully. the first time I get the message: Parameter 'matchday' is expected to be an integer in the range 1-46. After choosing another option, everything starts loading as it should.
Component
import React, { useEffect } from "react";
const Fixtures = ({ fixtures, getFixtures, loading }) => {
useEffect(() => {
getFixtures();
}, [getFixtures]);
const handleOnChange = e => {
getFixtures(e.target.value);
};
return (
<>
<select onChange={handleOnChange}>
<option value="1">Matchday 1</option>
<option value="2">Matchday 2</option>
</select>
</>
);
};
export const getFixtures = matchday => dispatch => {
dispatch(startFetchingFixtures());
const getFixturesUrl = matchday =>
`http://api.football-data.org//v2/competitions/2021/matches?matchday=${matchday}`;
fetch(getFixturesUrl(matchday), {
headers: {
"X-Auth-Token": "..."
}
}).then(response => response.json())
.then(response => response.matches)
.then(matches => dispatch(fetchedFixtures(matches)));
};