0

My Use Case

I am just starting with React (Native) with Hooks and Redux and have some issues understanding some parts of the state update concept for my use case. I am currently working on a React Native App that should update data every full minute. So if the user enters the app at say 11h:31m:27s,

  1. the initial state should be shown, then
  2. a setTimeout handles the first update with the offset to reach the full minute (in this case 33s to reach 11h:32m:00s)
  3. a setInterval handles the minutely update of the data from now on.

My Issue

Currently, with the below code the console.log("Reducer Fires!") inside the reducers.js file is fired increasingly more with every new minute (e.g. only once in the first minute, then two times in the second minute and so on). So I feel that I am still doing something wrong with how I handle the state of my data together with the setInterval() and setTimeout() logic. Could somebody review my code below and point me to the errors I am doing?

My Code (so far)

some dummy data:

export const DATA = [
    {"id": 1, "myAttr": 0},
    {"id": 2, "myAttr": 4},
    {"id": 3, "myAttr": 8},
    {"id": 4, "myAttr": 12},
]

the App.js file with the redux store:

import React from 'react';
import { createStore, combineReducers } from "redux";
import { Provider } from "react-redux";
import { View } from 'react-native';

// Components
import MyComponent from "./components/MyComponent";
import dataReducer from "./store/reducers";

//Create Redux store
const rootReducer = combineReducers({
  mydata: dataReducer
});

const store = createStore(rootReducer);

export default function App() {

  return (
    <Provider store={store} >
      <View>
        <MyComponent />
      </View>
    </Provider>
  );
}

the actions.js file storing my Redux action:

//Action to update the data
export const UPDATE_DATA = "UPDATE_DATA";

export const updateData = () => {
    return { type: UPDATE_DATA }
}

the reducers.js file to store the reducers:

import { DATA } from "../data/data";
import { UPDATE_DATA } from "./actions";

// Initial state when app launches
const initState = {
    data: DATA
}

const dataReducer = (state = initState, action) => {
    switch (action.type) {
        case UPDATE_DATA:
            //Update an attribute in each object of the data array

            const updatedData = state.data.map((i) => {
                const newMyAttr = i.myAttr + 1;
                return { ...i, myAttr: newMyAttr }
            }
            )

            console.log("Reducer Fires!");
            return { ...state, data: updatedData }

        default:
            return state;
    }
};

export default dataReducer;

the component where I am trying to update the state:

import React, { useEffect, useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Text, View } from 'react-native';

const MyComponent = (props) => {
    const myData = useSelector(state => state.mydata.data);

    let start = new Date();
    const fullMinuteOffset = (60 - start.getSeconds()) * 1000;

    const dispatch = useDispatch();

    const saveData = useCallback(() => {
        dispatch(updateData())
    }, [dispatch]);

    useEffect(() => {
        const timer = setTimeout(() => {
            saveData()
            const interval = setInterval(() => {
                saveData()
            }, 60000)

            return () => clearInterval(interval);
        }, fullMinuteOffset);

        return () => clearTimeout(timer);
    }, [saveData, fullMinuteOffset]);

    return(
        <View>
            <Text>{myData[0].myAttr}</Text>
        </View>
    );
};

export default MyComponent;

JoeBe
  • 1,224
  • 3
  • 13
  • 28

2 Answers2

2

Every time the component renders it will have a new value of fullOffserMinute and it's a dependency of useEffect thus registering a new interval call.

Here's a simplified working example of your code:

https://codesandbox.io/s/jolly-panini-k35s1?fontsize=14&hidenavigation=1&theme=dark

  // the relevant part of it
  let start = new Date();
  let [counter, setCounter] = useState(0);
  const fullMinuteOffset = (60 - start.getSeconds()) * 1000;

  function timerAction() {
    setCounter(counter => counter + 1);
  };

  useEffect(() => {
    console.log("-- use effect --");
    const timer = setTimeout(() => {
      console.log("save data timeout");
      timerAction();
      const interval = setInterval(() => {
        console.log("save data interval");
        timerAction();
      }, 5000);

      return () => clearInterval(interval);
    }, fullMinuteOffset);

    return () => clearTimeout(timer);
  }, []);

You should also read the answers here if you're going to update local state in intervals: State not updating when using React state hook within setInterval

ksankar
  • 465
  • 7
  • 13
  • Thanks for this. When I use the code the way you wrote it, my issue with the multiple prints is gone. – JoeBe Feb 17 '20 at 06:48
1

What happens if you change the useEffect dependencies to just this: [saveTime, fullMinuteOffset] -> to this []. It seems like everytime that component updates that useEffect call might be registering more intervals

bdanzer
  • 57
  • 1
  • 7