Home > Back-end >  React updating state only after second click
React updating state only after second click

Time:10-24

I am trying to build a react app and have run into a bug I can't seem to fix. I have a context file with a days variable in it and a data object that depends on number of days. it looks like this:

const DataContext = createContext({ days: 720, data: { timestamp: [], price: [], short_ma: [], long_ma: [], money: [], action: [] }, daysChangeHandler: (newDays) => { } })
export default DataContext

export const DataContextProvider = (props) => {
    const daysChangeHandler = (newDays) => {
        setDays(newDays, getNewData())

    }
    const getNewData = () => {
        getData(days).then(response => setData(response))
        console.log('NEW DATA')
    }

    const [days, setDays] = useState(720)
    const [data, setData] = useState({ timestamp: [], price: [], short_ma: [], long_ma: [], money: [], action: [] })

    return <DataContext.Provider
        value={{ days: days, data: data, daysChangeHandler: daysChangeHandler }}>
        {props.children}
    </DataContext.Provider>


}

getData triggers a call to a database and gets the data back, it works I tested it.

The app is wrapped in the dataContextProvider.

When a button is clicked, it looks like this:

export default function NumberOfDays(props) {

const context = useContext(DataContext)

    const predefinedDaysHandler = (days) => {

        context.daysChangeHandler(days)
}
return <button className='submit-button' onClick={() => predefinedDaysHandler(0)}>Max</button>
}

Finally, this should be displayed in a rendered component looking like this:

export default function DataChart(props) {
    const context = useContext(DataContext)
    
console.log(context.data)
return //rendered data here, the updated data should be displayed but is only on the second time I click the submit button
}

The problem is that I have to click twice on the submit button to get the updated data value?

I have tried using useEffect in the DataContextProvider:

useEffect(()=>{
getNewData(days)
}, [days])

But it changes nothing.

Also, when I press the submit button the first time, the console.log('NEW DATA') gets triggered, but the data somehow does not get updated in the component that should display my data before the second click.

To be really clear, the data that is displayed is always the data from just before the last update. How do I change this?

EDIT

Tried to replace daysChangeHandler with:

const daysChangeHandler = (newDays) => {
        setDays(newDays)
        getNewData()
    }

but it doesn't change anything.

CodePudding user response:

The Problem

Your setState method is not working properly since you are passing two parameters as to it.

The Solution:

in the DataContextProvider:

const DataContext = createContext({ days: 720, data: { timestamp: [], price: [], short_ma: [], long_ma: [], money: [], action: [] }, daysChangeHandler: (newDays) => { } })
export default DataContext

export const DataContextProvider = (props) => {
    const daysChangeHandler = (newDays) => {
        setDays(newDays)
        getData(days).then(response => setData(response))
    }

    const [days, setDays] = useState(720)
    const [data, setData] = useState({ timestamp: [], price: [], short_ma: [], long_ma: [], money: [], action: [] })

    return <DataContext.Provider
        value={{ days: days, data: data, daysChangeHandler: daysChangeHandler }}>
        {props.children}
    </DataContext.Provider>
}

Optional

If you want to use a callback function in your setDays to get the new Data with the previous implementation:

const daysChangeHandler = (newDays) => {
    setDays(newDays, () => getNewData())

}
const getNewData = () => {
    getData(days).then(response => setData(response))
    console.log('NEW DATA')
}

Also, you can pass the value object toDataContext.Provider in a shorter way since the key and value are equals:

return (
    <DataContext.Provider
        value={{ days, data, daysChangeHandler }}>
        {props.children}
    </DataContext.Provider>
)

CodePudding user response:

I make a mockup that is specific to your use case

My implementation is this

You may go to CodeSandbox to interact my Demo. But the code is just below

context.tsx file

import { createContext, useState } from "react";

const mockUp = {
  days: 720,
  data: {
    timestamp: [],
    price: [],
    short_ma: [],
    long_ma: [],
    money: [],
    action: []
  },
  daysChangeHandler: (newDays) => {}
};

const DataContext = createContext(mockUp);
export default DataContext;

export const DataContextProvider = (props) => {
  const [days, setDays] = useState(720);
  const [data, setData] = useState(mockUp);

  const daysChangeHandler = (newDays) => {
    getNewData();
    setDays(newDays);
  };
  const getNewData = () => {
    // getData(days).then(response => setData(response))
    // getData(days) mock up
    Promise.resolve(mockUp).then((res) =>
      setData(() => ({ ...res, days: 370 }))
    );
    console.log("NEW DATA");
  };

  return (
    <DataContext.Provider
      value={{ days: days, data: data, daysChangeHandler: daysChangeHandler }}
    >
      {props.children}
    </DataContext.Provider>
  );
};

DataChart.tsx

import { useContext, useEffect } from "react";
import DataContext from "./context";

export default function DataChart(props) {
  const { data } = useContext(DataContext);
  useEffect(() => {
    console.log("data chart", data);
  }, [data]);

  return null; //rendered data here, the updated data should be displayed but is only on the second time I click the submit button
}

NumberOfDays.tsx

import { useContext } from "react";
import DataContext from "./context";

export default function NumberOfDays(props) {
  const context = useContext(DataContext);

  const predefinedDaysHandler = (days) => {
    context.daysChangeHandler(days);
  };
  return (
    <button className="submit-button" onClick={() => predefinedDaysHandler(0)}>
      Max
    </button>
  );
}

App.tsx file

import { DataContextProvider } from "./context";
import DataChart from "./DataChart";
import NumberOfDays from "./NumberOfDays";

export default function App() {
  return (
    <DataContextProvider>
      <DataChart />
      <NumberOfDays />
    </DataContextProvider>
  );
}

CodePudding user response:

the solution is to call useEffect and set days as a dependency inside of context:

useEffect(() => {
        getData(days).then(response => setData(response))
    }, [days]);```
  • Related