React, visualizing and editing a JSON object. Populating data on inputs - javascript

I have this kind of objects
{
asignaturaId: 1,
content: {
[question_var]: {
elements: {
text: 'text1',
image: 'img1.jpg'
}
}
[question_var]: {
text: 'text2',
image: 'image2.jpg'
}
}
}
the thing that varies is the [question_var] part and how many objects does content key includes.
I want to be able to dynamically write out all the data on inputs and being able to replace the needed data.
so far I got this:
[...]
<FormControl>
<InputLabel htmlFor="modulo">Asignatura ID:</InputLabel>
<Input id="asignaturaId" name="asignaturaId" value={arrayData[0]} onChange={(e) => setAsignaturaId(e.target.value)} />
</FormControl>
{arrayData.map(pregunta => {
console.log(pregunta)
return (
<>
<FormControl>
<InputLabel htmlFor="texto">Texto:</InputLabel>
<Input id="texto" name="texto" onLoad={() => setTexto(pregunta[1].elements.text)} value={pregunta[1].elements} onChange={(e) => setText(e.target.value)} />
</FormControl>
[...]
what I omited was, I took the object and did Object.entries(myObject) and assigned it to the arrayData array. asignaturaId will be just one key on every object I receive, so I can easily pass that value and change it on an input. The problem is the content, as it is dynamic and change too much, I find it hard to use useEffect() hook to populate my input with the data I receive and manipulating it in the same input without changing the other values with the same keys or straight foward, not being able to edit them at all.
I have 4 days in this problem. please help!
Edit: I am sorry about my english, if I didn't make myself clear, I want to overwrite the object, keeping the data the user doesn't update in the front end.

Declear your json array first in your React hook Component and make sure each object should have unique key
const [yourJson,setYourJson] = useState({
asignaturaId: 1,
content: {
[question_var1]: {
elements: {
text: 'text1',
image: 'img1.jpg'
}
},
[question_var2]: {
text: 'text2',
image: 'image2.jpg'
}
}
});
Add Setter to add new object in your json content
const addNewData = (text, imgSrc) =>{
let newIndex = Object.keys(yourJson.content).length + 1;
let newJson = yourJson;
newJson.content[`question_var${newIndex}`] = {
'text': text,
'image': imgSrc
}
setYourJson({...newJson})
}
And in render callback use this mapping
<FormControl>
{Object.keys(yourJson.content).map(key => (
<InputLabel htmlFor="texto">{yourJson.content[key].text}</InputLabel>
// <Input id="texto" name="texto" ....
))}
</FormControl>

Related

React Select Placeholder

React Select palceholder does show up but with some erros. So below is the source code from my project where on click of a button, a modal is displayed where the users are allowed to fill the form. here the drop down list is being displayed where the placeholder for the drop down is not being displayed.
Object
getCountries()
[
{"id":"a6cedb32","name":"America","code":"USA"},
{"id":"042aa7bd0f","name":"Yemen","code":"YE"}
]
useEffect(() => {
if(countryList)
{
let countryDDL = countryList?.map(obj => ({
...obj,
label: obj.code
}))
setCountryDDL(countryDDL)
}
}, [countryList])
return (
<div>
<Select
options={countryDDL}
name="country"
placeholder="I ain't showing up"
/>
</div>
)
}
export function validationSchema() {
return Yup.object({
country: Yup.string().required("This field is required")
});
}
Your obj does not have any code, I'm not sure what are you trying to provide as a value.
Also obj??.code will result an error.
And obj array missing to ','
const obj = [
{ "id":"a654d086",
"label":"A" // here
"name":"A"
},
{
"id":"a654d656086",
"label":"B" //and here
"name":"B"
}
]
Your problem is not in placeholder at all. Your select can't work with provided data. Try something like this:
const [selectedObj, setSelectedObj] = useState();
const handleSelect = (value) => {
setSelectedObj(value)
}
<Select
options={obj}
value={{label: selectedObj.name}}
name="name"
onSelect={handleSelect}
placeholder="Please show up"
/>
This probably might not run, I haven't test it, but the main idea is here. You should select some object from your array of objects (named obj) and display its' value. Currently you are trying to pass name from obj itself and it doesn't have such property at all

Attribute is not saved in Gutenberg

I am trying to create a custom block for Wordpress Gutenberg.
I have the following attributes:
"icon": {
"type": "object",
"source": "html",
"selector": ".jig_defbox-icon"
},
"color": {
"type": "string",
"default": "#919191"
}
In my EditJS I try to store values from a color-picker and a radiogroup into these attributes.
const [ toggled, setToggled ] = useState( );
return(
<InspectorControls>
<ColorPalette
colors={[
{name: 'lightgray', color: '#d8d8d8'},
{name: 'darkgray', color: '#919191'},
{name: 'red', color: '#dc1a22'}
]}
disableCustomColors={true}
onChange={(value) => {props.setAttributes({color: value});}}
value={props.attributes.color}
clearable={false}
/>
<RadioGroup
onChange={ (value) => {setToggled(value);props.setAttributes({icon: value});} }
checked={props.attributes.icon}
>
{
str_array.map(
item => ( <Radio value={item}><Icon icon={icon_getter(item);}/></Radio>)
)
}
</RadioGroup>
</InspectorControls>
)
In my SaveJS I try to render my markup according to these attributes:
const {attributes} = props
<div style={{"fill": attributes.color}}>
<Icon icon={icon_getter(attributes.icon)}/>
</div>
The goal is to render an svg-icon according to the selection on my radiogroup.
Issue 1: Every new edit-session in backend, the selection of my radiogroup is gone, even with useState (tried without useState first)
Issue 2: Every new edit-session, a console error is logged, that says that the post-body markup does not match the markup returned by the save function, because the save-markup does not contain the icon attribute content
As far as I was able to enclose the problem, the icon attribute is not correctly saved. I tried to save the "key" for the icon as a string and object. If I log the value in the save function, it is empty, while the selected color works as expected, both in frontend and backend.
I was able to fix it via rethinking my concept of fetching the icon.
Apparently, Gutenberg tried to store the code of the icon into the database, but not the key. When loading the backend editor, the icon_getter function received a null value, therefore, the difference between post body and save function code.
I changed my editJS:
<RadioGroup
onChange={ (value) => {props.setAttributes({icon: value});} }
checked={props.attributes.icon}
>
{
my_icons_as_list.map(
item => ( <Radio value={item}><Icon icon={my_icons[props.attributes.icon];}/></Radio>)
)
}
</RadioGroup>
and my saveJS:
<Icon icon={ my_icons[ attributes.icon ] } />

How to update an array by index using the useState hook?

I have a select component that I would like to use to update the values of an array of objects based on the index.
I use the hook like this:
const [areas, setAreas] = useState(product.areas);
This returns the following for "areas":
[
0: {de: "Getraenke", en: "Drinks"},
1: {de: "Snacks", en: "Snacks"}
]
My select component looks like this:
{areas?.map((area: {de: string, en: string}, index: number) => (
<Wrapper key={index}>
<Select
label="Area"
name="product-area"
value={area[lang] || ''}
onChange={e => setArea([...area, { [lang]: e.target.value }])}
>
{areaOptions.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</Select>
</InputWrapper>
))}
In this way I unfortunately get the following for "area" after selecting an option (here "Sweets"):
[
0: {de: "Getraenke", en: "Drinks"},
1: {de: "Snacks", en: "Snacks"}
2: {en: "Sweets" }
]
So the first object of the array is not updated as intended, but a further object is appended, which unfortunately also lacks the "de" language.
My goal is to update the first object (or the object based on the index of the select component) of the array so that instead of:
0: {de: "Getraenke", en: "Drinks"}
..the updated object looks like this:
0: {de: "Suessigkeiten", en: "Sweets"}
The objects within the array should therefore be updated based on the index of the current select component and also take into account the selected language (default "en").
The easiest way to do this is to clone the array, update the specific array item by index and then replace the old array with it using useState, like this.
const updateArea = (e, lang, index) => {
const updatedAreas = [...areas];
updatedArea[index][lang] = e.target.value;
setAreas(updatedAreas);
}
...
{areas?.map((area: {de: string, en: string}, index: number) => (
<Wrapper key={index}>
<Select
label="Area"
name="product-area"
value={area[lang] || ''}
onChange={e => updateArea(e, lang, index)}
>
{areaOptions.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</Select>
</InputWrapper>
))}
Let's solve this step by step
1. A further object is appended
That's because you're telling it to do so :)
onChange={e => setArea([...area, { [lang]: e.target.value }])}
This means, copy the entire array ...area and add a new object at the end { [lang]: e.target.value }
Another mistake you're making is not using a callback function, proper way to access current ...area should be this:
onChange={e => setArea((currentArea) => [...currentArea, { [lang]: e.target.value }]}
This way you always have the most up-to-date version of area.
Now, to update the specific object by the index, you would do the following:
onChange={e => setArea((currentArea) => {
const { value } = e.target;
const newArea = currentArea.map((singleArea, areaIndex) => {
if (areaIndex === index) {
return { [lang]: value }
}
return singleArea;
});
return newArea;
}}
2. Lacks the "de" language
Again, you're explicitly only adding one language to it :)
{ [lang]: value } // { en: 'something' }
Not sure how to fix this one for you, but at least you can understand why its wrong, It should be something like (just a concept, this won't work):
{ [langDe]: value, [langEn]: value } // { de: 'Guten Tag', en: 'something' }

How to fix redux-form "validate" when rendering an input from a variable in react-admin?

So I'm using a custom-built dialog component in react-admin which has a SimpleForm inside it. When I try to use input-level validate attribute on a LongTextInput, it just doesn't work. There is no error in the console or anything like that, but the input just accepts the text as is without validation. I don't seem to understand where I'm going wrong.
In the code that you see below, the inputs in the form that I have to render are conditional based on the props I'm passing from the parent. Hence, creating the form in a variable and then rendering it in the return() method.
What I've tried is rendering the inputs directly in the return() method without using the variable, but that does not seem to fix the validation problem as well.
Here's the array that defines the validation parameters I want to use -
const validateReason = [required(), minLength(5), maxLength(100)];
And I'm using this in the below render() method -
render() {
const props = this.props;
let renderForm = null;
if(props.formType === 'reject') {
renderForm =
(<SimpleForm
form="form"
toolbar={null}>
<RadioButtonGroupInput onChange={this.handleChoiceChange} source="reason" choices={[
{ id: '1', name: 'Inappropriate' },
{ id: '2', name: 'Abusive' },
{ id: '3', name: 'Not Product Related' },
{ id: '4', name: 'Others' },
]} />
<FormDataConsumer label="Select L2 Category" alwaysOn>
{({ formData }) => formData.reason === '4' &&
<LongTextInput
defaultValue={this.state.reasontext}
onChange={this.handleReasonChange}
source="reason_text"
label={props.title}
validate={validateReason}
/>
}
</FormDataConsumer>
</SimpleForm>);
}
else if(props.formType === 'skip') {
renderForm =
(<SimpleForm
form="form"
toolbar={null}>
<LongTextInput
defaultValue={this.state.reasontext}
onChange={this.handleReasonChange}
source="reason_text"
label={props.title}
validate={validateReason}
/>
</SimpleForm>);
}
return (
<Fragment>
<Dialog
fullWidth
open={props.showDialog}
aria-label={props.title}
>
<DialogTitle>{props.title}</DialogTitle>
<DialogContent>
{renderForm}
</DialogContent>
<DialogActions>
<SaveButton
onClick={() => {
props.handleClose();
this.handleSaveClick(props.formType);
}}
label={props.positiveButtonText}
/>
<Button onClick={() => {
this.clearReason();
props.handleClose();
}}>
Cancel
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
}
I expect that the validate attribute validates my input correctly according to my validation parameters.

How to get a reference to target element inside a callback

I'm building a component which displays a series of generic input fields. The backing store uses a simple array of key-value pairs to manage the data:
[
{fieldkey: 'Signs and Symptoms', value:'fever, rash'},
{fieldkey: 'NotFeelingWell', value:'false'},
{fieldkey: 'ReAdmission', value:'true'},
{fieldkey: 'DateOfEvent', value:'12/31/1999'}
]
In order to eliminate a lot of boilerplate code related to data binding, the component uses these same keys when generating the HTML markup (see 'data-fieldname' attribute).
var Fields = React.createClass({
handleOnChange:function(e){
Actions.updateField( {key:e.target.attributes['data-fieldname'].value, value:e.target.value})
},
setValue:function(){
var ref = //get a reference to the DOM element that triggered this call
ref.value = this.props.form.fields[ref.attributes['data-fieldname']]
},
render:function(){
return (<div className="row">
<Input data-fieldname="Signs and Symptoms" type="text" label='Notes' defaultValue="Enter text" onChange={this.handleOnChange} value={this.setValue()} />
<Input data-fieldname="NotFeelingWell" type="checkbox" label="Not Feeling Well" onChange={this.handleOnChange} value={this.setValue()} />
<Input data-fieldname="ReAdmission" type="checkbox" label="Not Feeling Great" onChange={this.handleOnChange} value={this.setValue()} />
<Input data-fieldname="DateOfEvent" type="text" label="Date Of Event" onChange={this.handleOnChange} value={this.setValue()} />
</div>)
}
})
My goal is to use the same two functions for writing/reading from the store for all inputs and without code duplication (i.e. I don't want to add a refs declaration to each input that duplicates the key already stored in 'data-fieldname') Things work swimmingly on the callback attached to the 'onChange' event. However, I'm unsure how to get a reference to the DOM node in question in the setValue function.
Thanks in advance
I'm not sure if I understand your question right, but to reduce boilerplate I would map your array to generate input fields:
render:function(){
var inputs = [];
this.props.form.fields.map(function(elem){
inputs.push(<Input data-fieldname={elem.fieldkey} type="text" label="Date Of Event" onChange={this.handleOnChange} value={elem.value} />);
});
return (<div className="row">
{inputs}
</div>)
}
This will always display your data in props. So when handleOnChange gets triggered the component will rerender with the new value. In my opinion this way is better than accessing a DOM node directly.
If you want to use dynamic information on the input, you need to pass it through the array, and make a loop.
Here is a little example based on Dustin code:
var fieldArray = [ //replace by this.props.form.fields
{
fieldkey: 'Signs and Symptoms',
value: 'fever, rash',
type: 'text',
label: 'Notes'
},
{
fieldkey: 'NotFeelingWell',
value: 'false',
type: 'checkbox',
label: 'Not Feeling Well'
},
];
var Fields = React.createClass({
handleOnChange:function(e){
var fieldKey = e.target.attributes['data-fieldname'].value;
Actions.updateField({
key: fieldKey,
value: e.target.value
})
},
render() {
var inputs = [];
fieldArray.map(function(field) { //replace by this.props.form.fields
inputs.push(
<Input
data-fieldname={field.fieldkey}
value={field.value}
type={field.type}
label={field.label}
onChange={this.handleOnChange} />
);
}.bind(this));
return (
<div className="row">
{inputs}
</div>
);
}
});

Categories

Resources