Home > Blockchain >  React MUI components (Slider/Select) "onChange" doesn't change value immediately (pro
React MUI components (Slider/Select) "onChange" doesn't change value immediately (pro

Time:05-30

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
}));
  • Related