How to use react.usememo using react and javascript? - javascript

i am doing some filtering on items based on ids which is in selection object. and then evaluating true or not based on its completion true or false from items which is an array of objects.
below is my code,
const Items: React.FC<RouteComponentProps> = ({history}) => {
const [{selection}] = tableState;
const {
data: items,
count: itemsCount,
} = React.useMemo(() => {
(!isNil(data) ? data.items : { data: [], count: 0}),
[data]
);
let noJobs;
if (selection) { //how to put this block in useMemo
const filteredItems = items.filter(item =>
Object.keys(selection).includes(item.id)
);
noJobs = filteredItems.map(item => item.jobs.map(job => job.completed))
.flat().every(Boolean);
}
return (
<button disabled = {noJobs}> Click me </button>
);
}
How can i put the block which includes noJobs calcualtion (from if(selection)) to React.useMemo. could someone help me with this. I am learning react and hooks is new to me.
thanks.

Try followings it:
const Items: React.FC<RouteComponentProps> = ({history}) => {
const [{selection}] = tableState;
const {
data: items,
count: itemsCount,
} = React.useMemo(() => {
(!isNil(data) ? data.items : { data: [], count: 0}),
[data]
);
const noJobs = React.useMemo(() => {
if (selection) {
const filteredItems = items.filter(item => {
return Object.keys(selection).includes(item.id)
});
return filteredItems.map(item => item.jobs.map(job => {
return job.completed
}))
.flat()
.every(Boolean);
};
return undefined; //or everything you want
} , [items , selection]);
return (
<button disabled = {noJobs}> Click me </button>
);
}

Related

I Need to fix a issue in antd tree component

what my issue is
Scenario 1: It is not opening sub-lists after searching or removing any list name from the search bar
Scenario 2: After searching any list name in the search bar that is already selected, then after searching that selected list it is showing that list but its checkbox is not selected.
so what do I need after searching list name in the search bar if that list has a sub-list for example if I New Watchlists then I want to show that sub-list also that present under this list after searching in the search bar but right it coming with empty list you can see image below..
const hasSearchTerm = (n, searchTerm) =>
n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm) =>
arr?.filter(
(n) =>
hasSearchTerm(n.title, searchTerm) ||
filterData(n.children, searchTerm)?.length > 0
);
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: filterData(n.children, searchString, checkedKeys)
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
const Demo = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [checkedKeys, setCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [tree, setTree] = useState(treeData);
const onExpand = (expandedKeysValue) => {
console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = React.useCallback(
(checkedKeysValue, e) => {
if (e.checked) {
if (e.node?.children?.length) {
setCheckedKeys(
_.union(
checkedKeys,
_.cloneDeep([
...e.node.key,
...e.node.children.map((child) => child.key)
])
)
);
} else {
setCheckedKeys(_.union(checkedKeys, [e.node.key]));
}
} else {
if (e.node?.children?.length) {
setCheckedKeys(
_.union(
checkedKeys.filter((item) => {
return (
item !== e.node.key &&
!e.node.children.filter((child) => child.key === item).length
);
})
)
);
} else {
setCheckedKeys(
_.cloneDeep(checkedKeys.filter((item) => item !== e.node.key))
);
}
}
},
[checkedKeys, setCheckedKeys]
);
const onSelect = (selectedKeysValue, info) => {
console.log("onSelect", info);
setSelectedKeys(selectedKeysValue);
};
React.useEffect(() => {
const checked = [];
treeData.forEach((data) => {
data.children.forEach((item) => {
if (item.checked) {
checked.push(item.key);
}
});
});
setCheckedKeys(checked);
}, []);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
React.useEffect(() => {
if (searchValue) {
const filteredData = filteredTreeData(
treeData,
searchValue,
checkedKeys,
setExpandedKeys
);
setTree([...filteredData]);
} else {
setTree(treeData);
// setExpandedKeys([]);
}
}, [searchValue, checkedKeys]);
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={tree}
/>
</div>
);
};
CodeSandBox Link
When you run the filterTreeData function, you assign the children to only the tree that contains the search term. So, you're searching n levels deep for the term. Notice that it works like I'd expect it to if you only search one level deep. That is, if the node has children, you return the node as such rather than filtering down all the children that don't also contain the term. when you filter with the string "new" in your example, you'll notice that none of the children in the collection "New Watchlists" contain the term and they are therefore filtered out leaving you with an empty array. My naive solution is to just return the children as in my following example:
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: n.children
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
I call this solution naive, because I'm not exactly sure what your use case is. There might be more conditional logic you need to render in there for first searching n levels deep for the term and then and returning the whole node if the term is discovered anywhere inside the node or children.

Push Array React Native Keep Replace And Not Append

What is the correct way to use push array in React native ?
const BuilderIndicatorCard = (props) => {
const [isChecked, setIsChecked] = useState(false);
const [checkedValue, setCheckedValue] = useState([]);
let storeCheckedValue = [];
useEffect(() => {
if (isChecked) {
storeCheckedValue.push(props.indicator);
}
console.log(storeCheckedValue);
}, [isChecked, checkedValue])
// const removeCheckedStrategy = (checkedValue, array) => {
// var copyArray = [...array];
// var index = copyArray.indexOf(checkedValue);
// if (index !== -1) {
// copyArray.splice(index, 1);
// setArray(copyArray);
// }
// }
return (
<CheckBox
containerStyle={styles.checkbox}
size={15}
textStyle={styles.name}
title={props.indicator}
checked={isChecked}
onPress={() => {setIsChecked(!isChecked)}}
/>
);
};
When I do storeCheckedValue.push(props.indicator); why the array keep replace and not append ?
This is show in the console :
Array [
"Price Below MA (5)",
]
Array [
"Price Below MA (7)",
]
Array [
"Price Below MA (9)",
]
did I miss something in here ?
I found a proper way to solve my question above.
This is how I did it.
I use redux-toolkit
Create a slice like :
import { createSlice } from '#reduxjs/toolkit'
const addChecked = (state, action) => {
state.indicator = [...state.indicator, action.payload];
}
const removeChecked = (state, action) => {
state.indicator = state.indicator.filter(data => data != action.payload);
}
// status: 'idle' | 'loading' | 'succeeded' | 'failed',
export const BuilderSlice = createSlice({
name: 'Builder',
initialState: {
indicator: [],
status: 'idle',
error: null
},
reducers: {
addCheckedIndicator: addChecked,
removeCheckedIndicator: removeChecked
},
extraReducers: {
}
});
export const { addCheckedIndicator, removeCheckedIndicator } = BuilderSlice.actions
Then In the card component I change to like this :
import { addCheckedIndicator, removeCheckedIndicator } from '../redux/slice/builder/BuilderSlice';
const BuilderIndicatorCard = (props) => {
const dispatch = useDispatch();
const data = useSelector(state => state.Builder.indicator);
const [isChecked, setIsChecked] = useState(true);
const addValue = useCallback(() => {
setIsChecked(!isChecked);
if (isChecked) {
dispatch(addCheckedIndicator(props.indicator));
} else {
dispatch(removeCheckedIndicator(props.indicator));
}
}, [isChecked]);
return (
<CheckBox
containerStyle={styles.checkbox}
size={15}
textStyle={styles.name}
title={props.indicator}
checked={props.checked}
onPress={() => {addValue()}}
/>
);
};
I dispatch the function in slice in here:
dispatch(addCheckedIndicator(props.indicator));
dispatch(removeCheckedIndicator(props.indicator));
And then make sure use :
const addValue = useCallback(() => {
},[second]);
Not useEffect.

React | Update State | using spread and array.push to update array

I have jsfiddle Link https://jsfiddle.net/ilikeflex/r2uws1ez/30/
I am trying to set new state by accessing the previous state. I have three different cases where i am trying to update the state but i am not sure why does Case2 and Case 3 does not work. Can you please help me ??
const useState = React.useState;
function Example() {
const [todos, setTodos] = useState([
{id:1,text:'Learn React'},
{id:2,text:'Learn TypeScript'},
{id:3,text:'Learn Java'}
]);
const case1 = () => {
const newItem = {id:3,text:'Learn C++'};
setTodos((prevState) => {
return prevState.concat(newItem);
});
}
const case2 = () => {
const newItem = {id:3,text:'Learn .Net'};
setTodos((prevState) => {
prevState.push(newItem); // I am adding to previous state and returning it
return prevState;
});
}
const case3 = () => {
const newItem = {id:3,text:'Learn C#'};
setTodos((prevState) => {
let result = { ...prevState, newItem }; // using spread operator
return result;
});
}
return (
<div>
<button onClick={case1}>Case 1</button>
<button onClick={case2}>Case 2</button>
<button onClick={case3}>Case 3</button>
<ul>
{todos.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'))
To make case 2 work, you need to make a variable out of the prevState first. Then you can push to it and return that new variable.
const case2 = () => {
const newItem = {id:3,text:'Learn .Net'};
setTodos((prevState) => {
const newState = [...prevState]
newState.push(newItem);
return newState;
});
}
For case 3, you made a little mistake by making result an object instead of an array. Change the {} to [] and it should work fine, like this.
const case3 = () => {
const newItem = {id:3,text:'Learn C#'};
setTodos((prevState) => {
let result = [...prevState, newItem ] ;
return result;
});
}
In all the cases, a new array need be to created and returned.
Case 1.
return prevState.concat(newItem); [ concat create a new array ]
Case 2
const newState = [...prevState]; [ Spread Operator creates a new array ]
newState.push(newItem);
return newState;
Case 3
let result = [...prevState, newItem ] ; [ Spread Operator creates a new array and newItem is added simultaneously ]
return result;

Redux - How to calculate the cost of a list of carts from a normalised state

am fairly new to React/Redux. I have a normalised redux state that looks like so:
users: {
user1: {
id: 'user1',
name: 'Jim',
carts: ['cart1', 'cart2']
}
},
carts: {
cart1: {
id: 'cart1',
items: ['item1', 'item2']
},
cart2: {
id: 'cart2',
items: ['item3', 'item4']
}
},
items: {
item1: {
id: 'item1',
price: 1
},
item2: {
id: 'item2',
price: 2
},
item3: {
id: 'item3',
price: 3
},
item4: {
id: 'item4',
price: 4
}
}
I have also created the following selectors:
item.selector.js:
export const selectAllItems = state => state.items
export const selectItems = itemIds => createSelector(
[selectAllItems],
items => Object.keys(items)
.filter(itemId => itemIds.includes(itemId))
.reduce((arr, itemId) => {
arr.push(items[itemId])
return arr
}, [])
)
export const totalItemsCost = itemIds => createSelector(
[selectAllItems],
items => Object.keys(items)
.filter(itemId => itemIds.includes(itemId))
.reduce((cost, itemId) => {
cost += blocs[itemId].price
return cost
}, 0)
)
cart.selector.js:
export const selectAllCarts = state => state.carts
export const selectCart = cartId => createSelector(
[selectAllCarts],
carts => carts[cartId]
)
export const selectCarts = cartIds => createSelector(
[selectAllCarts],
options => Object.keys(carts)
.filter(key => cartIds.includes(key))
.reduce((arr, key) => {
arr.push(options[key])
return arr
}, [])
)
user.selector.js:
export const selectUsers = state => state.users
export const selectUser = userId => createSelector(
[selectUsers],
users => users[userId]
)
I wish to display the total cost of each cart on the user page and have the following React component:
class User extends React.Component {
render () {
return (
{ this.props.carts.map(cart => (<DisplayComponent id={ cart.id } cost={ $TOTAL_CART_COST }/>))}
)
}
}
const mapStateToProps = (state, ownProps) => ({
user: selectUser(ownProps.match.params.userId)(state),
carts: selectCarts(selectUser(ownProps.match.params.userId)(state).carts)(state)
})
export default connect(mapStateToProps, null)(User)
Im unsure on the best way to achieve this. I have tried creating a cartCost field which is updated by the reducer, like this:
case CartActionTypes.ADD_ITEM_SUCCESS:
return {
...state,
[action.payload.cartId]: {
...state[action.payload.cartId],
cartCost: totalItemsCost(addItemToCart(state[action.payload.cartId].items, action.payload.itemId))(state),
items: addItemToCart(state[action.payload.cartId].items, action.payload.itemId)
}
}
which uses:
export const addItemToCart = (items, newItem) => {
const existingItem = items.find(item => item === newItem)
if (existingItem) {
return items
} else {
return [...items, newItem]
}
}
But this gives me:
Uncaught (in promise) TypeError: Cannot convert undefined or null to object
at Function.keys (<anonymous>)
at item.selectors.js:18
at index.js:70
at index.js:30
at index.js:84
at index.js:30
at cartReducer (cart.reducer.js:35)
at combination (redux.js:459)
at persistReducer.js:147
at dispatch (redux.js:213)
at redux-logger.js:1
at index.js:11
at dispatch (redux.js:638)
at item.actions.js:30
All suggestions welcome, perhaps i'm not taking the correct approach creating the new field in the reducer? Thanks
This is what i ended up with:
cart.selector.js:
export const selectAllCarts = state => state.carts
export const selectCart = cartId => createSelector(
[selectAllCarts, selectAllItems],
(carts, items) => generateCart(carts[cartId], items)
)
export const generateCart = (cart, allItems) => {
return {
...cart,
cartCost: calculateCartCost(allItems, cart.items)
}
}
export const calculateCartCost = (allItems, cartItemIds) => {
return Object.keys(allItems)
.filter(itemId => cartItemIds.includes(itemId))
.reduce((cost, itemId) => {
cost += allItems[itemId].price
return cost
}, 0)
}
export const selectCarts = cartIds => createSelector(
[selectAllCarts, selectAllItems],
(carts, items) => Object.keys(carts)
.filter(key => cartIds.includes(key))
.reduce((arr, key) => {
arr.push(generateCart(carts[key], items))
return arr
}, [])
)
That now gives me a list of carts that all have the cart cost field i can use to display. Pretty sure its not optimal but best i have :)

Redux Reselect not updating when I compose?

I have a Redux Reselect selector that works fine:
// in reselect1.js
const getInputs = state => state.inputs;
export default createSelector(
getInputs,
inputs => {
const { thing } = inputs;
const items = inputs.items
.map(item => item.text)
.filter(item => item);
return {
thing,
items
};
},
);
In another selector it works if I repeat the logic to get the inputs without IDs in inputsWithoutIds:
// in reselect2.js
const inputsWithoutIds = state => {
const { thing } = state.inputs;
const items = state.inputs.items
.map(item => item.text)
.filter(item => item);
return {
thing,
items
};
};
const getSaved = state => state.saved;
export default createSelector(
getSaved,
inputsWithoutIds,
(saved, inputs) => {
const isSaved = saved.filter(saved => {
return _isEqual(saved, inputs);
}).length;
return {
isSaved,
};
},
);
However if I try to reuse my first selector then when state.inputs.items changes the new value is not picked up. So the value of isSaved doesn't change when it should.
// in reselect2.js
import inputsWithoutIds from './reselect1'
const getSaved = state => state.saved;
export default createSelector(
getSaved,
inputsWithoutIds,
(saved, inputs) => {
const isSaved = saved.filter(saved => {
return _isEqual(saved, inputs);
}).length;
return {
isSaved,
};
},
);
I can see in the docs that these functions are composable so Im not sure what I'm doing wrong? https://github.com/reduxjs/reselect

Categories

Resources