I'm trying to use Slider/Select to get user query parameters, which will make further changes to the URL (through handleChange) and make api calls through fetch hooks.
However, when I change the Slider value from 1 to 0.1, nothing happens, but if I change the slider value again to 0.1 to 0.2, it will return me the result of 0.1 ( therefore lagging by 1 click). Similar behavior is observed with the Select value.
Website here:https://thisorthatstockfrontend.herokuapp.com/rankStockSharpeFiltered
I don't have form control but after trying form control it didn't seem to fix the issue. I really do not want to have a submit button, any help is appreciated!
import React from 'react'
import { NavLink } from 'react-router-dom'
import { Button,InputLabel } from '@mui/material'
import { Typography,MenuItem } from '@mui/material'
import { DataGrid } from '@mui/x-data-grid'
import Slider from '@mui/material/Slider';
import { useEffect } from 'react'
import {FormControl} from '@mui/material';
import { useState } from 'react'
import { useFetch } from '../hooks/useFetch';
import { useForm,Controller } from "react-hook-form";
import Select,{SelectChangeEvent} from '@mui/material/Select';
import Grid from '@mui/material/Grid'
import { Box } from '@mui/system';
export default function StockListedFiltered()
{
const [values, setValues] = useState(
{
sharpeYear1: 1,
sharpeYear2: 5,
sharperatio1Cutoff: 1,
sharperatio2Cutoff: 1,
}
);
const [url,seturl]=useState('https://thisorthatstock.herokuapp.com/multipleStockPerformanceFilteredByYear?shortYearNum=' values.sharpeYear1 '&LongYearNum=' values.sharpeYear2 '&sharpeRatio1=' values.sharperatio1Cutoff '&sharpeRatio2=' values.sharperatio2Cutoff)
const handleChange = (propertyName) => (event) => {
console.log('hello')
event.preventDefault();
setValues((values) => ({
...values,
[propertyName]: event.target.value
}));
console.log('end')
console.log(values)
seturl('https://thisorthatstock.herokuapp.com/multipleStockPerformanceFilteredByYear?shortYearNum=' values.sharpeYear1 '&LongYearNum=' values.sharpeYear2 '&sharpeRatio1=' values.sharperatio1Cutoff '&sharpeRatio2=' values.sharperatio2Cutoff)
console.log(url)
};
const{data:stockPerformance ,isPending,error}=useFetch(url)
const columns=[
{field:'stockName', headerName:'ticker',width:200 },
{field:'years', headerName:'years' ,width:200},
{field:'SharpeRatio', headerName:'sharpe ratio',width:200},
{field:'AnnualReturn', headerName:'annual return',width:200 },
{field:'AnnualVolatility', headerName:'annual volatility',width:200 }
];
return (
<div>
<h2 style={{paddingTop:'3rem',textAlign:'center',paddingBottom:'2rem'}}> Filtered Stocks Ranked </h2>
<NavLink to="/rankStockSharpe">
<Button>
<Typography>All stock </Typography>
</Button>
</NavLink>
<NavLink to="/rankStockSharpeFiltered">
<Button>
<Typography>Filtered </Typography>
</Button>
</NavLink>
<Grid container style={{paddingTop:'2rem',textAlign:'center'}}>
<Grid item xs={2}></Grid>
<Grid item xs={4} style={{display:'flex',alignItems:'center',justifyContent:'center' }}>
<Grid item xs={6}>
<Typography>
Years of Data
</Typography>
<FormControl sx={{ m: 1, minWidth: 120 }}>
<Select
defaultValue={1}
labelId="sharpeYear1"
id="sharpeYear1-select"
value={values.sharpeYear1}
label="Year"
onChange={handleChange("sharpeYear1")}
>
<MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem>
<MenuItem value={3}>3</MenuItem>
<MenuItem value={4}>4</MenuItem>
<MenuItem value={5}>5</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={6}>
<Box sx={{ width: '80%' }}>
<Typography>
Sharpe Ratio cut off
</Typography>
<Slider
defaultValue={1}
value={values.sharperatio1Cutoff}
onChange={ handleChange("sharperatio1Cutoff")}
step={0.05}
valueLabelDisplay="auto"
min={0}
max={2}
/>
</Box>
</Grid>
</Grid >
<Grid item xs={4} style={{display:'flex',alignItems:'center',justifyContent:'center' }}>
<Grid item xs={6}>
<Typography>
Years of Data
</Typography>
<Select
defaultValue={5}
labelId="sharpeYear2"
id="sharpeYear2-select"
value={values.sharpeYear2}
label="Year"
onChange={handleChange("sharpeYear2")}
>
<MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem>
<MenuItem value={3}>3</MenuItem>
<MenuItem value={4}>4</MenuItem>
<MenuItem value={5}>5</MenuItem>
</Select>
</Grid>
<Grid item xs={6}>
<Box sx={{ width: '80%' }}>
<Typography>
Sharpe Ratio cut off
</Typography>
<Slider
defaultValue={1}
value={values.sharperatio2Cutoff}
onChange={handleChange("sharperatio2Cutoff")}
step={0.05}
valueLabelDisplay="auto"
min={0}
max={2}
/>
</Box>
</Grid>
</Grid>
</Grid>
<div style={{display:'flex',alignItems:'center',justifyContent:'center' }}>
{isPending&&<div>Loading Data...</div>}
{error &&<div>"This is awkard..."{error}</div>}
{!isPending && stockPerformance&&
<div style={{height:500,width:'70%'}}>
<DataGrid
rows={stockPerformance}
columns={columns}
></DataGrid>
</div>
}
</div>
</div>
)
}
CodePudding user response:
React useState
is asynchronous, so updating state & then accessing it immediately in next line will not work. Updated state is only available on the next render cycle.
The below will not console log the updated state. Same for setUrl
.
setValues((values) => ({
...values,
[propertyName]: event.target.value
}));
console.log('end')
console.log(values) // state is not yet updated
If you want to perform an action on state update, you need to use the useEffect
hook, much like using componentDidUpdate in class components since the setter returned by useState
doesn't have a callback pattern
useEffect(() => {
// action on update of values
console.log(values) // updated state is available here
}, [values]);
CodePudding user response:
You can clone the values
state, make your changes by mutating and update your state. Then use the modified object to calculate the url so it has the new values.
const handleChange = (propertyName) => (event) => {
event.preventDefault();
const newValues = { ...values };
newValues[propertyName] = event.target.value;
setValues(newValues);
seturl(
'https://thisorthatstock.herokuapp.com/multipleStockPerformanceFilteredByYear?shortYearNum='
newValues.sharpeYear1
'&LongYearNum='
newValues.sharpeYear2
'&sharpeRatio1='
newValues.sharperatio1Cutoff
'&sharpeRatio2='
newValues.sharperatio2Cutoff
);
};
Or even better, calculate url in a second time (not directly in your handler), plus it doesn't have to be in the state.
const handleChange = (propertyName) => (event) => {
event.preventDefault();
const newValues = { ...values };
newValues[propertyName] = event.target.value;
setValues(newValues);
};
const url = useMemo(() => {
return (
'https://thisorthatstock.herokuapp.com/multipleStockPerformanceFilteredByYear?shortYearNum='
values.sharpeYear1
'&LongYearNum='
values.sharpeYear2
'&sharpeRatio1='
values.sharperatio1Cutoff
'&sharpeRatio2='
values.sharperatio2Cutoff
);
}, [values]);
By the way, event could be null
in this callback, destructure it so it's assigned to a variable or use event.persist()
.
setValues((values) => ({
...values,
[propertyName]: event.target.value
}));