How to access JSON object in React state? - javascript

setRadio= (id) => {
const {formRating} = this.state;
fetch(`http://localhost:3030/getLessonCondsDB?formId=${id}`)
.then(response => response.json())
.then(response=>{
this.setState({formRating:response.data})
console.log(response.data);})
.catch(err=>console.error(err))
}
The above method assigns the JSON object which is displayed in console as [RowDataPacket {condId: 'C2.1(a)', rate: 3, condition: 'Random text here' }, RowDataPacket {condId: 'C2.2(b)',rate: 3,condition: 'more random text' }]to the state object formRating which is displayed in dev tools as below
formRating: Array
> 0: Object
condId: 'C2.1(a)'
rate: '3',
condition: 'Random text here'
> 1: Object
condId: 'C2.2(b)'
rate: '3',
condition: 'more random text'
Any attempt to console.log(formRating) just prints and empty line on the console.
Instead of fetching from the server I had previously hardcoded this data into an array as below
const formValues= [{condId :'C2.1(a)',rate:'3', condition:'Random text here'},{condId :'C2.2(b)',rate:'3', condition:'more random text'}]
and had a method in another component to create radioGroups mapping each set of conditions allowing users to change the rate value as discussed in How to set defaultValue of a radioGroup from a nested Array object in React state? which works with the hardcoded array but not the JSON array which produces a "TypeError: values.formRating.map is not a function" with the below function in the component where radioGroups are displayed allowing the user to customise the "rate" value.
createRadioGroups = ()=>{
const {values} = this.props;
console.log(values.formRating);
return(values.formRating.map(
item =>
<Grid container>
<Grid item xs={2} style={{marginTop:20, marginRight:0}}>{item.condId} </Grid>
<Grid item xs={6} style={{marginTop:20}}>{item.condition} </Grid>
<Grid item xs={4} style={{marginTop:10}}>
<RadioGroup defaultValue={item.rate} name={item.condId} onChange={this.changeButton(item.condId)} style={{display: 'flex', flexDirection: 'row'}}>
<FormControlLabel value="3" control={<Radio color="primary" />} label=' ' labelPlacement="top"/>
<FormControlLabel value="2" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
<FormControlLabel value="1" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
<FormControlLabel value="N/A" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
</RadioGroup>
</Grid>
</Grid>
))
};
Any help is appreciated.

That is because the fetch operation within setRadio() is asynchronous, thus any operations that are dependent on the state, or the values from setRadio() will fail. This is why calling createRadioGroups() before setRadio() is returned and completed will result in an undefined value.
I am not sure how exactly is your component structured, but you should handle any subsequent operations within the .then() block,
setRadio= (id) => {
const {formRating} = this.state;
fetch(`http://localhost:3030/getLessonCondsDB?formId=${id}`)
.then(response => response.json())
.then(response=>{
this.setState({formRating:response.data})
console.log(response.data);
// do the rest here
})
.catch(err=>console.error(err))
}
Or if the rendering is handled on the template, you should conditionally call the method only after formRating is populated.
render() {
const { formRating } = this.state;
return <>
{ formRating && formRating.length && this.createRadioGroups() }
</>
}
Or, if createRadioGroups() is on another child component,
render() {
const { values } = this.props;
return <>
{ values && values.formRating && values.formRating.length && this.createRadioGroups() }
</>
}

How are you passing the 'values' prop to the createRadioGroup? Seems like you need to pass it in (see snippet below) then try console logging the entire props object to make sure you are actually receiving it.
createRadioGroups = (props)=>{
const {values} = this.props;
After you check that, then consider when you are calling setRadio? Are you sure the state has already been updated so that it is available when you call createRadioGroup? If it possibly hasn't been updated then you can try initializing your state with the 'shape' of your expected data so that it will render with no data, then rerender once the state is updated. Would look something like this:
this.state = {
formValues=
[
{
condId :'',
rate:'',
condition:''
}
];

Try this
return(
<>
this.props.values&&this.props.values.formRating.map()
</>
)

Related

React child class receiving undefined props even if class is mounted

I'm trying to send an object as 'props' from a parent Class into a Child class with the intention of showing information:
Parent class
const TaskDetail = () => {
//Get parameter from URL
const { id } = useParams()
const [taskDetail, setTaskDetail] = useState([])
useEffect(() => {
TaskService.getTaskById(id)
.then(response => {
setTaskDetail(response.data);
})
}, [id]) //This [id] avoid infinite loop of requests
if (!taskDetail) return <div>No data</div>
return (
<div>
<Detail taskDetail={taskDetail}/>
</div>
)
}
This class makes a request to the server and gather a data object. This object is then passed onto the Child Detail where it will be deserialized and visualized accordingly.
Child class
const Detail = ({ taskDetail }) => {
return (
<Box
align='center'
justifyContent='center'
sx={{ width: '100%', marginTop: 4}}
bgcolor=''
//border='solid'
>
<Stack
//border='solid'
sx = {{width: '50%'}}
justifyContent='center'
//border='solid'
>
<Typography
sx = {{ marginTop: 5}}
variant='h4'
fontWeight='bold'
bgcolor='#b2dafa'
>NOMBRE DEL EJERCICIO<br/>{taskDetail.taskName}</Typography>
<Box
sx = {{ marginTop: 3}}
bgcolor='#b2dafa'
>
<Typography
variant='h5'
align='center'
sx = {{ margin: 2}}
fontWeight='bold'
>DETALLES DEL EJERCICIO</Typography>
<Typography
sx = {{ margin: 2}}
variant='text'
border='#555'
>{taskDetail.details}
</Typography>
</Box>
<Box
sx = {{ marginTop: 5}}>
<Typography
variant='h6'
>Marca para completar</Typography><Toogle
label=''
toogled={false}
onClick={null}/>
<br></br>
</Box>
{taskDetail.id}
<Box
sx = {{ marginTop: 2}}>
<AddComment taskId={taskDetail.id}/>
</Box>
<Box
sx = {{ marginTop: 2}}>
<ListComments taskId={taskDetail.id}/>
</Box>
</Stack>
</Box>
)
}
As you can observe, this object is also passed to other child components. The context is that TaskDetail shows information and then offers two features, ListComments and AddComments. At the current moment, I am having an issue in AddComment where the prop taskId={taskDetail.id} is undefined.
Function in Child where I am having this issue
function ListComments(props) {
const [comments, setComments] = useState([])
useEffect(() => {
console.log('DEBUG listcomments: ' + props.taskId)
TaskService.getTaskCommentsById(props.taskId)
.then(response => {
setComments(response.data)
})
}, [])
return (
<div>
<h2>Comentarios</h2>
{comments.map((comment, _) => {
return <Comment key={comment.id} comment={comment}/>
})}
</div>
)
}
I have noticed, that if I change something in the code and save it (re-renders the page). All of a sudden I get the atribute that I need instead of the undefined value.
How could I avoid this situation?
I trust that I have made a huge mistake that I am unable to see, but its part of the learning. For this reason, I am open to suggestions.
Since the data you get back from the service is an object I would suggest to initialize the state with an object {}.
const [taskDetail, setTaskDetail] = useState({});
In the ListComments component you can do the same as you did in the TaskDetail component. Run useEffect when the props.taskId changes. And add a early return if the taskId have no value yet.
useEffect(() => {
console.log("DEBUG listcomments: " + props.taskId);
if (!props.taskId) return;
TaskService.getTaskCommentsById(props.taskId).then((response) => {
setComments(response.data);
});
}, [props.taskId]);
Very cool that you're reaching out for help!
First, just a minor correction: They're not classes, they're functions / components.
I think the problem is the check condition at the top:
if (!taskDetail) return <div>No data</div>
Since taskDetail is initialised as an array, the condition will always be true since arrays are objects in javascript.
Because of this too, when you're passing it down, at least on the first render, none of these props in the lower components exist. So maybe try initalising it either as null, or changing the condition to the following:
if (!taskDetail || taskDetail.length === 0) return <div>No data</div>
One more thing, to make sure that the data is fetched, you need to add props.taskId to the dependency list in the List component.

How to show objects from array one by one , in single component

I'm trying to build a component who going to have multiple objects from the array.
I want to show them one by one.
Let's assume, I have 3 objects inside the array. Then first object-related content should be shown first until the user opts to continue or skip for the next object until all 3 objects have been shown. How can I do this inside the One page?
For example, this is a minimal Code that how I'm going to make a component, I want to handle the Data like that each object should be shown independently and move to next on user input. Whether the user wants to skip or continue, the next object should be shown on the same page.
import { Fragment } from 'react'
import Data from 'Data'
const Main = () => {
const data = [
{ id: 1, more: '...' },
{ id: 2, more: '...' },
{ id: 3, more: '...' }
]
const submitHandler = () => {
// some action
}
return (
<Fragment>
<Card style={{ minHeight: '40rem' }}>
<Card.Body>{data ? data.map((el) => <div key={el.id} >
<Data obj={el} /> // Passing to child
</div>) : null}
</Card.Body>
<Card.Footer>
<Button variant="outline-danger" onClick={submitHandler} className="mx-1">
Skip
</Button>
<Button variant="primary" onClick={submitHandler}>
Continue
</Button>
</Card.Footer>
</Card>
</Fragment>
)
}
export default Main
Edit:
#jasonmzx below suggested some solution but it's giving type error. Can anybody fix this here , CodeSandBox
Here you could use the state to track which index the user is currently on and render the output based on your array
import { Fragment, useState } from 'react';
import Data from 'Data';
const Main = () => {
const [index,setIndex] = React.useState(0); // Starting from the beginning
const data = [
{ id: 1, more: '...' },
{ id: 2, more: '...' },
{ id: 3, more: '...' }
]
// I'm making this a function for cleanliness
// This renders the array at the index of the state
const showDat = (data, index) => {
return
<p>{data[index].more}</p>
}
const submitHandler = () => {
// Skip or cont: update state!
setIndex(index+1);
}
return (
<Fragment>
<Card style={{ minHeight: '40rem' }}>
<Card.Body>{data ? data.map((el) => <div key={el.id} >
<Data obj={el} /> // Passing to child
</div>) : null}
</Card.Body>
<Card.Footer>
<Button variant="outline-danger" onClick={submitHandler} className="mx-1">
Skip
</Button>
<Button variant="primary" onClick={submitHandler}>
Continue
</Button>
{showDat(data, index)}
</Card.Footer>
</Card>
</Fragment>
)
}
export default Main
Basically here is what will happen (when you code the submitHandler); the submitHandler updates the state, the state adds 1 onto index, and since the state has updated, a re-render happens, meaning the showDat() function being called within the Main component's render is being called again with the new index, to display the next index in ur array

each child in a list should have unique 'key' prop

I keep getting this warning "each child in a list should have unique 'key' prop" even though I have unique items with different keys.
Whenever I create a new 'plant' object I give it a new uuid
setPlants(prevItems => {
return [
{name: newPlantName, key: uuid.v4(), timeInterval: null},
...prevItems,
];
And my listItem component is set up with a key
<ListItem
key={plant.key}
Whenever I print my list all the 'keys' have a different uuid. The warning occurs every time I refresh the app so it might be somehow because i'm using a database to access the data? I'm not really sure but I am using mmkv to store the data from my state and then I show that data when the app first opens.
This is the full mapping:
{plants &&
plants.map(plant =>
plant ? (
<PlantItem
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>
) : null,
)}
PlantItem component:
return (
<>
<ActionSheet
visible={actionSheetVisible}
closeOverlay={() => {
setActionSheetVisible(false);
}}
actions={actions}
/>
<ListItem
key={plant.key}
onPress={() => {
setActionSheetVisible(true);
}}
bottomDivider>
<ListItem.Content key={plant.key} style={styles.listItemContainer}>
<ListItem.Title>{plant.name}</ListItem.Title>
{/* <Icon name="check" size={20} /> */}
</ListItem.Content>
</ListItem>
{showAddTimeInterval && (
<AddTimeInterval
createTimeInterval={createTimeInterval}
closeModal={toggleShowAddTimeInterval}
plantName={plant.name}
/>
)}
</>
);
This is how my states are initiated
const [plantsStorage, setPlantsStorage] = useStorage('plantss');
const [plants, setPlants] = useState(plantsStorage ? plantsStorage : []);
useEffect(() => {
setPlantsStorage(plants);
});
The warning is just really annoying, if there is no way to change my code to fix it is there a way to mute it somehow? just for this specific warning not all warnings.
The React key should be used on the outermost mapped element.
React Lists and Keys
{plants.map(plant =>
plant ? (
<PlantItem
key={plant.key} // <-- use key here!
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>
) : null,
)}
Suggestion, filter the plants array to remove the null "holes".
{plants
.filter(Boolean) // include only truthy plant objects
.map(plant => (
<PlantItem
key={plant.key} // <-- use key here!
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>)
)}

How to update the option values in dropdown on child component from parent component state in react js?

I have a requirement like, In parent component i'm getting data from api and pass that array of data to DataTable child component to display in tablular format. In that, I need to display a drop-down for each column and option are predefined(No need of dynamic values). I need only, when the user select the dropdown values, it should update in parent component state and populates that selected in particular drop-down component.
Here is what i tried,
Parent component for storing dropdown values ::
let [schema, setSchema] = useState([])
const handleChange = (event, index) => {
setSchema([
...schema,
{
Name: event.target.name,
Type: event.target.value
}
]);
};
DataTable component inside parent compoent to display data ::
<Container>
<DataTable
data={data}
meta_data={meta_data}
handleChange={handleChange}
schema={schema}
/>
</Container>
Here the iteration of each object from array to display dropdown once ::
{
Object.keys(filteredData[0]).map((field, index) => {
return (
<>
<TableCell align="left" key={field} className={classes.headings}>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<FormControl className={classes.margin}>
<NativeSelect
id="demo-customized-select-native"
input={<BootstrapInput />}
onChange={(e) => props.handleChange(e, index)}
name={field}
value={props.schema.length > 0 ? props.schema[index]['Type'] : null}
>
<option value={0}>Type</option>
<option value={`str`}>string</option>
<option value={`int`}>interger</option>
<option value={`float`}>float</option>
</NativeSelect>
</FormControl>
</div>
</TableCell>
</>
)
}
)}
I want, In the value prop of NativeSelect to populate the value of schema,
The schema should looks like
[
{
Name: 'passegner',
Type: 'str'
},
{
Name: 'Month',
Type: 'float'
},
]
When i retrieving the Type field of that array based in Index. It is giving 'Type' of undefined, But i actually working when console from outside of return in child component.
value={props.schema.length > 0 ? props.schema[index]['Type'] : null} - > this line giving me the error and wroking fine when console.log() outside return.
console.log(props.schema.length > 0 ? props.schema[0]['Type']) -> it is working
How to resolve?
Any suggestions would be great.
Above the return statement put a console like
console.log(props.schema[index]['Type'])
Try to figure out what went wrong, From my Guess Object.keys(filteredData[0]) may have move array values compared to props.schema[index]. So It may throw an error.
The useState hook has the following form:
const [state, setState] = useState(initialState);
In the React docs it is written:
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
Meaning that you should change this:
setSchema([
...schema,
{
Name: event.target.name,
Type: event.target.value
}
]);
into this:
setSchema((prevSchema) => {
return [
...prevSchema,
{
Name: event.target.name,
Type: event.target.value
}
])
};

My setState method called multiple times which causes a problem?

I am having a weird problem, I readed some answers about it and some solutions but still can not manage to solve mine, that is my problem, which is well known, ( lot of code I know ) :
Maximum update depth exceeded. This can happen when a component repeatedly
calls setState inside componentWillUpdate or componentDidUpdate. React limits
the number of nested updates to prevent infinite loops.
This is my component :
class Operations extends Component {
state = {
data: [],
mode: 1, // 1 desktop - 2 phone - 3 bigdesktop - 4 tablette
contratSelected: 0
};
constructor(props) {
super(props);
}
componentDidMount() {
this.setState({ data: !isEmpty(this.props.operations) ? this.props.operations : "" });
//
// Many other methods here, but not needed to show the problem
//
sortDateOperation = ({ order }) => {
const data = partition(this.state.data, item => !item.isChild);
for (let counter = 0; counter < data[0].length; counter += 1) {
data[0][counter].chiffrage = this.dateToNumber(data[0][counter].dateOperation);
}
const result = orderBy(
data[0],
["annee", "chiffrage"],
["desc", order === 1 ? "asc" : "desc"]
);
result.forEach((res, index) => {
res.id = index;
});
// The Line causing error
this.setState({ data: result });
return result;
};
render() {
return (
<Fragment>
<Title text={this.props.title || ""} color="primary" />
{this.state.mode !== 2 && (
<div className="co-table-data">
<div className="co-data-table">
<Grid container>
<Grid item xs={12} sm={12}>
<Datatable
value={this.state.data}
type="grey"
autoLayout
upperCaseHeader
rowGroupHeaderTemplate={data => data.annee}
rowGroupFooterTemplate={() => undefined}
rowGroupMode="subheader"
groupField="annee"
className="co-operations-contrat"
>
<Column
header={intl.get("CONTRAT_DATE_DE_VALEUR")}
field="dateOperation"
sortable="custom"
sortFunction={this.sortDateOperation}
body={this.getDateContent}
/>
<Column
header={intl.get("CONTRAT_TYPE_MOUVEMENT")}
field="typeMouvement"
body={this.getTypeContent}
/>
<Column
header={`${intl.get("MONTANT")}(€)`}
field="montantOperation"
sortable="custom"
sortFunction={this.sortMontanta}
body={this.getMontantContent}
/>
</Datatable>
</Grid>
<Grid item xs={12} />
</Grid>
</div>
</div>
)}
{this.state.mode === 2 && <MobileDatatable />}
</Fragment>
);
}
}
export default Operations;
So when I click on my Columln is the datatable, my dates get sorted, I need to update my state ( data ) but I get this error, here exactly :
....
<Column
header={intl.get("CONTRAT_DATE_DE_VALEUR")}
field="dateOperation"
sortable="custom"
sortFunction={this.sortDateOperation}
body={this.getDateContent}
/>
....
Any help would be much appreciated.
problem is in your Column componet's header prop
header={intl.get("CONTRAT_DATE_DE_VALEUR")}
should be this
header={() => {intl.get("CONTRAT_DATE_DE_VALEUR")}}
you can't execute function directly inside component react will automatically call for you
so, change all three Column component's header property to this
header={() => {intl.get("CONTRAT_DATE_DE_VALEUR")}}
header={() => {intl.get("CONTRAT_TYPE_MOUVEMENT")}}
header={() => {`${intl.get("MONTANT")}(€)`}}

Categories