React Reduct - Select Onchange get method request - javascript

I have the following situation, i provide the a codesandbox demo to demonstrate my existing problem.
A bit of explanation about and how to reproduce the case
I am here in the component League.js, any league in the list has a link
<a href={`#${item.league_id}`} onClick={(e) => onClick(e, item.league_id)}>
const onClick = (evt, id) => {
evt.preventDefault();
getDetail(id)
};
The onclick function populates in the Details.js component the select options with the names of the teams. You can see below.
if (teamsDetail.length) {
details = (
<select value={selectedOption} onChange={selectTeamStat}>
{teamsDetail && teamsDetail.length > 0
? teamsDetail.map(item => (
<option key={`${item.team_id}`} value={item.team_id}>
{item.name}
</option>
))
: null}
</select>
);
}
This is now the problem i have in Detail.js component, when i select the team name in my select i want send a get request getStats where i have to set two parameters ( team_id and league_id )
Here the relevant code
const [selectedOption, setSelectedOption] = useState("");
const selectTeamStat = evt => {
const { value } = evt.target;
setSelectedOption(value);
getStats(357, value);
};
At the moment i can only pass the team_id which is the select value selectedOption
How can i also pass the league_id parameter?
I have hardcoded now putting 357 but i need to use the parameter league_id. How can i pass it from League.js Component?

On the top of my head, here's two solutions you may want to try:
Set each of you select's options as a tuple:
value={[item.league_id, item.team_id]}
or set each of you select's options' team_id and league_id as data-attributes

I have figured it out how to pass the leagueId state
These the steps i have done
In the reducers/index.js i have created an initial state
RECEIVE_LEAGUE
league:[],
case RECEIVE_LEAGUE:
return {...state, leagueId: action.json};
In the actions/index.js
export const receivedLeague = json => ({
type: RECEIVE_LEAGUE,
json: json
});
I have added a dispatch in the getTeamsDetailById(id)
dispatch(receivedLeague(id));
In the Details.js component
I have added the state leagueId on top
and edited my selectTeamStat function
const [selectedOption, setSelectedOption] = useState('');
const selectTeamStat = (evt) => {
const { value } = evt.target;
setSelectedOption(value)
getStats(leagueId, value);
};

Related

useEffect hook not getting called on prop changes

I have 2 dropdowns, I need to update the values of the second dropdown based on the first. On page load, I need to initialize the dropdown values based on conditions, like so:
const getInitialState= () => {
//defaultSelected has a value if saved in DB, if not then I set the 0 option value in type, based on this value option value will be populated in the dropdown. I get all the values from parent component
let updatedType= defaultSelected !== ""
? defaultType
: Region.RegionType;
console.log('typeeeee', type)
return updatedType;
}
useEffect(() =>{
//Here in options dropdown values object is set based on type selected
let updatedOptions = !_.isEmpty (type)
? "a" //value from db if save
: "b"; //If not 0th value of the object
setOptions(prevOptions => ({
...prevOptions,
["options"]: updatedOptions
}));
},[type]);
const [type, setType] = useState(getInitialState);
const [options, setOptions] = useState('');
const changeCrTypeHandler = (event) =>{
const { name, value } = event.target;
setType(type =>({
...type,
[name]: value}));
}
return (
<>
<select
onChange={changeCrTypeHandler}
style={{ width: "150px" }}
value={!_.isEmpty(type)}
name= "type"
>
{_.map(customMap, (crId) => { //I get this cusomMap from Parent component
return (
<option
id={crId.customType}
value={crId.customType}
key={crId.customType}
>
{crId.customType}
</option>
);
})}
</select>{" "}
<select
onChange={changeCrHashHandler} //second dropdown onchange
style={{ width: "250px" }}
value={crHashId}
name= "options"
>
{_.map(!_.isEmpty(options), (o) => { //This options are created based on type, which is currently not getting set
return (
<option
id={o.customId}
value={o.customId}
key={o.customId}
>
{o.name}
</option>
);
})}
</select>
Based on type value then I need to set the value of the option.
But my useEffect hook doesn't get called upon changes in type. How can I call the useEffect method whenever there is any changes in the type?
Any help for this would be much appreciated.
You are calling setState before it is defined. For the initial value, you just return it without calling setState. For having the second dropdown state depending on the first one's state, you could use the useEffect hook, like so:
const [firstDropdown, setFirstDropdown] = useState(getInitialState({type:"", options:""}));
const [secondDropdown, setScondDropdown] = useState(0); // you could use whatever initial value you want here
const getInitialState= () => {
//some logic to get the value in type
//just return that inital value without calling setState
}
// the function inside useEffect runs every time firstDropdown changes
useEffect(()=>{
// some logic on firstDropdown
let value = firstDropdown+25
setScondDropdown(value)
},[firstDropdown]);
Update:
In your useEffect, change this :
setOptions(prevOptions => ({
...prevOptions,
["options"]: updatedOptions
}));
to this :
setOptions(updatedOptions)
Use the useState as below:
const [state, setState] = useState({})
setState((prev) => {
// do whatever you want to do with the previous state
})
And for the name, please do not use setState as your setter function of useState. because setState is another method in the class based components in React

Props in child doesn't update when parent updates it's state

I've spent a few days on this and it is driving me crazy now.
I have a state in a parent component containing an Array[string] of selected squares which is passed to the child component (a map) along with the set function from the hook. The issue is that when I set the new squares they are changed in the parent, but on selection of another square it is not taking into account the already selected squares.
function Parent(props){
const [selectedSquares, setSquares] = useState([]);
useEffect(() => {
console.log('parent useEffect', selectedSquares);
}, [selectedSquares]);
return (
<Child selectedSquares={selectedSquares}
handleSquaresChange={setSquares}
/>
)
}
function Child(props){
const {selectedSquares, handleSquaresChange} = props;
useEffect(() => {
console.log('child useEffect', selectedSquares)
}, [selectedSquares]);
const handleSelect = evt => {
if(evt.target){
const features = evt.target.getFeatures().getArray();
let selectedFeature = features.length ? features[0] : null;
if (selectedFeature) {
console.log('select (preadd):', selectedSquares);
const newTile = selectedFeature.get('TILE_NAME');
const newSquares = [...selectedSquares];
newSquares.push(newTile);
const newTest = 'newTest';
handleSquaresChange(newSquares);
console.log('select (postadd):', newSquares);
}
}
return(
<Map>
<Select onSelect={handleSelect}/>
</Map>
)
}
On the first interactionSelect component I get this output from the console:
parent useEffect: [],
child useEffect: [],
select (preadd):[],
child useEffect:['NX'],
parent useEffect: ['NX'],
select (postadd): ['NX'].
Making the second selection this is added to the console:
select (preadd):[],
select (postadd): ['SZ'],
child useEffect:['SZ'],
parent useEffect: ['SZ'].
Turns out there is an addEventListener in the library I am using that is going wrong. Thanks to everyone who responded but turns out the issue was not with React or the state stuff.
Consider something like the code below. Your parent has an array with all your options. For each option, you render a child component. The child component handles the activity of its own state.
function Parent(props){
// array of options (currently an array of strings, but this can be your squares)
const allOptions = ['opt 1', 'opt 2', 'opt 3', 'etc'];
return (
<>
// map over the options and pass option to child component
{allOptions.map((option) => <Child option={option}/>)}
</>
)
}
function Child({ option }){
const [selected, setSelected] = useState(false); // default state is false
return (
<>
// render option value
<p>{option}</p>
// shows the state as selected or not selected
<p>Option is: {selected ? "selected" : "not selected"}</p>
// this button toggles the active state
<button onClick={() => setSelected(!selected)}>Toggle</button>
</>
)
}

ReactJS: make a select that load all values from api

I'm creating a select (at the moment i'm using React-Select component) to retrive all the result from the api.
The problem is that API gives me back 20 values, so I should find a method to load other 20 values ( as I make another api call )
const option = personList && personList .map((spl) => {
return {
value: spl.perCod,
label: spl.perName
}
})
<Row>
<Col>
<Select
id="perCod"
name="perCod"
options={option}
/>
</Col>
</Row>
the personList is populated calling the api:
useEffect(() => {
sortEntities();
}, [paginationState.activePage, paginationState.order, paginationState.sort]);
const sortEntities = = () => {
//...
props.getFilteredEntities(
search, // i pass there the parameters for the research
paginationState.activePage - 1,
paginationState.itemsPerPage,
`${paginationState.sort},${paginationState.order}`
),
}
props.getFilteredEntities in my reducer is:
export const getFilteredEntities: ICrudSearchAction<Person> = (search, page, size, sort) => {
const params = new URLSearchParams(search) ? new URLSearchParams(search).toString() : null;
const requestUrl = `${apiUrl}${sort ? `?page=${page}&size=${size}&sort=${sort}` : ''}${sort ? '&' : '?'}${params}`;
return {
type: ACTION_TYPES.FETCH_PERSON_LIST,
payload: axios.get<Person>(`${requestUrl}${sort ? '&' : '?'}cacheBuster=${new Date().getTime()}`),
};
};
At the moment my select has the first 20 results from api. I should need to load others. How can I do? thank you.
change your <Select> code with this,
you have to add option tag within iteration, to render all options within select tag,
<Select id="perCod" name="perCod">
{option.map(o=><option key={Math.random()} value={o.perCod} >{o.perName}</option>)}
</Select>

How to observe change in a global Set variable in useEffect inside react component function?

I have a page wherein there are Listings.
A user can check items from this list.
Whenever the user checks something it gets added to a globally declared Set(each item's unique ID is added into this set). The ID's in this set need to be accessed by a seperate Component(lets call it PROCESS_COMPONENT) which processes the particular Listings whose ID's are present in the set.
My Listings code roughly looks like:
import React from "react";
import { CheckBox, PROCESS_COMPONENT } from "./Process.jsx";
const ListItem = ({lItem}) => {
return (
<>
//name,image,info,etc.
<CheckBox lId={lItem.id}/>
</>
)
};
function Listings() {
// some declarations blah blah..
return (
<>
<PROCESS_COMPONENT /> // Its a sticky window that shows up on top of the Listings.
//..some divs and headings
dataArray.map(item => { return <ListItem lItem={item} /> }) // Generates the list also containing the checkboxes
</>
)
}
And the Checkbox and the PROCESS_COMPONENT functionality is present in a seperate file(Process.jsx).
It looks roughly like:
import React, { useEffect, useState } from "react";
let ProcessSet = new Set(); // The globally declared set.
const CheckBox = ({lID}) => {
const [isTicked, setTicked] = useState(false);
const onTick = () => setTicked(!isTicked);
useEffect( () => {
if(isTicked) {
ProcessSet.add(lID);
}
else {
ProcessSet.delete(lID);
}
console.log(ProcessSet); // Checking for changes in set.
}, [isTicked]);
return (
<div onClick={onTick}>
//some content
</div>
)
}
const PROCESS_COMPONENT = () => {
const [len, setLen] = useState(ProcessSet.size);
useEffect( () => {
setLen(ProcessSet.size);
}, [ProcessSet]); // This change is never being picked up.
return (
<div>
<h6> {len} items checked </h6>
</div>
)
}
export { CheckBox, PROCESS_COMPONENT };
The Set itself does get the correct ID values from the Checkbox. But the PROCESS_COMPONENT does not seem to be picking up the changes in the Set and len shows 0(initial size of the set).
I am pretty new to react. However any help is appreciated.
Edit:
Based on #jdkramhoft
's answer I made the set into a state variable in Listings function.
const ListItem = ({lItem,set,setPSet}) => {
//...
<CheckBox lID={lItem.id} pset={set} setPSet={setPSet} />
)
}
function Listings() {
const [processSet, setPSet] = useState(new Set());
//....
<PROCESS_COMPONENT set={processSet} />
dataArray.map(item => {
return <ListItem lItem={item} set={processSet} setPSet={setPSet} />
})
}
And corresponding changes in Process.jsx
const CheckBox = ({lID,pset,setPSet}) => {
//...
if (isTicked) {
setPSet(pset.add(lID));
}
else {
setPSet(pset.delete(lID));
}
//...
}
const PROCESS_COMPONENT = ({set}) => {
//...
setLen(set.size);
//...
}
Now whenever I click the check box I get an error:
TypeError: pset.add is not a function. (In 'pset.add(lID)', 'pset.add' is undefined)
Similar error occurs for the delete function as well.
First of all, the set should be a react state const [mySet, setMySet] = useState(new Set()); if you want react to properly re-render with detected changes. If you need the set to be available to multiple components you can pass it to them with props or use a context.
Secondly, React checks if dependencies like [ProcessSet] has been changed with something like ===. Even though the items in the set are different, no change is detected because the object is the same and there is no re-render.
Update:
The setState portion of [state, setState] = useState([]); is not intended to mutate the previous state - only to provide the next state. So to update your set you would do something like:
const [set, setSet] = useState(new Set())
const itemToAdd = ' ', itemToRemove = ' ';
setSet(prev => new Set([...prev, itemToAdd]));
setSet(prev => new Set([...prev].filter(item => item !== itemToRemove)));
As you might notice, this makes adding and removing from a set as slow as a list. So unless you need to make a lot of checks with set.has() I'd recommend using a list:
const [items, setItems] = useState([])
const itemToAdd = ' ', itemToRemove = ' ';
setItems(prev => [...prev, itemToAdd]);
setItems(prev => prev.filter(item => item !== itemToRemove));

How to obtain additional data from select options choice in React / Typescript app

I have an array of exchanges, once the user selects an exchange, I want to use both the exchange's name and its base quote.
For example, if the user selects the first option below, I want to capture "poloniex" as well as the base key "USDC".
First I tried using this:
{exchanges.map((exchange, i) =>
(<option key={i} value={exchange.exchange} base={exchange.quote}>
{capFirst(exchange.exchange)} ({exchange.quote}) ${exchange.price_quote}
</option>))}
However I get the error that base does not exist, however shouldn't I be able to add any attribute to an option? Perhaps not if it's in JSX? Also data didn't work.
Type '{ children: string[]; key: number; value: string; base: string; }' is not assignable to type 'DetailedHTMLProps, HTMLOptionElement>'.
Property 'base' does not exist on type 'DetailedHTMLProps, HTMLOptionElement>'.ts(2322)
Another way I tried is to get the index key of the option selected, however the follow code produces key = null.
Below target.value will give me its value, but I also need the base quote.
#bind
handleExchangeSelect(event: React.FormEvent<HTMLSelectElement>) {
const { exchanges } = this.props;
const target = event.target as HTMLSelectElement;
console.log('target', target);
const exchange = target.value;
const key = target.getAttribute('key');
console.log('exchange', exchange);
console.log('key', key);
// if (key) {
// console.log(exchanges[Number(key)]);
// }
this.setState({
exchange,
// exchange_base: base ? base : ''
});
}
I'm hoping for a more obvious, cleaner way, but I found this and it works:
https://reactjs.org/docs/forms.html
Note
You can pass an array into the value attribute, allowing you to select multiple options in a select tag:
<select multiple={true} value={['B', 'C']}>
Solution in my app
The select options
{exchanges.map((exchange, i) =>
(<option key={i} value={[exchange.exchange, exchange.quote]}>
{capFirst(exchange.exchange)} ({exchange.quote}) ${exchange.price_quote}
</option>))}
The select handler
#bind
handleExchangeSelect(event: React.FormEvent<HTMLSelectElement>) {
const target = event.target as HTMLSelectElement;
const exchangeValues = target.value.split(',');
const exchange = exchangeValues[0];
const exchange_base = exchangeValues[1];
console.log('exchange', exchange);
console.log('exchange_base', exchange_base);
this.setState({
exchange,
exchange_base
});
}
This gets me what I wanted:
{
exchange: 'gdax',
exchange_base: 'USD'
}
The react way to solve that would be to create an own select component which could look like this:
class ExchangeSelect extends Component {
handleSelect = event => {
const {exchanges, onSelect} = this.props;
const selected = exchanges.find(exchange => exchange.exchange === event.target.value);
onSelect(selected);
};
render() {
const {exchanges} = this.props;
return (
<select onChange={this.handleSelect}>
{exchanges.map(exchange => (
<option key={exchange.exchange} value={exchange.exchange}>
{exchange.exchange} ({exchange.quote}) ${exchange.price_quote}
</option>
))}
</select>
);
}
}
With exchanges being a list of exchange objects you could then use it like this:
const exchanges = [
{exchange: 'poloniex', quote: 'USDC', price_quote: '0.42'},
{exchange: 'bitibu', quote: 'USDT', price_quote: '0.666'},
{exchange: 'bittrex', quote: 'USDT', price_quote: '0.21'},
];
In render():
<ExchangeSelect
exchanges={exchanges}
onSelect={exchange => console.log(exchange.quote)}
/>
Live Example:

Categories