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
Related
I have multiple useForm hooks in my form component. I want to create a method to dynamically handle one submit button for an arbitrary number of hooks. The idea is that the partial forms should be validated successively.
For an isolated scenario, this approach works:
import React from 'react'
import { useForm } from 'react-hook-form'
const MyFormComponent = () => {
const form1 = useForm();
const form2 = useForm();
const onSubmitSuccess = () => {
//some success logic
};
const handleMultiple = form1.handleSubmit(form2.handleSubmit(onSubmitSuccess));
return <React.Fragment>
{
...form1Fields
}
{
...form2Fields
}
<button onClick={handleMultiple}>submit</button>
</React.Fragment>
}
Now I want to create a generic 'handleMultiple'. I tried to utilize the redux 'compose' function, but that approach just ignores the validation:
import { compose } from 'redux'
const getHandleMultiple = (submitFunc, ...forms) => {
const handleMultiple = compose(
...forms.map(form => form.handleSubmit),
submitFunc
);
return {
handleMultiple
}
}
Any ideas how to archive this? Different approaches are also appreciatied.
EDIT: I tried to wrap the compose-method in a 'useMemo' hook, as #AhmedI.Elsayed suggested, but that didn't solve the problem:
const useComposedFormSubmit = (submitFunc, ...forms) => {
const handleComposedSubmit = useMemo(() => {
return compose(
...forms.map(form => form.handleSubmit),
submitFunc
)
}, [forms, submitFunc])
return {
handleComposedSubmit
}
}
EDIT 2: Code sandbox:
https://codesandbox.io/s/react-hook-form-compose-handlesubmit-yes5y9
Edit 1: So handleSubmit gets called on your onSubmit and this gets repeated for every handleSubmit we have, this is basically as if we express it mathematically as:
handleSubmit2(handleSubmit1(onSubmit))(x)
but compose?
compose(...forms.map((form) => form.handleSubmit), submitFunc);
This is
handler2(handler1(submitFunc(x)))
and if we composed in opposite order
submitFunc(handler1(handler2(x)))
which is still not what we need. What we need to do is ignore compose because that's not what we need, and implement our own function.
const useComposedFormSubmit = (submitFunc, ...forms) => {
return useMemo(() => {
const fns = [...forms.map(f => f.handleSubmit)];
return fns.reduce((p, c) => {
return c(p);
}, submitFunc);
}, [submitFunc, forms]);
};
No reduce version
const useComposedFormSubmit = (submitFunc, ...forms) => {
return useMemo(() => {
const fns = [...forms.map(f => f.handleSubmit)];
let resultFunc = submitFunc;
for (const fn of fns) {
resultFunc = fn(resultFunc);
}
return resultFunc;
}, [submitFunc, forms]);
};
OLD ANSWER, NOT WORKING BUT EXPLAINS HOW TO EXPRESS THE OP POST IN compose
I think the problem is due to forms being undefined at some point, not sure but you can test this one and I'm pretty sure it should work, this is a hook. Hooks should start with use as documented.
const useMultipleSubmitters = (submitFunc, ...forms) => {
return useMemo(() => {
return compose(...forms.map(f => form.handleSubmit), submitFunc)
}, [forms, submitFunc])
}
and adjust as needed. It's the same as yours but recalculates on rerenders.
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.
I know this question has been asked before, but the solutions were different from the way I structure/code my React app as shown below. How use one update/render based on the updated listData?
import React, { useState } from "react";
const PageName = () => {
const [listData, setlistData] = useState([]);
function add(){
let newrow = {};
listData.push(newrow);
setlistData(listData);
}
return (
<div>
{listData.length}
<button onClick={() => add()}>Add</button>
{
listData.map(function (row, i) {
return (
<p key={i}>row</p>
);
})
}
</div>
);
};
export default PageName;
If it is a state variable it is react's job to rerender. Your job is to ensure you are passing in a new reference to setState() without mutating the state.
Use ... spread operator.
import React, { useState } from "react";
const PageName = () => {
const [listData, setlistData] = useState([]);
function add(){
let newrow = {};
let newListData = [...listData];
newListData.push(newrow);
setlistData(newListData);
}
return (
<div>
{listData.length}
<button onClick={() => add()}>Add</button>
{
listData.map(function (row, i) {
return (
<p key={i}>row</p>
);
})
}
</div>
);
};
export default PageName;
The reference stays the same if you only push to the same object .
If you push to the list , under the hood the list gets the data but React cannot rerender because the object listData does not save value but reference , which doesn't change . Creating a new object by copying all the data and putting it in a new object changes the reference of newreferenceListData ,which causes re render.
function add(){
let newrow = {};
let newreferenceListData = [...listData,newrow];
setlistData(newreferenceListData);
}
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 -->
};
Playing with React those days. I know that calling setState in async. But setting an initial value like that :
const [data, setData] = useState(mapData(props.data))
should'nt it be updated directly ?
Bellow a codesandbox to illustrate my current issue and here the code :
import React, { useState } from "react";
const data = [{ id: "LION", label: "Lion" }, { id: "MOUSE", label: "Mouse" }];
const mapData = updatedData => {
const mappedData = {};
updatedData.forEach(element => (mappedData[element.id] = element));
return mappedData;
};
const ChildComponent = ({ dataProp }) => {
const [mappedData, setMappedData] = useState(mapData(dataProp));
console.log("** Render Child Component **");
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
export default function App() {
const [loadedData, setLoadedData] = useState(data);
const [filter, setFilter] = useState("");
const filterData = () => {
return loadedData.filter(element =>
filter ? element.id === filter : true
);
};
//loaded comes from a useEffect http call but for easier understanding I removed it
return (
<div className="App">
<button onClick={() => setFilter("LION")}>change filter state</button>
<ChildComponent dataProp={filterData()} />
</div>
);
}
So in my understanding, when I click on the button I call setFilter so App should rerender and so ChildComponent with the new filtered data.
I could see it is re-rendering and mapData(updatedData) returns the correct filtered data BUT ChildComponent keeps the old state data.
Why is that ? Also for some reason it's rerendering two times ?
I know that I could make use of useEffect(() => setMappedData(mapData(dataProp)), [dataProp]) but I would like to understand what's happening here.
EDIT: I simplified a lot the code, but mappedData in ChildComponent must be in the state because it is updated at some point by users actions in my real use case
https://codesandbox.io/s/beautiful-mestorf-kpe8c?file=/src/App.js
The useState hook gets its argument on the very first initialization. So when the function is called again, the hook yields always the original set.
By the way, you do not need a state there:
const ChildComponent = ({ dataProp }) => {
//const [mappedData, setMappedData] = useState(mapData(dataProp));
const mappedData = mapData(dataProp);
console.log("** Render Child Component **");
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
EDIT: this is a modified version in order to keep the useState you said to need. I don't like this code so much, though! :(
const ChildComponent = ({ dataProp }) => {
const [mappedData, setMappedData] = useState(mapData(dataProp));
let actualMappedData = mappedData;
useMemo(() => {
actualMappedData =mapData(dataProp);
},
[dataProp]
)
console.log("** Render Child Component **");
return Object.values(actualMappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
Your child component is storing the mappedData in state but it never get changed.
you could just use a regular variable instead of using state here:
const ChildComponent = ({ dataProp }) => {
const mappedData = mapData(dataProp);
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};