ReactJs useState set array of objects - reactjs

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)
});

Related

How to execute React custom hook only when data is available or Apollo client loading is false

I have custom React hook which adding some scripts and add a variable to window object:
const useMyHook = ({ id, type }: Props) => {
useScript('https:domain/main.js');
useScript('https://domain/main2.js');
useEffect(() => {
window.mydata = { id: `${id}`, type: `${type}` };
}, [id]);
};
I am using Apollo client and GraphQl for fetching data.
In my Page component, when I console.log(myData) the data returns undefined and then right after it returns the data (without refreshing). I am using Fragments.
From useQuery hook I can get the loading variable. How do I have to use loading and my custom hook so when loading === false -> use my custom hook.
I tried something like this:
const foo = useMyHook({ id: myData.id, type: myData.type });
Then below in the component in the return:
return (
{!loading && foo}
// Rest of the component jsx code
)
But still sometimes it returns first undefined?
How can I fix this issue?
# Update:
For now I added another prop loading: boolean and added this to the custom hook:
useEffect(() => {
if (!loading) {
window.mydata = { id: `${id}`, type: `${type}` };
}
}, [id]);
Is this correct approach. And why does my fragment query first returns undefined?
You can add another prop like enabled for handling this issue
sth like this:
useMyHook.tsx :
const useMyHook = ({ id, type,enabled }: Props) => {
useScript('https:domain/main.js');
useScript('https://domain/main2.js');
useEffect(() => {
if(enabled)
{
window.mydata = { id: `${id}`, type: `${type}` };
}
}, [enabled]);
};
other component:
const foo = useMyHook({ id: myData.id, type: myData.type,enabled:!loading && (data || error) });
return foo
and you need to use data and error that can be deconstructed from useQuery (like loading ) to just make sure if loading is false and data or error exists (that's because of first time that the component renders, loading is false but and data and error doesn't exists because request is not submitted yet, that's why you see undefined first )

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
),
}]);
};

Multiple Parameters in URL

I have a search form with multiple search filters.
I followed the tutorial example
https://dev.to/gaels/an-alternative-to-handle-global-state-in-react-the-url--3753
It works fine with one search filter, input textbox or checkbox. But now both. The URL looks like this
https://apiurl/?keyword=xxx&checkbox=
OR
https://apiurl/?keyword=&checkbox=item1.
I have no idea how to update both parameter state. Any suggestion? Should I use query parameter object like state={ query:{keyword:'', checkbox:[]}} or just state={keyword:'', checkbox:[]} and how to update URL for multiple parameters?
Thanks.
function getParams(location) {
const searchParams = new URLSearchParams(location.search);
return {
keyword: searchParams.get('keyword') || '',
checkbox: searchParams.get('checkbox') || '',
};
}
function setParams({keyword = "", checkbox = ""}) {
const searchParams = new URLSearchParams();
searchParams.set("keyword", keyword);
searchParams.set("checkbox", checkbox );
return searchParams.toString();
}
state = { keywordValue: "", checkboxValue: "" };
updateKeywordValue = e => this.setState({ keywordValue: e.target.value });
updateCheckboxValue = e => this.setState({ updateCheckboxValue : e.target.value });
use these functions instead of updateInputValue and then create another function, which is triggered on click of a button, that calls the setParams function.
Now calling this.props.history.push(?`${url}\`); will push the values to both the query params
to get the params, you have to do const {keyword, checkbox} = getParams(location);

React.useState is changing initialValues const

I'm experiencing some odd behavior with react's useState hook. I would like to know why this is happening. I can see a few ways to sidestep this behavior, but want to know whats going on.
I am initializing the state with the following const:
const initialValues = {
order_id: '',
postal_code: '',
products: [
{
number: '',
qty: ''
}
]
}
const App = (props) => {
const [values, setValues] = React.useState(initialValues);
...
products is an array of variable size. As the user fills in fields more appear.
The change handler is:
const handleProductChange = (key) => (field) => (e) => {
if (e.target.value >= 0 || e.target.value == '') {
let products = values.products;
products[key][field] = e.target.value;
setValues({ ...values, products });
}
}
What I am noticing is that if I console log initialValues, the products change when the fields are changed. None of the other fields change, only inside the array.
Here is a codepen of a working example.
How is this possible? If you look at the full codepen, you'll see that initialValues is only referenced when setting default state, and resetting it. So I don't understand why it would be trying to update that variable at all. In addition, its a const declared outside of the component, so shouldn't that not work anyway?
I attempted the following with the same result:
const initialProducts = [
{
number: '',
qty: ''
}
];
const initialValues = {
order_id: '',
postal_code: '',
products: initialProducts
}
In this case, both consts were modified.
Any insight would be appreciated.
Alongside exploding state into multiple of 1 level deep you may inline your initial:
= useState({ ... });
or wrap it into function
function getInitial() {
return {
....
};
}
// ...
= useState(getInitial());
Both approaches will give you brand new object on each call so you will be safe.
Anyway you are responsible to decide if you need 2+ level nested state. Say I see it legit to have someone's information to be object with address been object as well(2nd level deep). Splitting state into targetPersonAddress, sourePersonAddress and whoEverElsePersonAddress just to avoid nesting looks like affecting readability to me.
This would be a good candidate for a custom hook. Let's call it usePureState() and allow it to be used the same as useState() except the dispatcher can accept nested objects which will immutably update the state. To implement it, we'll use useReducer() instead of useState():
const pureReduce = (oldState, newState) => (
oldState instanceof Object
? Object.assign(
Array.isArray(oldState) ? [...oldState] : { ...oldState },
...Object.keys(newState).map(
key => ({ [key]: pureReduce(oldState[key], newState[key]) })
)
)
: newState
);
const usePureState = initialState => (
React.useReducer(pureReduce, initialState)
);
Then the usage would be:
const [values, setValues] = usePureState(initialValues);
...
const handleProductChange = key => field => event => {
if (event.target.value >= 0 || event.target.value === '') {
setValues({
products: { [key]: { [field]: event.target.value } }
});
}
};
Probably the simplest move forward is to create a new useState for products which I had started to suspect before asking the question, but a solution to keep the logic similar to how it was before would be:
let products = values.products.map(product => ({...product}));
to create a completely new array as well as new nested objects.
As #PatrickRoberts pointed out, the products variable was not correctly creating a new array, but was continuing to point to the array reference in state, which is why it was being modified.
More explanation on the underlying reason initialValues was changed: Is JavaScript a pass-by-reference or pass-by-value language?

how to create custom selector with local state

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?

Resources