react is not updating components using state in class - reactjs

so first of all I am trying to assign null values to the state component and then assign them values later so here is my constructor of a class
constructor(props){
super(props);
this.state = {
Countrys:null,
States:null,
Citys:null
}
}
and then I am asigning them value is ComponentDidUpdate function so here is my componentDidMount function
componentDidMount() {
navigator.geolocation.getCurrentPosition(function(position) {
console.log(position)
console.log("Latitude is :", position.coords.latitude);
console.log("Longitude is :", position.coords.longitude);
});
this.setState({
Countrys:Country.getAllCountries(),
States:State.getAllStates(),
Citys:City.getAllCities()
})
/*console.log("Country:: "+Country.getAllCountries()[0].name);
console.log("City:: "+City.getAllCities()[0].name);*/
}
and then I am trying to access them in my jsx elements in return using map like this
{this.state.Citys.map((ele ,index) =>
<option value={`${ele}`} className="d-inline mx-2 col-sm-5" onClick={({target}) => target.classList.toggle('bg-primary')}>
{ele.name}
</option>
)}
but its showing me error that
TypeError: Cannot read properties of null (reading 'map')
can anyone tell me what is wrong here or how to correct it
and when I am trying to assign City.getAllCities() like functions directly to this.state instead of assigning them with null it shows me page unresponsive
And City.getAllCities(), Country.getAllCountries(), State.getAllStates() are from npm package "country-state-city"

If, like you mentioned, you're using country-state-city, then there is no reason to defer loading those three values until componentDidMount at all: they're static imports from the country-state-city package.
Just import them at the top of your file, create your initial state in your constructor, assign those values, and you're done without any null states during render.
import { Component } from "react";
import { Country, State, City } from "country-state-city";
...
export class YourClass extends Component {
constructor(props) {
super(props);
this.state = {
countries: Country.getAllCountries(),
states: State.getAllStates(),
cities: City.getAllCities()
};
}
render() { ... }
}

The first render will have state.Citys set to null, as you've set in the constructor.
ComponentDidMount will only fire after the first render.
you should initialized the state in the constructor

As Bernardo Ferreira Bastos Braga mentioned, state.Citys is null in the first render.
One thing you can do to avoid the error is to render conditionally.
{this.state.Citys !== null && this.state.Citys.map((ele ,index) =>
<option value={`${ele}`} className="d-inline mx-2 col-sm-5" onClick={({target}) => target.classList.toggle('bg-primary')}>
{ele.name}
</option>
)}
Or
{this.state.Citys === null ? <SomeOtherComponent/Element/EmptyFragment/etc.. /> : this.state.Citys.map((ele ,index) =>
<option value={`${ele}`} className="d-inline mx-2 col-sm-5" onClick={({target}) => target.classList.toggle('bg-primary')}>
{ele.name}
</option>
)}
In these examples, the map function won't be called if state.Citys is null.

Related

How to store Key-Value pairs in a list or library in React JS

I want to store unique key and value in my class component state(selectedFeatures) as a list of key-value pairs, by taking the arguments which are passed from the body. ex: {“user1”:"opt1", “user2”:"opt3"}. The key must be unique, which means if the same key received from the body value should be updated to the relevant key which is stored previously
I did it in this way and it gives an error as “this.state.selectedFeatures is not iterable”. Therefore how to resolve this.
import React, { Component} from 'react';
import {features} from '../../services';
class UserData extends Component {
constructor(props) {
super(props)
this.state = {
featureTypes:[],
selectedFeatures:{}
}
}
getSelectedFeatures =(event,keyValue)=>{
const features = {};
features[keyValue] = event.target.value
this.setState({selectedFeatures: features})
}
componentDidMount(){
//http request from service component
features().then(response => {
this.setState({featureTypes:response})
})
.catch(error => {
console.log(error)
})
}
render() {
const {featureTypes} = this.state
return (
<div>
{featureTypes.map((feature, i) => (
<div key={i}>
<label>
<h4>
{feature.feature}
</h4>
</label>
<select
defaultValue="select-feature"
onChange={(event) => this.getSelectedFeatures(event, feature.feature)}>
{feature.types.map((type, i) => (
<option key={i} value={type}>
{type}
</option>
))}
</select>
</div>
))}
</div>
)
}
}
export default UserData
Without creating an array just simply adding to an object can be achieved via this method
addTo = (event, val) => {
this.setState((prev) => ({
selectedFeatures: {
...prev.selectedFeatures,
[event.target.value]: val
}
}));
};
NOTE I changed the name from getSelectedFeatures to addTo from simplicity + because get would mean it returns something. In this case, you send and add it.
the prev is a previews state that was before the state change.
Also I went a step further and created a demo project https://codesandbox.io/s/flamboyant-lake-4rvfz?file=/src/App.js
Inside of it there are multiple different containers that you may click and it will save what has been click as a event.target and a value they themself send to the method. If the container is clicked again OR a different but same type container is clicked, the value is overriten rather then added as a new parameter. Its constructed using class, as in your code. This is a simple quick demo that, after analizing, you may adapt to it as you wish with any code you want.

Render child component in parent after re-rendering sibling component

I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}

React / TypeError: files.map is not a function

I am trying to upload some files via <input type="file"> to the state, in order to pass it back to the main component. With the code below I get the Uncaught TypeError: Cannot read property 'setState' of undefined when trying to update the state with the uploaded files.
import React, { Component } from 'react'
import * as RB from 'react-bootstrap'
import Button from 'components/Button/Button'
class uploadMob extends Component {
constructor(props) {
super(props)
this.state = {
files: [],
}
}
onFilesAdded(files) {
this.setState((prevState) => ({
files: prevState.files.concat(files),
}))
this.handleUpload()
}
handleUpload = (e) => {
const { pdfUploadToState } = this.props
debugger
pdfUploadToState(this.state.files)
console.log('PUSHED FILE', this.state.files)
}
render() {
const files = this.state.files
return (
<RB.Form.Group>
<div className="upload-btn-wrapper">
<div className="Files">
{files.map((file, key) => {
return (
<div key={key} className="Row">
<span className="Filename">
{file.value}
</span>
</div>
)
})}
</div>
<Button size="sm" variant="light">
Dateien hochladen
</Button>
<input
type="file"
name="file"
id="files"
onChange={this.onFilesAdded}
/>
</div>
</RB.Form.Group>
)
}
}
export default uploadMob
I would very much appreciate the help with this. It is driving me a bit crazy..
The problem is with this line:
onFilesAdded(files) {
You need to either bind() it to this like this:
constructor(props) {
super(props)
this.state = {
files: [],
};
this.onFilesAdded = this.onFilesAdded.bind(this);
}
or convert it to an arrow function:
onFilesAdded = files => {
The problem is this referred inside onFilesAdded does not point the component instance by default. By using the two methods above, we make sure that by calling this., the component is correctly referred.
You need to use an arrow function to have the correct value of this.
setState is asynchronous so you need to pass the this.handleUpload function as a callback in order to updload the files once setState is completed.
From the documentation :
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
onFilesAdded = files => {
this.setState(prevState => ({
files: [...prevState.files, ...files]
}), this.handleUpdload)
}

Passing Events in ReactJS Form

I'm trying to make a component with React and I have 2 classes ( the App class that has the state and the ImageType class that has a dropdown using select and option).
I want to change the state in the App when I make a selection in the ImageType class but I get the error (out.js:6 Uncaught TypeError: Cannot read property 'value' of undefined).
I know that I'm doing something wrong but I can't realize what.
I want the "value" of the option tag to became the new value of this.state.field
Thank you
class ImageType extends React.Component{
onDone=()=>{
if(typeof this.props.done === "function"){
this.props.done(this.props.imageType)
}
}
render(){
return(
<div>
<select value={this.props.imageType} onChange={this.onDone}>
<option value="illustration">Illustration</option>
<option value="vector">Vector</option>
</select>
</div>
)
}
}
class App extends React.Component{
state={
field:""
}
done = (event) =>{
this.setState({
field: event.target.value
})
}
render(){
return(
<div>
<ImageType imageType={this.state.field} done={this.done}/>
</div>
)
}
}
In ImageComponent you have to pass event in onDone function:
// replaced brackets with event
onDone = event => {
if(typeof this.props.done === "function"){
this.props.done(event)
}
}
#Spartacus are right, since this.props.done function accepts a event parameter, you shouldn't pass a this.props.imageType which is not event type.
Or you can just pass the selected image type to the callback in the App component.
ImageType component
onDone = event => {
if (typeof this.props.done === "function") {
this.props.done(event.target.value);
}};
pass the selected value to the callback function in App component,
App component
done = imageType => {
this.setState({
field: imageType
});};
check the demo here

When is the render() called in a class that extends Component?

What refreshes the view in react or is the code always live displayed?
I have a function called removeAdmin and makeAdmin which adds and removes users as Admins and then when a user is an admin the render of Member component renders and admin shield logo. It works fine but I'm wondering whether render is being triggered each time I change the UI using a function or if render is live listening to changes in it's components?
class MemberList extends Component {
constructor(props) {
super(props)
this.state = {
members: [],
loading: false,
administrators: []
}
this.makeAdmin = this.makeAdmin.bind(this)
this.removeAdmin = this.removeAdmin.bind(this)
}
componentDidMount(){
this.setState({loading:true})
fetch('https://api.randomuser.me/?nat=US&results=12')
.then(response => response.json())
.then(json => json.results)
.then(members => this.setState({
members,
loading:false
}))
}
makeAdmin(email){
const administrators = [
...this.state.administrators,
email
]
this.setState({administrators})
}
removeAdmin(email){
const administrators = this.state.administrators.filter(
adminEmail => adminEmail !== email
)
this.setState({administrators})
}
render() {
const { members, loading } = this.state
return (
<div className="member-list">
<h1>Society Members</h1>
{
(loading) ?
<span> loading...</span>:
<span>{members.length} members</span>
}
{ (members.length)?
members.map(
(member, i) =>
<Member key={i}
// This admin prop is worked out by enumerating through the administrator
// array with some(). some() passes in the enumerators, checking whether
// the current member in members.map() exists in the administrators array
// and returns admin=true if so.
admin={this.state.administrators.some(
adminEmail => adminEmail === member.email
)}
name={member.name.first + '' + member.name.last}
email={member.email}
thumbnail={member.picture.thumbnail}
makeAdmin={this.makeAdmin}
removeAdmin={this.removeAdmin}/>
):
<span>Currently 0 members</span>
}
</div>
)
and the Member component:
class Member extends Component {
componentWillMount(){
this.style={
backgroundColor: 'grey'
}
}
render() {
const { name, thumbnail, email, admin, makeAdmin, removeAdmin } = this.props
return (
<div className="member" style={this.style}>
<h1>{ name } {(admin) ? <FaShield/> : null}</h1>
<div>
<img src={ thumbnail }/>
</div>
<div>
{
(admin)?
<Button title="Make Admin" onClick={() => removeAdmin(email) } color="#841584"> Remove Admin </Button>
:
<Button title="Make Admin" onClick={ () => makeAdmin(email) } color="#841584"> Make Admin </Button>
}
<a href={`mailto:${ email }`}><p> {email} </p></a>
</div>
</div>
)
}
}
export default Member
What triggers a new render on components is when the state changes or when receiving new properties.
There are two main objects that drive the render in each component, the this.props and the this.state. If any of this objects gets updated then the render method gets executed.
The this.props object gets updated whenever you send new properties to the childrens. this.state gets updated using the this.setState method.
That being said, it's really important to keep track of the properties you send to the children, as a rule of thumb I always recommend not using the spread operator to pass props to the children, for example:
<Parent>
<Child {...this.props} />
</Parent>
I'd avoid that pattern because if any of the props changes, than all props are sent to the child. Instead I recommend sending only what the children needs.
<Parent>
<Child some={this.props.value} />
</Parent>
You need to be very careful when you need to render your component, otherwise it's so easy to re-render everything! Which will lead to performance issues.
It depends on what you define your component render method to be.
It can change based on the state or props that you give it.
Less frequent, but you can check out shouldComponentUpdate as it allows you to overwrite the method to give it more “smarts” if you need the performance boost.

Resources