How can I change a project thats done by using class based "this, this.props etc" to function based?

Viewed 90

I started to change the codes from class based to function based. (Sorry if im wrong to say that. I mean i am just deleting "this" , "render" keywords, so it becomes function based as I know.

Here is the first part which i changed :

const GlobalContext = createContext({});

const GlobalContextProvider = () => {
  const [JSON, setJSON] = useState('.json?cache=2');
  const [debug, setDebug] = useState(true);
  const [currentTheme, setCurrentTheme] = useState("light");
  const [keyboardOpen, setKeyboardOpen] = useState(false);
  const [isBottomSheetOpen, setBottomSheetOpen] = useState(false);
  const [showSplash, setShowSplash] = useState(true);
  const [showLoginRegister, setShowLoginRegister] = useState(true);
  const [inAction, setInAction] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [isUploading, setIsUploading] = useState(false);
  const [userToken, setUserToken] = useState({
    email: "",
    password: ""
  });
  const [userData, setUserData] = useState({
    name:"",
    surname:"",
    email:"",
    accountType:"",
    remaining:0,
    quota:0,
    corporate:"",
    apiKEY:"",
    apiURL:""
  });
  const [folderData, setFolderData] = useState([]);
  const [receiptData, setReceiptData] = useState([]);
  const [currentData, setCurrentData] = useState([]);
  const [baseEmptyData, setBaseEmptyData] = useState([]);
  const [extra, setExtra] = useState([]);
  const [items, setItems] = useState([{
    urunAdi: '',
    tutar: '',
    kdv: ''
    }
  ]);
  const [toastRef, setToastRef] = useState({});
  const [sheetRef, setSheetRef] = useState(null); // its same as doing useState({})
  const [settings, setSettings] = useState({
    theme: 'light',
    maxYear: 2,
    showEmpty: false,
  });

  // componentDidMount and componentWillMount Parts here
  const KeyboardFunction = () => {
    useEffect(() =>  {
      Keyboard.addListener("keyboardDidShow", _keyboardDidShow);
      Keyboard.addListener("keyboardDidHide", _keyboardDidHide);

      // cleanup function
      return () => {
        Keyboard.removeListener("keyboardDidShow", _keyboardDidShow);
        Keyboard.removeListener("keyboardDidHide", _keyboardDidHide);
      };
    }, []);
  }
  
  _keyboardDidShow = () => setKeyboardOpen(true);
  _keyboardDidHide = () => {
    return (
      setTimeout(() => {
        setKeyboardOpen(true)
      }, 200) // 200 ms
    );
  }

  const setBottomSheet = (val) => setBottomSheetOpen(val);

  /* MISC BEGIN */
  const log = (...args) => {Debug ? console.log(Array.prototype.slice.call(args).join(" ")): undefined};

And for callback in useEffect(), I defined this. How can I call setCB() in useEffect() function?

const [cbRef,setCB] =  useState();  // this is for calling callback functions in useEffect()
  useEffect(()=> {
    if(cbRef != null){
      cbRef();
    }
  },[cbRef]);

How can I change the rest? The rest is like that, I mean if you could help me with one function, I may do the same with other functions, they look similar.

 loadSettings = async (callback) => {
    try {
      const data = await AsyncStorage.getItem('settings');
      if (data !== null) {
        const jData = Object.assign({}, this.state.settings, JSON.parse(data));
        this.setState({ settings: jData, currentTheme: jData.theme }, () => {
          callback();
        });
      } else {
        await AsyncStorage.setItem('settings', JSON.stringify(this.state.settings));
        this.setState({ currentTheme: 'light' }, () => {
          callback();
        });
      }
    } catch (err) {
      this.Log('loadSettings Error => ', err);
      callback();
    }
  }
  saveSettings = async (key, value, callback) => {
    try {
      let jData = this.state.settings;
      jData[key] = value;
      this.Log(jData);
      await AsyncStorage.setItem('settings', JSON.stringify(jData));
      this.setState({ settings: jData }, () => {
        if (callback != undefined) {
          callback();
        }
      })
    } catch (err) {
      this.Log('saveSettings Error => ', err);
      if (callback != undefined) {
        callback();
      }
    }
  }
  toggleTheme = (callback) => {
    this.setState(({ currentTheme }) => ({
      currentTheme: currentTheme === 'light' ? 'dark' : 'light',
    }), () => {
      callback();
    });
  };
  /* MISC END */

  /**
   * Save data to currentData state in order to reach from different scenes.
   * Params: currentData:string, callback:function -> void
   */
  setCurrentData = (currentData, callback) => {
    this.setState({ currentData }, () => {
      if (callback != undefined) { callback(); }
    });
  }

  /**
   * Hide Splash Screen
   * Params: NONE
   */
  hideSplash = () => { this.setState({ showSplash: false }); };

  /**
   * Hide Login-Register Screen
   * Params: NONE
   */
  hideLoginRegister = () => { this.setState({ showLoginRegister: false }); };

  /**
   * Get Async Storage Data from Device, such as userToken or App Settings
   * Params: DataKey:string, callback:function -> json
   */
  getDataFromPhone = async (dataKey, callback) => {
    try {
      const data = await AsyncStorage.getItem(dataKey);
      if (data !== null) {
        callback({ "errorCode": -1, "data": JSON.parse(data) });
      } else {
        this.Log('Data ("' + dataKey + '") you try to fetch is empty');
        callback({ "errorCode": "ASYNC_STORAGE_EMPTY" });
      }
    } catch (err) {
      this.Log('getDataFromPhone Error => ', err);
      callback({ "errorCode": "ASYNC_STORAGE_EMPTY" });
    }
  }

  /**
   * Set Async Storage Data on Device, such as usertoken or App Settings
   * Params: DataKey:string, Data:string, callback:function -> json
   */
  setDataToPhone = async (dataKey, data, callback) => {
    try {
      await AsyncStorage.setItem(dataKey, JSON.stringify(data));
      if (callback != null || callback != undefined) {
        callback({ "errorCode": -1 });
      }
    } catch (err) {
      this.Log('setDataToPhone Error => ', err);
      if (callback != null || callback != undefined) {
        callback({ "errorCode": "ASYNC_STORAGE_SETITEM" });
      }
    }
  }

  /* FILE ACTION BEGIN */

  /**
   * Set Uploading State to given param
   * Params: Uploading:boolean, callback:function -> void
   */
  setUploading = (uploading, callback) => {
    this.setState({ uploading }, () => {
      if (callback != undefined) {
        callback();
      }
    });
  }
5 Answers

As request, this is an example for one of your functions.

Since you have declared const [ settings, setSettings ] = useState(), you may use settings directly instead of this.state.settings

useState does not accept a callback, so you need to move the call back into useEffect.

 loadSettings = async (callback) => {
    try {
      const data = await AsyncStorage.getItem('settings');
      if (data !== null) {
        const jData = Object.assign({}, settings, JSON.parse(data));
        setSettings({ settings: jData, currentTheme: jData.theme })
      } else {
        await AsyncStorage.setItem('settings', JSON.stringify(settings));
        setSettings({ currentTheme: 'light' });
      }
    } catch (err) {
      this.Log('loadSettings Error => ', err);
      callback(); 
    }
  }

useEffect(() => {
     callback() //the callback function
},[settings])

With the dependency [settings] in useEffect, it will be called whenever the settings state is changed.

You can have multiple useEffect in each functional component.

Bonus

If you only want to call useEffect once when page loads, you leave the dependency empty like following.

useEffect(() => {
   //whatever function i want to call when component mounts.
}, [])

The useState hook doesn't have a callback to call after state has been updated, you should use an useEffect hook with a dependency on the state that was updated.

this.Log should now also be your log function, and the miscellaneous references to this.state.XXX should just be the new state variables you defined previously via useState hooks.

loadSettings = async (callback) => {
  try {
    const data = await AsyncStorage.getItem('settings');
    if (data !== null) {
      const jData = Object.assign({}, settings, JSON.parse(data));
      setSettings(jData);
      setCurrentTheme(jData.theme);
    } else {
      await AsyncStorage.setItem('settings', JSON.stringify(settings));
      setCurrentTheme('light');
    }
  } catch (err) {
    log('loadSettings Error => ', err);
    callback();
  }
}

useEffect(() => { // <-- hook callback called when any dependency updates
  callback();
}, [settings, currentTheme]); // <-- add any other dependencies to array

when declaring states with hooks like useState and useEffect, you don't need to bind the functions. For example, you declared a state like

const [settings, setSettings] = useState({
    theme: 'light',
    maxYear: 2,
    showEmpty: false,
  });

so, you can refer to it without using this.state , and one more thing is useState cannot accept callback, but useEffect can because it uses lifecycles methods of react.

Summary:

instead of using this.state.name , use directly name and where there are callbacks, use useEffect.

I am answering your another question which you asked me in comment.. So, after doing a little research, i have come across a solution. Let take an example of

     function example({ userId }) {
      const [data, dataSet] = useState();
    
  // this external asynchronous function is called in useEffect
      const fetchMyAPI = useCallback(async () => {
        let response = await fetch('api/data/' + userId)
        response = await response.json()
        dataSet(response)
      }, [userId])
                
    
      useEffect(() => {
        fetchMyAPI()
      }, [fetchMyAPI])
    
    }

Notice the difference.. try using

const loadSettings = useCallback(async () => {
    //code here
})

instead of

loadSettings = async (callback) => {
     //code here
}

if you want more help then must consider reading that post

As you have already declared state in your program using hooks like:

const [showLoginRegister, setShowLoginRegister] = useState(true);
 const [userToken, setUserToken] = useState({
    email: "",
    password: ""
  });

so, you don't need to use "this" keyword in order to use or set state of functional component.

const logout = () => { 
  setToken({ 
      email: '', password: '', 
   }, () => { 
       showLoginRegister: true
  }); 
}

Now look what I have done is just erase "this" keyword from your syntax.

NOTE

if you are confused with the "this" statement, then remember, that "this" keyword is only used to call/refer react state in class components. In functional components, you can call/refer to the state directly. But there are more valid reasons behind that.

Related