pass props into another component in react js - reactjs

I am trying to pass props.userId to another component and console.log it in PersonalDetail component. I am not able to pass it.
It says undefined props.userId has values of integers
constructor(props) {
super(props);
var id = props.userId;
this.state = { isClose: false, userid1: { id } };
}
<PersonalDetail userid1={this.state.userid1} />
persoalnal.js
import { useState } from "react";
export default function PersonalDetail(props) {
var userid = props.userid1;
console.log(props.userid1);
return (
<>
<h1>personal details</h1>
</>
);
}

You should check the upper component props userId's value.
constructor(props) {
super(props);
console.log(props.userId);
this.state = {
isClose: false,
userid1: { id: props.userId },
};
}
<PersonalDetail userid1={this.state.userid1.id} />

Related

Child component not triggering rendering when parent injects props with differnt values

Here are my components:
App component:
import logo from './logo.svg';
import {Component} from 'react';
import './App.css';
import {MonsterCardList} from './components/monster-list/monster-card-list.component'
import {Search} from './components/search/search.component'
class App extends Component
{
constructor()
{
super();
this.state = {searchText:""}
}
render()
{
console.log("repainting App component");
return (
<div className="App">
<main>
<h1 className="app-title">Monster List</h1>
<Search callback={this._searchChanged}></Search>
<MonsterCardList filter={this.state.searchText}></MonsterCardList>
</main>
</div>
);
}
_searchChanged(newText)
{
console.log("Setting state. new text: "+newText);
this.setState({searchText:newText}, () => console.log(this.state));
}
}
export default App;
Card List component:
export class MonsterCardList extends Component
{
constructor(props)
{
super(props);
this.state = {data:[]};
}
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
_loadData(monsterCardCount)
{
fetch("https://jsonplaceholder.typicode.com/users", {
method: 'GET',
}).then( response =>{
if(response.ok)
{
console.log(response.status);
response.json().then(data => {
let convertedData = data.map( ( el, index) => {
return {url:`https://robohash.org/${index}.png?size=100x100`, name:el.name, email:el.email}
});
console.log(convertedData);
this.setState({data:convertedData});
});
}
else
console.log("Error: "+response.status+" -> "+response.statusText);
/*let data = response.json().value;
*/
}).catch(e => {
console.log("Error: "+e);
});
}
render()
{
console.log("filter:" + this.props.filter);
return (
<div className="monster-card-list">
{this.state.data.map((element,index) => {
if(!this.props.filter || element.email.includes(this.props.filter))
return <MonsterCard cardData={element} key={index}></MonsterCard>;
})}
</div>
);
}
}
Card component:
import {Component} from "react"
import './monster-card.component.css'
export class MonsterCard extends Component
{
constructor(props)
{
super(props);
}
render()
{
return (
<div className="monster-card">
<img className="monster-card-img" src={this.props.cardData.url}></img>
<h3 className="monster-card-name">{this.props.cardData.name}</h3>
<h3 className="monster-card-email">{this.props.cardData.email}</h3>
</div>
);
}
}
Search component:
import {Component} from "react"
export class Search extends Component
{
_searchChangedCallback = null;
constructor(props)
{
super();
this._searchChangedCallback = props.callback;
}
render()
{
return (
<input type="search" onChange={e=>this._searchChangedCallback(e.target.value)} placeholder="Search monsters"></input>
);
}
}
The problem is that I see how the text typed in the input flows to the App component correctly and the callback is called but, when the state is changed in the _searchChanged, the MonsterCardList seems not to re-render.
I saw you are using state filter in MonsterCardList component: filter:this.props.searchText.But you only pass a prop filter (filter={this.state.searchText}) in this component. So props searchTextis undefined.
I saw you don't need to use state filter. Replace this.state.filter by this.props.filter
_loadData will get called only once when the component is mounted for the first time in below code,
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
when you set state inside the constructor means it also sets this.state.filter for once. And state does not change when searchText props change and due to that no rerendering.
constructor(props)
{
super(props);
this.state = {data:[], filter:this.props.searchText};
}
If you need to rerender when props changes, use componentDidUpdate lifecycle hook
componentDidUpdate(prevProps)
{
if (this.props.searchText !== prevProps.searchText)
{
this._loadData();
}
}
Well, in the end I found what was happening. It wasn't a react related problem but a javascript one and it was related to this not been bound to App class inside the _searchChanged function.
I we bind it like this in the constructor:
this._searchChanged = this._searchChanged.bind(this);
or we just use and arrow function:
_searchChanged = (newText) =>
{
console.log("Setting state. new text: "+newText);
this.setState({filter:newText}, () => console.log(this.state));
}
Everything works as expected.

Is there any signal that parent component's state set and then child component's state set too?

when parent component set state(itemSelected: item) i want child component set state(isShowForm: true) too, so is there any signal or condition let me do that thing?
<pre>
//this is child Component
class HeaderComponent extends Component {
constructor(props) {
super(props);
this.state = {
isShowForm: false,
};
}
handleEdit = () =>{
if(any signal?){
this.setState({isShowForm:true})
}
}
export default HeaderComponent;
//this is parent Component
class Task extends Component {
constructor(props){
super(props);
this.state = {
itemSelected: null,
}
}
handleEdit = (item) => {
this.setState({itemSelected: item})
}
render() {
let {itemSelected} = this.state;
return(
HeaderComponent itemSelected={itemSelected}/>
)
</pre>
You can pass the required state from parent component to child and use componentDidUpdate lifecycle in child to listen to the prop and react accordingly.
class HeaderComponent extends Component {
constructor(props) {
super(props);
this.state = {
isShowForm: false,
};
}
componentDidUpdate(prevProps) =>{
// Any identifying property to see if the itemSelected object has indeed changed. I'm just assuming that it has a unique ID
if(prevProps.itemSelected && prevProps.itemSelected.id !== this.props.itemSelected.id) {
this.setState({ isShowForm: true })
}
}
export default HeaderComponent;
//this is parent Component
class Task extends Component {
constructor(props){
super(props);
this.state = {
itemSelected: null,
}
}
handleEdit = (item) => {
this.setState({itemSelected: item})
}
render() {
let {itemSelected} = this.state;
return(
<HeaderComponent itemSelected={itemSelected}/>
)

Passing an object as props and receiving it

I have a component Data and its child component BarChart.
Data component looks as following:
export default class Data extends Component {
constructor(props) {
super(props);
this.state = {
data: {
labels: [],
datasets: [{
label: "",
data: [],
backgroundColor: ''
}]
}
}
}
componentWillMount() {
this.geData();
}
geData = () => {
let labelsData = someContent;
let datasets = otherContentl;
this.setState({data: {...this.state.data, labels: labelsData, datasets: datasets}}, ()=>{console.log(this.state.data)});
}
render(){
return (
<BarChart data={this.state.data} />
);
}
}
When I check the result of console.log(this.state.data) in getData function, it prints out the correct data.
However, when I receive the props in BarChart component, I only receive datasets key filled with the correct data, but labels key is an empty array.
export default class BarChart extends Component {
constructor(props) {
super(props);
console.log(props);
}
componentWillMount() {
this.setState({chartData: this.props.data});
}
render() {
return (
<div className="barChart">
<Bar
data={this.state.chartData}
/>
</div>
);
}
}
Why does that happen? How can it be fixed?
What I had in BarChart component is:
constructor(props) {
super(props);
this.state = {
chartData: {}
}
}
componentWillMount() {
this.setState({chartData: this.props.data});
}
render() {
return (
<div className="barChart">
<Bar
data={this.state.chartData}
/>
</div>
);
}
What I changed is receiving the props immediately and using it, instead of receiving it in state or componentWillMount:
export default class BarChart extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentWillMount() { }
componentDidMount() { }
render() {
return (
<div className="barChart">
<Bar
data={this.props.data}
/>
</div>
)
}
}
So, whenever the props is changed, the component will re-render.

componentDidMount not called when Route with a different param is called

i'm rendering "Details" component in a callback in my UsersListContainer like this:
class UsersListContainer extends Component {
goToUserById(id) {
if (!id) { return false; }
this.props.history.push(`/users/${id}`);
}
render() {
return (
<UserList
goToUser={(id) => this.goToUserById(id)}/>
);
}
}
My "Details" container:
class UserDetailsContainer extends Component {
componentDidMount() {
this.props.getUserDetails(this.props.match.params.id);
}
render() {
return (
<UserDetails user={this.props.selectedUser}/>
);
}
}
const mapDispatchToProps = dispatch => {
return {
getUserDetails: id => dispatch(getUser(id))
};
};
const mapStateToProps = (state) => ({
selectedUser: state.user.selectedUser
});
And in my presentational "User" component I display a set of data from redux store like this:
class UserDetails extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.user.name,
address: this.props.user.name,
workingHours: this.props.user.workingHours,
phone: this.props.user.phone
};
}
I'm not displaying component props directly and I use state because they are meant to be edited. This works, but the problem is that all these props are not updating simultaneously with component load which means when I select user for the first time it displays the right info, then I switch back to "/users" to choose another, and his props remain the same as props of the previous user. I tried componentWillUnmount to clear the data but it didn't work
Solved this by using lodash lib
in my presentational component I compare if objects are equal
constructor(props) {
super(props);
this.state = {
name: "",
address: ""
};
}
componentWillReceiveProps(nextProps) {
_.isEqual(this.props.user, nextProps.user) ? (
this.setState({
name: this.props.user.name,
address: this.props.user.address
})
) : (
this.setState({
name: nextProps.user.name,
address: nextProps.user.address,
})
)
}
When you implement a Route like /users/:id, and if you change the id to something else, the entire component is not re-mounted and hence the componentDidMount is not called, rather only the props change and hence you need to implement componentWillReceiveProps function also
componentDidMount() {
this.props.getUserDetails(this.props.match.params.id);
}
componentWillReceiveProps(nextProps) {
if(this.props.match.params.id !== nextProps.match.params.id) {
this.props.getUserDetails(nextProps.match.params.id);
}
}

Why component unmounts before a request is performed in React-Flux

I'm using React-Flux to build an app. I have a parent component A, a child component B and another component C which is called simultaneously both by A and B.
The thing is that when C is called directly from A data is mounted, run and unmounted correctly. When the same component is called from B, component is mounted and unmounted, but GET requests are performed after the component unmounts. Why does this happen?
This is the C component
class Nameday extends Component {
constructor(){
super();
this.state = {
saints: AppStore.getNames(),
}
this.onChange = this.onChange.bind(this);
}
componentWillMount(){
AppStore.addChangeListener(this.onChange);
}
componentDidMount(){
AppActions.getNames();
}
onChange(){
this.setState({
saints: AppStore.getNames(),
});
}
componentWillUnmount(){
AppStore.removeChangeListener(this.onChange);
}
render() {
let dates = [];
this.state.saints.map((saint, i) => {
saint.names.map((name, j) => {
if (name === firstName) {
dates.push(saint.date);
}
return dates;
});
return null;
});
let option = ....
return (
option
);
}
}
When the Nameday component is called from B component, the onChange function isn't performed at all.
This is the parent component:
class ParentListItem extends Component {
constructor(props){
super(props);
this.state = {
parent: this.props.parent,
}
this.onNamedayChange = this.onNamedayChange.bind(this);
}
onNamedayChange(date, id, parent, index){
let newParent = {
firstName: this.props.parent.firstName,
lastName: this.props.parent.lastName,
phone: this.props.parent.phone,
email: this.props.parent.email,
birthday: this.props.parent.birthday,
nameday: {
nameday_id: id,
date: date,
},
};
AppActions.updateParent(this.props.parent._id.$oid,
update(this.props.parent, {$merge: newParent}));
}
render() {
const {parent} = this.props;
return (
<div>
<ListGroup>
<ListGroupItem><img src="images/phone.png" className='glyph' width='35px' role="presentation"/> {parent.phone}</ListGroupItem>
<ListGroupItem><img src="images/mail-ru.png" className='glyph' width='35px' role="presentation"/> {parent.email}</ListGroupItem>
<ListGroupItem><img src="images/cake-layered.png" className='glyph' width='35px' role="presentation"/> {parent.birthday}</ListGroupItem>
//this is the component called from both ParentListItem.js and ConnectionListItem.js
<Nameday name={parent.firstName} callbackNamedayParent={this.onNamedayChange} date={parent.nameday.date} dateId={parent.nameday.nameday_id} onList={true} child={false}/>
</ListGroup>
<div>
//this is the child component
<ConnectionListItem key={connection.id} connection={connection} index={parent.connections.indexOf(connection)} parent={parent} id={parent._id.$oid} callbackParent={this.onChildChanged}/>
</div>
</div>
);
}
}
export default ParentListItem;

Resources