I'm learning React and trying to make a small project by myself for the first time, but I'm having trouble with useEffect.
I'm trying to autofill a form with information from my backend. I can get it to autofill, but it continuously sends GET requests. This is what I have:
useEffect(() => {
axios
.get('/admin/edit-product' + location.search)
.then((res) => {
const updatedControls = {
...controlsState,
title: {
...controlsState.title,
value: res.data.title,
},
image: {
...controlsState.image,
value: res.data.image,
},
price: {
...controlsState.price,
value: res.data.price,
},
description: {
...controlsState.description,
value: res.data.description,
},
};
setControlsState(updatedControls);
})
.catch((err) => console.error(err));
}, [controlsState, location.search]);
I thought that the dependency array was supposed to stop it from running continuously, but I guess I'm missing something else.
Not sure if it's needed, but this is what my original state looks like:
const [controlsState, setControlsState] = useState({
title: {
elementType: 'input',
elementConfig: {
type: 'text',
},
label: 'Product Title: ',
value: '',
},
image: {
elementType: 'input',
elementConfig: {
type: 'url',
},
label: 'Image URL: ',
value: '',
},
price: {
elementType: 'input',
elementConfig: {
type: 'number',
},
label: 'Price: ',
value: '',
},
description: {
elementType: 'textarea',
elementConfig: {
name: 'description',
htmlFor: 'description',
},
label: 'Description: ',
value: '',
},
});
and location is from react-router-dom useLocation
You have given controlsState as a dependency for useEffect. But inside your useEffect you are using setControlsState which changes the value of controlsState. And since you have given controlsState as a dependency, useEffect will occur everytime any of its dependency changes. Hence it is repeatedly happening
If you want useEffect to run only once, give [] as second parameter:
useEffect(() => {
...your code...
}, [])
Related
I have the following ContextualMenu structure inside my SPFx Extension build with Fluent UI React:
const menuProps: IContextualMenuProps = {
items: [
{
key: 'Access',
itemType: ContextualMenuItemType.Section,
sectionProps: {
topDivider: true,
bottomDivider: true,
title: 'Sites you have access to',
items: [
{ key: 'DynamicValue1.1', text: 'DynamicValue1.2' },
{ key: 'DynamicValue2.1', text: 'DynamicValue2.2' },
],
},
},
],
};
I also a MS Graph call running getting me some SharePoint Sites & Teams.
I would now like to push those dynamic responses to the to the menuProps at the right place.
So basically add the dynamic array into the nested items object.
items: [
{ key: 'DynamicValue1.1', text: 'DynamicValue1.2' },
{ key: 'DynamicValue2.1', text: 'DynamicValue2.2' },
],
How can I target that "object"? (hope I understand correctly and items is an object...)
Is there a way to do this using array.push()?
To make this library agnostic, it would look something like this:
const obj = {
items: [
{
key: 'Access',
itemType: '',
sectionProps: {
topDivider: true,
bottomDivider: true,
title: 'Sites you have access to',
items: [
{ key: 'DynamicValue1.1', text: 'DynamicValue1.2' },
{ key: 'DynamicValue2.1', text: 'DynamicValue2.2' },
],
},
},
],
};
obj.items[0].sectionProps.items.push({ key: 'DynamicValue3.1', text: 'DynamicValue3.2' })
console.log(obj.items[0].sectionProps.items)
Your console.log would return this:
[
{ key: 'DynamicValue1.1', text: 'DynamicValue1.2' },
{ key: 'DynamicValue2.1', text: 'DynamicValue2.2' },
{ key: 'DynamicValue3.1', text: 'DynamicValue3.2' }
]
If you can access menuProps: IContextualMenuProps, then just replace obj with the necessary variable.
Apologies if title is not clear.
I am using json2csv npm package to prepare csv from json object and this package allows us to add a hook to transform object before actual csv line is prepared.
I only need to manipulate two properties out of all. How can I do this effectively? My code feels too bloated.
const {
Parser: Json2csvParser,
transforms: { unwind },
} = require('json2csv');
const json2csvFields = [
{ value: 'root.filename', label: 'File Name' },
{ value: 'issue.root.priority', label: 'Priority' },
{ value: 'issue.root.url', label: 'URL' },
{ value: 'issue.root.startline', label: 'Start Line' },
{ value: 'issue.root.stopline', label: 'Stop Line' },
{ value: 'issue.root.startcolumn', label: 'Start Column' },
{ value: 'issue.root.stopcolumn', label: 'Stop Column' },
{ value: 'issue.root.issuename', label: 'Issue Name' },
{ value: 'issue.root.issuecategory', label: 'Issue Category' },
{ value: 'issue._', label: 'Issue Description' },
];
const sampleData = [
{
root: {
filename:
'/home/users/john-doe/workspace/foo-project/src/main/classes/foo.cls',
},
issue: {
root: {
priority: 1,
url: 'www.example.com',
startline: 100,
stopline: 105,
startcolumn: 20,
stopcolumn: 25,
issuename: 'blah',
issuecategory: 'Category A',
},
_: ' Fox ',
},
},
];
const json2csvOptions = {
fields: json2csvFields,
quote: '',
header: true,
transforms: [
(item) => ({
'root.filename': item.root.filename.replace(
'/home/users/john-doe/workspace/foo-project/src/main/classes/',
''
),
'issue._': `"${item.issue._.trim()}"`,
// Except for the above two, everything else doens't need any transformation.
'issue.root.priority': item.issue.root.priority,
'issue.root.url': item.issue.root.url,
'issue.root.startline': item.issue.root.startline,
'issue.root.stopline': item.issue.root.stopline,
'issue.root.startcolumn': item.issue.root.startcolumn,
'issue.root.stopcolumn': item.issue.root.stopcolumn,
'issue.root.issuename': item.issue.root.issuename,
'issue.root.issuecategory': item.issue.root.issuecategory,
}),
],
};
const json2csvParser = new Json2csvParser(json2csvOptions);
const csv = json2csvParser.parse(sampleData);
console.log(csv);
This prints below output:
File Name,Priority,URL,Start Line,Stop Line,Start Column,Stop Column,Issue Name,Issue Category,Issue Description
foo.cls,1,www.example.com,100,105,20,25,blah,Category A,"Fox"
EDIT: Updated code to a working example.
After listing the two properties with special treatment, use Object.fromEntries and Object.entries to transform all the issue.root properties to their flat structure with .s in the property names. Then that object can be spread into the returned object.
const transformsFn = ({ root, issue }) => ({
'root.filename': root.filename.replace(
'/home/users/john-doe/workspace/foo-project/src/main/classes/',
''
),
'issue._': `"${issue._.trim()}"`,
...Object.fromEntries(
Object.entries(issue.root).map(
([key, val]) => [`issue.root.${key}`, val]
)
),
});
const json2csvOptions = {
fields: json2csvFields,
quote: '',
header: true,
transforms: [transformsFn],
};
I've data coming from Redux in this format:
[
0: {value: '1.5', label: 'Extra cheese'}
1: {value: '3', label: 'Tomato'}
]
and i try to load them into my react-select.
But it fails, bcs it loads instantly the initialToppings as defaultValue (So it shows me empty Strings as defaultValue). And this Value can never be changed again. But without initialToppings i get nothing at defaultValue bcs redux is to slow and the defaultValue is empty so i can't load it in again later...
const initialToppings = [{ label: '', value: '' }];
const [defaultToppings, setDefaultToppings] = useState(initialToppings);
useEffect(() => {
setDefaultToppings(
editProduct?.selectedToppings?.map((topping, value) => ({
...topping,
value,
})) ?? initialToppings
);
}, [editProduct]);
<Select
options={extraOptions}
formatOptionLabel={formatExtras}
isMulti
defaultValue={defaultToppings}
// defaultValue={[
// { label: 'Test 1', value: '1' },
// { label: 'Test 2', value: '2' },
// ]}
onChange={setSelectedToppings}
/>
You can add key props to Select to force remounting component and make it re-render
<Select
key={defaultToppings}
options={extraOptions}
formatOptionLabel={formatExtras}
isMulti
defaultValue={defaultToppings}
// defaultValue={[
// { label: 'Test 1', value: '1' },
// { label: 'Test 2', value: '2' },
// ]}
onChange={setSelectedToppings}
/>
I've a simple codesandbox, you can check it
Just say my state was repeatitive as such:
state = {
customer: {
name: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Your Name"
},
value: ""
},
street: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Street"
},
value: ""
},
And I wanted to store the object data into a helper function so that I could add zip code, country, and so on so forth and not have my state be cluttered. Would your helper Function look like this? I am having trouble figuring out how to write such a function
const helperFunction = function({elementType, ElementConfig, type, placeholder, value}){
setState({elementType: 'some value', ElementConfig: {type: "", placeholder: ""}})
}
so I can call state = {
name: helperFunction()
}
I guess I really don't know how to write it but I want to show my thought process. I am sure it is easier than this. I am just new to coding. If someone could help me or link me to an article that would be awesome. Thanks.
If I understand your question and what you want behavior to be then I think you could either use a reference object with the shape you want and "default" values and copy, or create the utility function which would do the same but allow for some "configurability". If you are setting your initial state, either in a constructor or in the body, you can't call this.setState.
const referenceObject = {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "",
},
value: "",
};
Then when you are declaring state shallow copy the reference object
state = {
name: { ...referenceObject },
};
If you are looking for a utility function to do this and set some values then one example could look as such.
const helperFunction = ({ elementConfig, ...config }) => ({
...referenceObject,
...config,
elementConfig: {
...referenceObject.elementConfig,
...elementConfig,
}
});
The idea being you shallow copy each level of nesting and apply the override/new value.
Initialize state with what you want to override
state = {
name: helperFunction({
elementConfig: { placeholder: 'Placeholder' },
}),
};
const referenceObject = {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "",
},
value: "",
};
const helperFunction = ({
elementConfig,
...config
}) => ({
...referenceObject,
...config,
elementConfig: {
...referenceObject.elementConfig,
...elementConfig,
}
});
const state1 = {
name: { ...referenceObject
},
};
console.log(state1);
const state2 = {
name: helperFunction({
elementConfig: {
placeholder: 'My Custom Placeholder'
}
}),
};
console.log(state2);
Basically i've made proyxy-component which renders different components based on what the :type is and it works great. The point is that I create a schema of the form controls and a separate data object where the data from the form controls is stored. Everything is working good but i have a problem when formData object contains nested objects.
In my example test.test1
How can i make the v-model value dynamic which is generated based on what the string is.
My Compoennt
<proxy-component
v-for="(scheme, index) in personSchema.list"
:key="index"
:type="scheme.type"
:props="scheme.props"
v-model="formData[personSchema.prefix][scheme.model]"
v-validate="'required'"
data-vv-value-path="innerValue"
:data-vv-name="scheme.model"
:error-txt="errors.first(scheme.model)"
></proxy-component>
Data
data() {
return {
selectOptions,
formData: {
person: {
given_names: '',
surname: '',
sex: '',
title: '',
date_of_birth: '',
place_of_birth: '',
nationality: '',
country_of_residence: '',
acting_as: '',
test: {
test1: 'test',
},
},
},
personSchema: {
prefix: 'person',
list: [
{
model: 'given_names',
type: 'custom-input-component',
props: {
title: 'Name',
},
},
{
model: 'surname',
type: 'custom-input-componentt',
props: {
title: 'Surname',
},
},
{
model: 'test.test1',
type: 'custom-input-component',
props: {
title: 'test 1',
},
},
{
model: 'sex',
type: 'custom-select-component',
props: {
title: 'Sex',
options: selectOptions.SEX_TYPES,
trackBy: 'value',
label: 'name',
},
},
],
},
};
},
I would recomment you to write a vue-method (under the data section) that returns the object for v-model
v-model="resolveObject(formData[personSchema.prefix][scheme.model])"
or
v-model="resolveObject([personSchema.prefix] , [scheme.model])"
There you can do handle the dot-notation and return the proper nested property.
I don't think it's possible directly with v-model, you can take a look at:
https://v2.vuejs.org/v2/guide/reactivity.html
Maybe the best solution would be use a watch (deep: true) as a workaround.
(I would try first to use watch properties inside formData[personSchema.prefix][scheme.model].)