how to create custom selector with local state - reactjs

I need to create a custom selector that accepts redux state and local state to produce the result. But I don't know how to.
app.tsx
const initialConfig = {
storeType: {
[StoreType.CHAIN_STORE]: false,
[StoreType.PROSPECT]: false,
[StoreType.UNKNOWN]: false,
[StoreType.PRODUCT]: false
},
photo: {
binding: false
},
store: {
noUnit: false,
checkFlag: false,
newRepo: false,
notAssociated: false,
deletedAssociation: false
},
keyword: ''
};
//it's ui state (user's custom filter)
const copy: <T extends object>(source: T) => T = source =>
JSON.parse(JSON.stringify(source));
const [config, setConfig] = useState<Config>(copy(initialConfig));
const photos = copy(useSelector(photoWithUnitSelector).reduce(
filterFun(config),
[]
);
//each time render need to do a deep copy to ensure useSelector is not memorized.But it's a really perform issue.
photoWithUnitSelector = createSelector([A,B,C],(a,b,c)=>[...result])
//It is my selector that all data is from redux state.
filterFun = (config) => (prev,curr) => {
return my_own_fun_to_get_filtered_photos(config, prev, curr)
}
All I want is useSelector(selector(config),shallowEqual)
(combine redux state and local state to get my result)
But I don't know how to write the selector function.
Can someone give me a guide?

Related

Replacing my layout React Context with a global Zustand store

I have a context for the layout of my React app that utilizes a hook for finding out the current window size:
export const LayoutContext = createContext({
menuIsOpen: false,
setMenuIsOpen: (isOpen: boolean) => {},
overlayIsOpen: false,
setOverlayIsOpen: (isOpen: boolean) => {},
isMobile: false,
isTablet: false,
isLaptop: false,
isDesktop: false,
});
export default function LayoutProvider({
children,
}: {
children: React.ReactNode;
}) {
const context = useLayoutContext();
return (
<LayoutContext.Provider value={context}>{children}</LayoutContext.Provider>
);
}
function useLayoutContext() {
const windowSize = useWindowSize();
const isMobile = windowSize.width <= Breakpoint.MEDIUM; // <= 768
...
...
// Initial load of page layout. If <=1024px neither chat or menu is open by default.
useEffect(() => {
const isMobileDevice = window.innerWidth <= Breakpoint.MEDIUM;
const isLargeDevice = window.innerWidth >= Breakpoint.LARGE;
const isDesktopDevice = window.innerWidth >= Breakpoint.EXTRA_LARGE;
if (isMobileDevice) {
setShowMobileNavigation(true);
}
if (isDesktopDevice) {
setMenuIsOpen(true);
}
}, []);
There's a bit more code but these are the important parts. I'm looking to replace this with a global Zustand store, but I'm having trouble understand exactly how to do it. Where would I use the useWindowSize hook? And where would I use the initial useEffect to decide the layout, if I'm to move away from this context provider that wraps the layout?
I know this isn't specifically a Zustand question but I figured the logic is roughly the same whether it be moving from Context to Redux, jotai, recoil...
Appreciate any and all help
In my case, Zustand store does not need to wrap the component and can use anywhere so im using a hook similar to useWindowSize and useEffect to detect device type in my App.tsx.

Change Boolean value based on the previous state value

I have created the toggle function where it will change the Boolean value to false. And I am passing that handler function to button, now I am trying to achieve the same by using previous value, the problem I am facing here is I am having a mock data which will have the following structure {[{}]} inside one object I'll have an array inside that I'll have another objects. I have posted the mock and older implementation by selecting only one value from the mock, could any one guide me how to change the boolean value for the mock which I have. Thanks in advance.
const custDetail = {
customers: [
{
name: "Abc",
isCreated: true,
},
{
name: "bcd",
isCreated: true,
},
{
name: "Dec",
isCreated: true,
},
],
};
Code:
const [creatingCust, setCreatingCust] = useState([custDetail])
const custData = [...creatingCust]
custData[0].customers[0].isCreated = false
setCreatingCust(custData)
//trying to use prevState but I am getting undefined
const onClick = () => {
setCreatingCust(prevState => ({isCreated:!prevState.customers[0].isCreated}))
Shallow copy the state, and all nested state, that is being updated. I suggest using the customer name property to match the customer element in the customers array that you want to update. Use Array.prototype.map to create a new array reference.
I suggest also just storing custDetail in the creatingCust state. I don't a reason to nest it in an array.
Example:
const [creatingCust, setCreatingCust] = useState(custDetail);
const onClick = (name) => {
setCreatingCust(prevState => ({
...prevState,
customers: prevState.customers.map(
customer => customer.name === name
? {
...customer,
isCreated: !customers.isCreated
}
: customer
),
}));
};
If you must have creatingCust be an array the process is similar, but instead of shallow copying into a new object you shallow copy into a new array.
const onClick = (name) => {
setCreatingCust(prevState => [{
...prevState[0],
customers: prevState[0].customers.map(
customer => customer.name === name
? {
...customer,
isCreated: !customers.isCreated
}
: customer
),
}]);
};

Update deeply nested state object in redux without spread operator

I've been breaking my head for a week or something with this !!
My redux state looks similar to this
{
data: {
chunk_1: {
deep: {
message: "Hi"
}
},
chunk_2: {
something: {
something_else: {...}
}
},
... + more
},
meta: {
session: {...},
loading: true (or false)
}
}
I have an array of keys like ["path", "to", "node"] and some data which the last node of my deeply nested state object should be replaced with, in my action.payload.
Clearly I can't use spread operator as shown in the docs (coz, my keys array is dynamic and can change both in values and in length).
I already tried using Immutable.js but in vain.. Here's my code
// Importing modules ------
import {fromJS} from "immutable";
// Initializing State ---------
const InitialState = fromJS({ // Immutable.Map() doesn't work either
data: { ... },
meta: {
session: {
user: {},
},
loading: false,
error: "",
},
});
// Redux Root Reducer ------------
function StoreReducer(state = InitialState, action) {
switch (action.type) {
case START_LOADING:
return state.setIn(["meta"], (x) => {
return { ...x, loading: true };
});
case ADD_DATA: {
const keys = action.payload.keys; // This is a valid array of keys
return state.updateIn(keys, () => action.payload); // setIn doesn't work either
}
}
Error I get..
Uncaught TypeError: state.setIn (or state.updateIn) is not a function
at StoreReducer (reducers.js:33:1)
at k (<anonymous>:2235:16)
at D (<anonymous>:2251:13)
at <anonymous>:2464:20
at Object.dispatch (redux.js:288:1)
at e (<anonymous>:2494:20)
at serializableStateInvariantMiddleware.ts:172:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:258:1)
at Object.dispatch (<anonymous>:3665:80)
What I want ?
The correct way to update my redux state (deeply nested object) with a array containing the keys.
Please note that you are using an incredibly outdated style of Redux. We are not recommending hand-written switch..case reducers or the immutable library since 2019. Instead, you should be using the official Redux Toolkit with createSlice, which allows you to just write mutating logic in your case reducers (and thus also just using any helper library if you want to use one).
Please read Why Redux Toolkit is how to use Redux today.
you could use something like that:
import { merge, set } from 'lodash';
export default createReducer(initialState, {
...
[updateSettingsByPath]: (state, action) => {
const {
payload: { path, value },
} = action;
const newState = merge({}, state);
set(newState, path, value);
return newState; },
...}

ReactJs useState set array of objects

Im using react functional component with useState.I am trying to update an Array in an Object but its not updating.below is my state
const [prodImg, setProdImg] = useState({
show : false,
url : '',
title : '',
imgList : []
})
I want to update imgList here by a function which receives an array of objects like below.
const setImage = ({ fileList }) => {
setProdImg(
{
...prodImg,
imgList: fileList
},
console.log(prodImg.imgList)
)
}
here fileList is a destructured Array of object.But prodImg.imgList is not updating
fileList contains structure like this :
[
{
name : 'asc.jpg',
},
{
name : 'aqwe.jpg',
}
]
React may update state asynchronously. In your code you just write to console imgList of the current state, which you hold in the stack, but state is actually updated after you write it to the console. Try logging the state in the effect hook instead:
const [prodImg, setProdImg] = useState({
show : false,
url : '',
title : '',
imgList : []
})
// this callback will be invoked on the component update, eg when state is changed.
useEffect(() => {
console.log(prodImg.imgList)
});

Passing props from Container to mapDispatchToProps

I'm trying to access a Container's props (that were passed in from Redux state) from within mapDispatchToProps (mDTP). My container is responsible for dispatching an action to fetch data, but the action that it dispatches needs access to a piece of redux state. That state will be used as a field in the HTTP header.
The redux state that I am trying to access is languages. I want to then pass it in as a header to mDTP
Here is my container:
const ExperienceListContainer = ({ list, loading, failed, readList, languages }) => {
useEffect(() => {
readList()
}, [])
const apiRequestParams = {
endpoint: 'GetToursByCity',
count: 10,
step: 10,
cityid: 'e2e12bfe-15ae-4382-87f4-88f2b8afd27a',
startdate: '2018-12-20 8:30:00',
enddate: '2018-12-20 22:15:00',
sort_by: 'rating',
guests: 10,
min_price: 1,
max_price: 100.50,
time_of_day: 1,
sort_by: 'rating',
categories: '',
languages: languages
}
return <ExperienceList {...{ list, loading, failed, languages }} />
}
ExperienceListContainer.propTypes = {
list: T.arrayOf(T.object).isRequired,
limit: T.number,
loading: T.bool,
failed: T.bool,
readList: T.func.isRequired,
filter: T.shape({
interests: T.arrayOf(T.shape({
id: T.number.isRequired,
text: T.string.isRequired
})),
languages: T.arrayOf(T.shape({
id: T.number.isRequired,
text: T.string.isRequired
})),
}),
}
const mapStateToProps = state => {
return (
({
list: state.resource.experiences,
languages: state.filter.languages
})
)
}
const mapDispatchToProps = (dispatch) => ({
readList: () => {
return (
dispatch(resourceListReadRequest('experiences', { apiRequestParams }))
)
}
})
export default connect(mapStateToProps, mapDispatchToProps)(ExperienceListContainer)
The problem with this current code is that mDTP cannot access apiRequestParams. It is undefined as far as it is concerned.
Based on my research, the answers that pop up are to use mergeProps (as an arg passed into connect()) and ownProps (a param included with mapDispatchToProps), but neither of these seem to be working, at least in the way I'm implementing them. Is there a better way to accomplish what I'm trying to do?
You have to pass them to readList as a parameter to access them like this:
readList(apiParams)
And use them like this:
readList: apiRequestParams => {
return (
dispatch(resourceListReadRequest('experiences', {
apiRequestParams }))
)
This is because apiRequestParams is local to the container itself and cannot be access outside of the component.
You would also have to declare it above the useEffect and pass it as parameter to the useEffect so that it gets evaluates, if the language changes for example and keep the reference with useRef or declare it within the useEffect so that you don't cause an infinity loop.
Hope that helps. Happy coding.

Resources