React Native - Class Component to Functional Component (UseEffect: state undefined) - javascript

I need to convert my Class Components code to Functional Hooks Components. I have the Class Components logic below that works. However for me to implement Context Api I need to convert it to hooks.
I get storeList undefined when I console log it and this error...
TypeError: undefined is not an object (evaluating 'storeList'.map).
Is there a way I can make introduce a state inside a UseEffect? Thank you in advance
const { data, status } = useQuery('stores', fetchStores)
const [isSelected, setIsSelected] = useState(false)
const storeList = data
useEffect(() => {
let array = storeList.map((item, index) => {
isSelected = false
return { ...item }
})
setIsSelected({ ...isSelected, array: { ...storeList.array } })
selectHandler()
}, [])
const selectHandler = (ind) => {
let array = storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
setIsSelected({ ...isSelected, array: { ...storeList.array } })
}
Here is the same code as Class component and it works perfectly
async componentDidMount() {
let array = this.state.storeList.map((item, index) => {
this.isSelected = false
return { ...item }
})
this.setState({ storeList: array })
}
selectionHandler = (ind) => {
const { storeList } = this.state
let array = storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
this.setState({ storeList: array })
}

Try This:
const { data, status } = useQuery('stores', fetchStores)
const [isSelected, setIsSelected] = useState(false)
const storeList = data
useEffect(() => {
let array = storeList.map((item, index) => {
isSelected = false
return { ...item }
})
setIsSelected(state => ({ ...state, array: { ...storeList.array } }));
selectHandler()
}, [])
const selectHandler = (ind) => {
let array = storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
setIsSelected(state => ({ ...state, array: { ...storeList.array } }));
}
Reference: https://stackoverflow.com/a/63522873/10994570

This worked!! I hope it can help someone else. Thanks
const { data, status } = useQuery('stores', fetchStores)
const [isSelected, setIsSelected] = useState(false)
const storeList = data
useEffect(() => {
handlerControl()
}, [])
const handlerControl = async () => {
try {
let array = await storeList.map((item, index) => {
isSelected = false
return { ...item }
})
setIsSelected(array)
} catch (e) {
console.log(e)
}
}
const selectHandler = async (ind) => {
//alert('pressed')
try {
let array = await storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
setIsSelected(array)
console.log(index)
} catch (e) {
console.log(e)
}
}

Related

React - how can I make the return of JSX wait until my useEffect() ended [duplicate]

I have fetch method in useEffect hook:
export const CardDetails = () => {
const [ card, getCardDetails ] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => getCardDetails(data))
}, [id])
return (
<DetailsRow data={card} />
)
}
But then inside DetailsRow component this data is not defined, which means that I render this component before data is fetched. How to solve it properly?
Just don't render it when the data is undefined:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data));
}, [id]);
if (card === undefined) {
return <>Still loading...</>;
}
return <DetailsRow data={card} />;
};
There are 3 ways to not render component if there aren't any data yet.
{data && <Component data={data} />}
Check if(!data) { return null } before render. This method will prevent All component render until there aren't any data.
Use some <Loading /> component and ternar operator inside JSX. In this case you will be able to render all another parts of component which are not needed data -> {data ? <Component data={data} /> : <Loading>}
If you want to display some default data for user instead of a loading spinner while waiting for server data. Here is a code of a react hook which can fetch data before redering.
import { useEffect, useState } from "react"
var receivedData: any = null
type Listener = (state: boolean, data: any) => void
export type Fetcher = () => Promise<any>
type TopFetch = [
loadingStatus: boolean,
data: any,
]
type AddListener = (cb: Listener) => number
type RemoveListener = (id: number) => void
interface ReturnFromTopFetch {
addListener: AddListener,
removeListener: RemoveListener
}
type StartTopFetch = (fetcher: Fetcher) => ReturnFromTopFetch
export const startTopFetch = function (fetcher: Fetcher) {
let receivedData: any = null
let listener: Listener[] = []
function addListener(cb: Listener): number {
if (receivedData) {
cb(false, receivedData)
return 0
}
else {
listener.push(cb)
console.log("listenre:", listener)
return listener.length - 1
}
}
function removeListener(id: number) {
console.log("before remove listener: ", id)
if (id && id >= 0 && id < listener.length) {
listener.splice(id, 1)
}
}
let res = fetcher()
if (typeof res.then === "undefined") {
receivedData = res
}
else {
fetcher().then(
(data: any) => {
receivedData = data
},
).finally(() => {
listener.forEach((cb) => cb(false, receivedData))
})
}
return { addListener, removeListener }
} as StartTopFetch
export const useTopFetch = (listener: ReturnFromTopFetch): TopFetch => {
const [loadingStatus, setLoadingStatus] = useState(true)
useEffect(() => {
const id = listener.addListener((v: boolean, data: any) => {
setLoadingStatus(v)
receivedData = data
})
console.log("add listener")
return () => listener.removeListener(id)
}, [listener])
return [loadingStatus, receivedData]
}
This is what myself needed and couldn't find some simple library so I took some time to code one. it works great and here is a demo:
import { startTopFetch, useTopFetch } from "./topFetch";
// a fakeFetch
const fakeFetch = async () => {
const p = new Promise<object>((resolve, reject) => {
setTimeout(() => {
resolve({ value: "Data from the server" })
}, 1000)
})
return p
}
//Usage: call startTopFetch before your component function and pass a callback function, callback function type: ()=>Promise<any>
const myTopFetch = startTopFetch(fakeFetch)
export const Demo = () => {
const defaultData = { value: "Default Data" }
//In your component , call useTopFetch and pass the return value from startTopFetch.
const [isloading, dataFromServer] = useTopFetch(myTopFetch)
return <>
{isloading ? (
<div>{defaultData.value}</div>
) : (
<div>{dataFromServer.value}</div>
)}
</>
}
Try this:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
if (!data) {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data))
}
}, [id, data]);
return (
<div>
{data && <DetailsRow data={card} />}
{!data && <p>loading...</p>}
</div>
);
};

useEffect is running when any function is running

First of all, I researched the question a lot, but I could not find a solution. I would appreciate if you help.
functional component
I add the code briefly below. this is not full code
state and props
// blog id
const { id } = props.match.params;
// state
const initialState = {
title: "",
category: "",
admin_id: "",
status: false
};
const [form, setState] = useState(initialState);
const [adminList, setAdminList] = useState([]);
const [articleText, setArticleText] = useState([]);
const [me, setMe] = useState([]);
const [locked, setLocked] = useState(true);
const timerRef = useRef(null);
// queries and mutations
const { loading, error, data } = useQuery(GET_BLOG, {
variables: { id }
});
const { data: data_admin, loading: loading_admin } = useQuery(GET_ADMINS);
const [editBlog, { loading: loadingUpdate }] = useMutation(
UPDATE_BLOG
);
const [lockedBlog] = useMutation(LOCKED_BLOG);
multiple useEffect and functions
useEffect(() => {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
}, [loading, data]);
useEffect(() => {
if (!loading_admin && data_admin) {
const me = data_admin.admins.filter(
x => x.id === props.session.activeAdmin.id
);
setAdminList(data_admin);
setMe(me[0]);
}
}, [data_admin, loading_admin]);
useEffect(() => {
const { id } = props.match.params;
lockedBlog({
variables: {
id,
locked: locked
}
}).then(async ({ data }) => {
console.log(data);
});
return () => {
lockedBlog({
variables: {
id,
locked: false
}
}).then(async ({ data }) => {
console.log(data);
});
};
}, [locked]);
// if loading data
if (loading || loading_admin)
return (
<div>
<CircularProgress className="loadingbutton" />
</div>
);
if (error) return <div>Error.</div>;
// update onChange form
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
// editor update
const onChangeEditor = text => {
const currentText = articleText.text;
const newText = JSON.stringify(text);
if (currentText !== newText) {
// Content has changed
if (timerRef.current) {
clearTimeout(timerRef.current);
}
setArticleText({ text: newText });
if (!formValidate()) {
timerRef.current = setTimeout(() => {
onSubmitAuto();
}, 10000);
}
}
};
// auto save
const onSubmitAuto = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
editBlog({
variables: {
id,
admin_id,
title,
text: articleText.text,
category,
status
}
}).then(async ({ data }) => {
console.log(data);
});
};
// validate
const formValidate = () => {
const { title, category } = form;
return !title || !articleText.text || !category;
};
// clear state
const resetState = () => {
setState({ ...initialState });
};
return (
// jsx
)
first issue, when call onSubmitAuto, first useEffect is running again. i dont want this.
because I just want it to work on the first mount.
second issue, if the articleText state has changed before, when mutation it does not mutate the data in the form state. but if the form state changes first, it mutated all the data. I think this issue is the same as the first issue.
I hope I could explain the problem. :/
Ciao, I have an answer to the first issue: when onSubmitAuto is triggered, it calls editBlog that changes loading. And loading is on first useEffect deps list.
If you don't want that, a fix could be something like that:
const isInitialMount = useRef(true);
//first useEffect
useEffect(() => {
if(isInitialMount.current) {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
if (data !== undefined) isInitialMount.current = false;
}
}, [loading, data]);

multiple custom react hooks in the same component

Is it an acceptable practice to have two custom react hooks in the same component, one after another?
The issue I am dealing with is as follows:
The first custom hook useBudgetItems will load, but the subsequent one will be undefined. I think I understand why it's happening (my budgetSettings property inside my useBudgetSettings loads after the console.log() statement), but I am not sure how to get around this and whether this is the right approach.
const BudgetCost ({ projectId }) => {
const { budgetCost, loaded } = useBudgetCost({ key: projectId });
const { budgetSettings } = useBudgetSettings({ key: projectId });
const [totalBudget, setTotalBudget] = useState(budgetCost.totalBudget);
const [budgetCosts, setbudgetCosts] = useState(budgetCost.items);
// This will be undefined
console.log(budgetSettings)
if(!loaded) return <div/>
return (
...
...
)
});
My useBudgetCost custom hook is as follow (the useBudgetSettings isn't much different in the mechanics.
const useBudgetCost = ({ key, notifyOnChange }) => {
const [loaded, setIsLoaded] = useState(false)
const { budgetCost, setBudgetCost } = useContext(ProjectContext)
useEffect(() => {
if(key)
return getBudgetCost(key);
},[key]);
const getBudgetCost = (key) => {
let { budgetCosts, loaded } = UseBudgetCostsQuery(key);
setBudgetCost(budgetCosts);
setIsLoaded(loaded);
}
let onBudgetCostChange = (update) => {
let tempBudgetCostItems = copyArrayReference(budgetCost);
tempBudgetCostItems = {
...tempBudgetCostItems,
...update
}
setBudgetCost(tempBudgetCostItems)
if (notifyOnChange)
notifyOnChange(update)
}
return {
loaded,
budgetCost,
onBudgetCostChange
}
}
useBudgetSettings component:
const useBudgetSetting = ({ key }) => {
const [loaded, setIsLoaded] = useState(false)
const { budgetSettings, setBudgetSettings } = useContext(ProjectCondext)
const globalContext = useContext(GlobalReferenceContext);
useEffect(() => {
if(key)
return getBudgetSetting(key);
},[key]);
const getBudgetSetting = (key) => {
let { budgetSettings, loaded } = UseBudgetSettingsQuery(key);
console.log(budgetSettings);
setBudgetSettings(budgetSettings);
setIsLoaded(loaded);
}
const getBudgetReferences = (overrideWithGlobal = false) => {
if(overrideWithGlobal)
return globalContext.getBudgetReferences();
return budgetSettings.map((item) => { return { value: item.key, label: item.costCode } });
}
const getCategoryText = (key) => _.get(_.find(getBudgetReferences(), (bc) => bc.value === key), 'label');
return {
loaded,
budgetSettings,
getCategoryText,
getBudgetReferences
}
}

Search functionality in reduxReact-native

I am trying to achieve search functionality in redux
I am using the following code but its not working but the same code is working in plain javascript but not with redux
searchItems: (name ) => async (dispatch) => {
let newData = []
dispatch({
type: types.SEARCH_ITEMS,
payload: (newData = DATA.filter((item) => {
const itemData = `${item.name.toUpperCase()}`;
const textData = name.toUpperCase();
itemData.indexOf(textData) > -1;
return newData;
})),
});
},
};
the newData is returning and empty array
export const reducer = (state = initialState, action) => {
const { type } = action;
switch (type) {
case types.SEARCH_ITEMS:
return [...action.payload];
default:
return state;
}
};
What am i dong wrong?
You should return from the filter callback true or false:
Try to do it this way:
searchItems: (name ) => async (dispatch) => {
dispatch({
type: types.SEARCH_ITEMS,
payload: DATA.filter((item) => {
const itemData = `${item.name.toUpperCase()}`;
const textData = name.toUpperCase();
return itemData.indexOf(textData) > -1;
}),
});
},
};

react redux not updating view component

my view is now updating when redux state is changed, but if i click button "toggleToDoReducer" the view is updating to the new state.
here's my code
const toggleToDoReducer = (state, payload) => {
return ({
...state,
items: state.items.map((item, index) => {
if (index === payload) {
return { text: item.text, isChecked: !item.isChecked }
}
return item
})
})
}
shuffleMe = (oldData) => {
let i, j, temp;
for (i = oldData.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = oldData[i];
oldData[i] = oldData[j];
oldData[j] = temp;
}
let newArray = oldData;
return (newArray)
}
const shuffleReducer = (state) => {
return ({
...state,
items: shuffleMe(state.items)
})
}
any help would be great appreciated
I think shuffleMe function is mutating the state directly. Rather pass the copy of the state. An sample implementation below
const toggleToDoReducer = (state, payload) => {
return {
...state,
items: state.items.map((item, index) => {
if (index === payload) {
return { text: item.text, isChecked: !item.isChecked };
}
return item;
})
};
};
const shuffleMe = oldData => {
oldData.forEach((element, i) => {
const j = Math.floor(Math.random() * (i + 1));
[oldData[i], oldData[j]] = [oldData[j], oldData[i]];
});
return oldData;
};
const shuffleReducer = state => {
return {
...state,
items: shuffleMe([...state.items])
};
};
Try with setState({}) function for re-render your view.

Categories

Resources