I have a React Native functional component.
I'm using useEffect to fetch some data from AsycStorage and set it to local state. However, before rendering, I want to do some processing/calculations on this data before I can render it on screen. Where should I be doing this calculation?
My screen looks as follows:
const BasicScreen = ({ data, getPosts }) => {
const [myItems, setItems] = useState([]);
const checkForItems = () => {
var storageItems = AsyncStorage.getItem("MyItems").then((item) => {
if (item) {
return JSON.parse(item);
}
});
setItems(storageItems);
};
useEffect(() => {
async function getItems() {
await checkForItems(); // calling function to get data from storage
}
getItems(); // Local Storage
getPosts(); // Store action
}, []);
return (
<View>
<>
<Text>{JSON.stringify(processedItemsA)}</Text>
<Text>{JSON.stringify(processedItemsB)}</Text>
</>
</View>
);
}
export default BasicScreen;
As you can see, I check for items in AsyncStorage and set that data to local state myItems.
I want to do some mathematical calculations and some conditional logic, for example, separate the data of myItems into two separate categories, and then render that on screen. Something like processedItemsA and processedItemsB. Where should I be doing this processing of data?
processedItemsA = myItems => {
// Some logic
}
processedItemsB = myItems => {
// Some logic
}
I'm not sure about where this logic should go.
Note that this processing is required because apart from storage, I also get some data from redux store, and then process it along with that data.
This way you can achieve this
const [processedItemsA, setProcessedItemsA] = useState({});
const [processedItemsB, setProcessedItemsB] = useState({});
doProcessedItemsA = myItems => {
...
setProcessedItemsA({...data}); // set data after process Item A
}
doProcessedItemsB = myItems => {
...
setProcessedItemsB({...data}); // set data after process Item B
}
const checkForItems = () => {
var storageItems = AsyncStorage.getItem("MyItems").then((item) => {
if (item) {
const parsedItem = JSON.parse(item);
doProcessedItemsA(parsedItem);
doProcessedItemsB(parsedItem);
}
});
// setItems(storageItems); <-- No need to set here -->
};
Related
I am using functional component and trying to achieve MVVM. I want to get updated value of redux state without using useSelector hook
Here is my code
model.js
export class Model {
getData = () => {
return store.getState().mReducer.jsonData
}
setData = (data) => {
store.dispatch(setData(data)) // storing in redux for further use
}
}
ViewModel.js
export class ViewModel {
onChangeTextHandler = (text) => {
this.model.setData(tmp)
}
}
View.js
export const View = () => {
const vm = useMemo(() => new ENReceivingViewModel(), [])
const model = useMemo(() => new ENREceivingModel(), []);
//IF I use this line then rerender happens otherwise nothing happens
//const { selectedOption, jsonData } = useSelector(state => state.ReceivingReducer)
return (
<TextInput value = {model.getData()[0]}
onChangeText={vm.onChangeTextHandler} />
)}
I don't think that would be possible to handle it in this way, the model object keeps the only value of the store that was in the initialization.
I think passing store to method of class will do what you want:
like this:
export class Model {
getData = (store) => {
return store.getState().mReducer.jsonData
}
setData = (data) => {
store.dispatch(setData(data)) // storing in redux for further use
}
}
and in component:
import store from "./store"
<TextInput value = {model.getData(store)[0]}
onChangeText={vm.onChangeTextHandler} />
or another way is to add dependency in useMemo
like this :
const model = useMemo(() => new ENREceivingModel(), [someState]);
in this way every time that someState changes, a new ENREceivingModel will be replaced the previous one
I want to update this "holidays" array, by calling function only when countryAttr state changes.
export default function Home() {
const [countryAttribute, setCountryAttribute] = React.useState(null)
const getHolidays = () => {
const CALENDAR_REGION = `en.${countryAttribute.attr}`;
const calendar_url = `${BASE_CALENDAR_URL}/${CALENDAR_REGION}%23${BASE_CALENDAR_ID_FOR_PUBLIC_HOLIDAY}/events?key=${mykey}`
let holidays = new Array()
return axios.get(calendar_url)
.then(res => {res.data.items.map(
val => {
holidays = [...holidays, {holidayName: val.summary}
]
})
})
}
return (...)
}
Unfortunately, now, that pre-render any time when this component is loaded, and I want to write this array somewhere until demand state will be changed.
Suppose I have a list of items I would like to render and select (like a Todo app).
I'd like to keep the selection logic inside custom react hook and have items live somewhere else in local state.
Now, I would like to update the selection list, kept in the custom hook, whenever I fetch some more items. For this task I am passing data as parameter to selection hook and I am using useEffect to update the selection:
import { useEffect, useState } from "react";
const itemsArrayToObject = (items) =>
Object.fromEntries(items.map((i) => [i.id, { ...i, selected: false }]));
export function useSelection({ data }) {
const [selection, setSelection] = useState(itemsArrayToObject(data));
useEffect(() => {
setSelection((selection) => {
return {
...itemsArrayToObject(data),
...selection
};
});
}, [data]);
const isSelected = (itemId) => selection?.[itemId]?.selected ?? false;
const toggle = (itemId) => {
setSelection((s) => {
const item = s[itemId];
return {
...s,
[itemId]: {
...item,
selected: !item.selected
}
};
});
};
return {
isSelected,
toggle
};
}
This almost works but the problem is if I want to synchronize two things: fetching data and toggling items. Eg.
const onLoadAndToggle = async () => {
await load();
toggle(0);
};
load is a async function that fetches the data. It also triggers state update so that data is updated and the selection can be updated inside useSelection hook.
Example how it all can work:
const [data, setData] = useState([]);
const addItems = (items) => {
setData((state) => [...state, ...items]);
};
const { load } = useFetch({ addItems });
const { isSelected, toggle } = useSelection({ data });
const onLoadAndToggle = async () => {
await load();
toggle(0);
};
Now, the problem is that when calling toggle(0) my custom hook has a stale selection, even when using setState(state => ... singature.
It is because the whole fetching and updating data in state takes too long.
I can see some ugly ways to solve that problem but I wonder what would be the elegant or idiomatic react way to solve that.
I have made a code sandbox, if it helps: https://codesandbox.io/s/selection-fetch-forked-nyl0kt?file=/src/App.js:376-512
Try clicking "Load and toggle first" first to see how the app crashed because the selection is not yet updated.
What you need is to initialize toogled items from the code itself. We can do this by providing the id's of the items that we want to toggle to the hook itself.
Updated hook -
const itemsArrayToObject = (items, itemsToggled) => {
if (Array.isArray(itemsToggled)) {
return Object.fromEntries(
items.map((i) => [i.id, { ...i, selected: itemsToggled.includes(i.id) }])
);
}
return Object.fromEntries(
items.map((i) => [i.id, { ...i, selected: false }])
);
};
export function useSelection({ data }, itemsToggled) {
const [selection, setSelection] = useState(
itemsArrayToObject(data, itemsToggled)
);
useEffect(() => {
setSelection((selection) => {
return {
...itemsArrayToObject(data, itemsToggled),
...selection
};
});
}, [data, itemsToggled]);
Now call to hook becomes -
const { isSelected, toggle } = useSelection({ data }, [0, 1]);
Updated codesandbox
This also decouples loading data & toggling of an item initially.
I have a component like this:
const [products, setProducts] = useState([]);
const [store, setStore] = useState([]);
const fetchProducts () => {
... fetch('product_url').then((products) => {
setProducts(products);
}).catch();
}
const fetchStore () => {
... fetch('store_url').then((store) => {
setStore(store);
}).catch();
}
useEffect(() => {
fetchProducts();
fetchStore();
}, []);
const handleLogicAfterProductsAndStoresLoadingSuccess = () => {
// products, store
//Some logic here
}
My question is where can I put the handleLogicAfterProductsAndStoresLoadingSuccess()?
Generally, just test for the values inside your render function, and put any processing after the request.
// Testing for length because you are initializing them to empty arrays. You should probably use null instead.
if (!(products.length && store.length)) {
return <LoadingComponent />
}
// Any other processing you need goes here
return <>
{/* Your result goes here */}
</>;
you can create another useEffect to track both changes of products & store:-
// handle what happen after products & store fetched
const handleLogicAfterProductsAndStoresLoadingSuccess = (products, store) => {
// if both successfully fetched data (not empty array)
if(products.length > 0 && store.length > 0) {
// do something
} else {
alert('Data not fetched correctly!')
}
}
// handle changes happen to products & store (note: this useEffect will be fired every time there are changes made to the products & store)
useEffect(() => {
handleLogicAfterProductsAndStoresLoadingSuccess(products, store)
}, [products, store])
I want to use UseState hook for updating data in my Table component. The data to be used in the Table component is fetched by another function which is imported paginationForDataAdded.
Its look like stackoverflow due to re-rendering.
setAllData(searchResults); will re-render the component and again make api call and repated.
right way to call API.
const [allData, setAllData] = useState([]);
useEffect(function () {
const {
searchResults,
furnishedData,
entitledData
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
setAllData(searchResults);
});
Assuming paginationForDataAdded is a function that returns a Promise which resolves with an object that looks like the following:
{
searchResults: { resultarray: [...] },
furnishedData: [...],
entitledData: [...]
}
You should do the following your in component:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
paginationForDataAdded({
searchFunction: search,
collectionsData: collections,
})
.then(
({ searchResults, furnishedData, entitledData }) => {
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData);
}
)
.catch(/* handle errors appropriately */);
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
However, if paginationForDataAdded is not an asynchronous call, then you should do the following:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
const {
searchResults,
furnishedData,
entitledData,
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData)
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
Hope this helps.