Convert class component function to a functional component function - reactjs

I have class component functions that handle a search function.
filterData(offers,searchKey){
const result = offers.filter((offer) =>
offer.value.toLowerCase().includes(searchKey)||
offer.expiryDate.toLowerCase().includes(searchKey)||
offer.quantity.toLowerCase().includes(searchKey)
)
this.setState({offers:result})
}
const handleSearchArea = (e) =>{
const searchKey=e.currentTarget.value;
axios.get(`/viewPendingSellerOffers`).then(res=>{
if(res.data.success){
this.filterData(res.data.existingOffers,searchKey)
}
});
}
Now I try to convert these class component functions to functional component functions. To do this I tried this way.
const filterData = (offers,searchKey) => {
const result = offers.filter((offer) =>
offer.value.toLowerCase().includes(searchKey)||
offer.expiryDate.toLowerCase().includes(searchKey)||
offer.quantity.toLowerCase().includes(searchKey)
)
setState({offers:result})
}
const handleSearchArea = (e) =>{
const searchKey=e.currentTarget.value;
axios.get(`/viewPendingSellerOffers`).then(res=>{
if(res.data.success){
filterData(res.data.existingOffers,searchKey)
}
});
}
But I get an error that says "'setState' is not defined". How do I solve this issue?

Solution:
import React, {useState} from "react";
import axios from "axios";
const YourFunctionalComponent = (props) => {
const [offers, setOffers] = useState()
const filterData = (offersPara, searchKey) => {// I changed the params from offers to offersPara because our state called offers
const result = offersPara.filter(
(offer) =>
offer?.value.toLowerCase().includes(searchKey) ||
offer?.expiryDate.toLowerCase().includes(searchKey) ||
offer?.quantity.toLowerCase().includes(searchKey)
);
setOffers(result);
};
const handleSearchArea = (e) => {
const searchKey = e.currentTarget.value;
axios.get(`/viewPendingSellerOffers`).then((res) => {
if (res?.data?.success) {
filterData(res?.data?.existingOffers, searchKey);
}
});
};
return (
//To use your *offers* state object just call it like this {offers?.El1?.El2}
);
};
export default YourFunctionalComponent;
Note: It is recommended to do null check before accessing nested objects like this res?.data?.existingOffers, Optional chaining will help us in this regard.

Related

React Hooks: How can I use a hook to map an array from a dumb component

I have my api call in a React hook, like so:
import { getAllTheBakeries } from './bakeries.api';
export const useBakeryList = () => {
const [bakeries, setBakeries] = React.useState([]);
React.useEffect(() => {
const fetchBakeries = async () => {
try {
const bakeryList = await getAllTheBakeries();
setBakeries(bakeryList.bakeries);
} catch (error) {
console.log(error);
}
};
fetchBakeries();
},[])
return bakeries;
}
And I want to use this returned array in a dumb component like so:
import { useBakeryList } from './bakery-list.hook';
export const BakeryList = () => {
// I can see the returned array in my console
console.log(useBakeryList())
// access the returned array and map it
return(
{bakeries.map((bakery) => (
<p>bakery.name</p>
))}
)
}
What is the proper syntax to access that array returned by the hook?
import { useBakeryList } from './bakery-list.hook';
export const BakeryList = () => {
let bakeries = useBakeryList();
return(
{bakeries.map((bakery) => (
<p>bakery.name</p>
))}
)
}
Do you want something like that?

How do I return data from a custom react hook?

I have a custom react hook that is fetching data from an api. I'm getting error: "Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component."
I don't know if this is due to the structure of my custom hook or how I'm using the hook in the component where it's being called.
Here is my custom hook:
import { useState, useEffect } from "react";
const axios = require('axios');
const crypto = require('crypto');
function useFetchNGSData(endpoint) {
const[data, setData] = useState(null);
const dateString = () => {
// MORE CODE
return date.toString();
}
const ngs_username = process.env.REACT_APP_NGS_USERNAME;
const ngs_password = process.env.REACT_APP_NGS_PASSWORD;
const ngs_access_token = process.env.REACT_APP_NGS_ACCESS_TOKEN;
const ngs_secret_key = process.env.REACT_APP_NGS_SECRET_KEY;
const string_to_sign = ngs_username + ngs_password + ngs_access_token + dateString();
const digest = crypto.createHmac('sha1', ngs_secret_key).update(string_to_sign).digest('base64');
const authKey = 'NGS ' + ngs_access_token + ':' + digest;
const url = 'https://api.ngs.com';
useEffect(() => {
axios.defaults.headers.common['Authorization'] = authKey;
axios.get(url + endpoint)
.then((response) => {
setData(response.data);
})
.catch((error) => {
console.log(error);
})
},[endpoint]);
return { data };
}
export default useFetchNGSData;
Here's the component that is using the custom hook:
import React, { useState } from 'react';
import useFetchNGSData from '../useFetchNGSData';
const Data = (props) => {
const [players, setPlayers] = useState([]);
const handleGetPlayers = () => {
teamCollection.forEach(teamId => {
setPlayers(useFetchNGSData('/league/roster/current?teamId=5100')
});
}
return (
<div>
// ... handleGetPlayers
</div>
);
}
export default Data;
I know I'm omitting a lot of code but hopefully this is enough to demonstrate the issue.
You need to move the hook out of the event handler, and in to the body of the component.
import React, { useState } from 'react';
import useFetchNGSData from '../useFetchNGSData';
const Data = (props) => {
const { data: players } = useFetchNGSData('/league/roster/current?teamId=5100')
return (
<div>
// ... handleGetPlayers
</div>
);
}
export default Data;
But really, you don't need to use setPlayers here. You already have that data in your returned values from your custom hook.

Access React Context value outside of the body of a function component

Case
I want to make isLoading (global state using React Context) value and changeIsLoading function (its changing function from IsLoadingContext.js file) becomes accessible to all files (function components and simple javascript functions).
I know that React Hooks can only be called inside of the body of a function component.
Question: So in my case here, how could I called isLoading and changeIsLoading inside a util file (non-function component or just a simple javascript function)?
What should I change from the code?
Code flow
(location: SummariesPage.js) Click the button inside SummariesPage component
(location: SummariesPage.js) Call onApplyButtonIsClicked function in SummariesPage component
(location: SummariesPage.js) Change isLoading global state into true then call fetchAPISummaries function
(location: fetchAPISummaries.js) Call fetchAPICycles function
(location: fetchAPICycles.js) Call exportJSONToExcel function
(location: exportJSONToExcel.js) Export the JSON into an Excel file then change isLoading global state into false
IsLoadingContextProvider component will be rerendered and the isLoading value in SummariesPage will be true
Error logs
Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
The code
IsLoadingContext.js:
import React, { useState } from 'react'
const IsLoadingContext = React.createContext()
const IsLoadingContextProvider = (props) => {
const [isLoading, setIsLoading] = useState(false)
const changeIsLoading = (inputState) => {
setIsLoading(inputState)
}
return(
<IsLoadingContext.Provider
value={{
isLoading,
changeIsLoading
}}
>
{props.children}
</IsLoadingContext.Provider>
)
}
export { IsLoadingContextProvider, IsLoadingContext }
SummariesPage.js:
import React, { useContext } from 'react'
// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'
// COMPONENTS
import Button from '#material-ui/core/Button';
// UTILS
import fetchAPISummaries from '../../utils/export/fetchAPISummaries'
const SummariesPage = () => {
const { isLoading, changeIsLoading } = useContext(IsLoadingContext)
const onApplyButtonIsClicked = () => {
changeIsLoading(true)
fetchAPISummaries(BEGINTIME, ENDTIME)
}
console.log('isLoading', isLoading)
return(
<Button
onClick={onApplyButtonIsClicked}
>
Apply
</Button>
)
}
export default SummariesPage
fetchAPISummaries.js:
// UTILS
import fetchAPICycles from './fetchAPICycles'
const fetchAPISummaries = (inputBeginTime, inputEndTime) => {
const COMPLETESUMMARIESURL = .....
fetch(COMPLETESUMMARIESURL, {
method: "GET"
})
.then(response => {
return response.json()
})
.then(responseJson => {
fetchAPICycles(inputBeginTime, inputEndTime, formatResponseJSON(responseJson))
})
}
const formatResponseJSON = (inputResponseJSON) => {
const output = inputResponseJSON.map(item => {
.....
return {...item}
})
return output
}
export default fetchAPISummaries
fetchAPICycles.js
// UTILS
import exportJSONToExcel from './exportJSONToExcel'
const fetchAPICycles = (inputBeginTime, inputEndTime, inputSummariesData) => {
const COMPLETDEVICETRIPSURL = .....
fetch(COMPLETDEVICETRIPSURL, {
method: "GET"
})
.then(response => {
return response.json()
})
.then(responseJson => {
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson))
})
}
const formatResponseJSON = (inputResponseJSON) => {
const output = inputResponseJSON.map(item => {
.....
return {...item}
})
return output
}
export default fetchAPICycles
exportJSONToExcel.js
import { useContext } from 'react'
import XLSX from 'xlsx'
// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'
const ExportJSONToExcel = (inputSummariesData, inputCyclesData) => {
const { changeIsLoading } = useContext(IsLoadingContext)
const sheetSummariesData = inputSummariesData.map((item, index) => {
let newItem = {}
.....
return {...newItem}
})
const sheetSummaries = XLSX.utils.json_to_sheet(sheetSummariesData)
const workBook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workBook, sheetSummaries, 'Summaries')
inputCyclesData.forEach(item => {
const formattedCycles = item['cycles'].map((cycleItem, index) => {
.....
return {...newItem}
})
const sheetCycles = XLSX.utils.json_to_sheet(formattedCycles)
XLSX.utils.book_append_sheet(workBook, sheetCycles, item['deviceName'])
})
XLSX.writeFile(workBook, `......xlsx`)
changeIsLoading(false)
}
export default ExportJSONToExcel
I believe the real problem you are facing is managing the asynchronous calls. It would be much readable if you use async/await keywords.
const onApplyButtonIsClicked = async () => {
changeIsLoading(true)
await fetchAPISummaries(BEGINTIME, ENDTIME)
changeIsLoading(false)
}
You will need to rewrite fetchAPICycles to use async/await keywords instead of promises.
const fetchAPICycles = async (
inputBeginTime,
inputEndTime,
inputSummariesData
) => {
const COMPLETDEVICETRIPSURL = ...;
const response = await fetch(COMPLETDEVICETRIPSURL, {
method: "GET",
});
const responseJson = await response.json();
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
};

React Hooks + Mobx => Invalid hook call. Hooks can only be called inside of the body of a function component

I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}

React exporting useContext causes errors

Context.js
const GlobalContext = React.createContext();
const initState = {count:0};
const GlobalContextProvider = props => {
const [state, setState] = useState(initState);
return (
<GlobalContext.Provider value={{state:state, setState:setState}}>
{props.children}
</GlobalContext.Provider>
)
};
const GlobalContextValue = useContext(GlobalContext)
export {GlobalContextValue, GlobalContextProvider}
When I exported the GlobalContextValue, Chrome or React throws an error saying this is an invalid hook call, but I want to be able use setState in a module that's showing below.
fetchAPI.js
import { GlobalContextValue } from './GlobalContext';
const {state, setState} = GlobalContextValue;
function load() {
fetch('localhost:8000/load')
.then(res => res.json())
.then(json => setState(json));
};
You can't use hooks outside of React functional components.
You can probably do this another way though.
Disclaimer: I didn't test this code, but it should do what you want, although I don't recommend doing this at all.
const GlobalContext = React.createContext();
const globalState = { count: 0 }
let subscribers = []
export function setGlobalState(value) {
Object.assign(globalState, value)
subscribers.forEach(f => f(globalState))
}
export function subscribe(handler) {
subscribers.push(handler)
return () => {
subscribers = subscribers.filter(s => s !== handler)
}
}
const GlobalContextProvider = props => {
const [state, setState] = useState(globalState)
useEffect(() => subscribe(setState), [])
return (
<GlobalContext.Provider value={{ state: state, setState: setGlobalState }}>
{props.children}
</GlobalContext.Provider>
);
};

Resources