Get child components to talk to eachother - reactjs

I know this has been asked before however I've been playing around with this for awhile and I'm not getting it. I have two child components being used and connected by parent page. The first child component is a form and the second child component is a table containing a list of all the userProfiles. When I submit the form, I wand to tell the table component to run a function. How do I do this?
Path: Parent Component
export default class StudentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
<AddStudentForm
guardianUserProfiles={this.props.guardianUserProfiles}
/>
<StudentTable
studentUserProfiles={this.props.studentUserProfiles}
/>
</div>
);
}
}
Path: AddStudentForm
export default class AddStudentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<Form onSubmit={this.handleSubmit} className="mb-3">
<Input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
id="firstName"
placeholder="First name"
/>
<button type="submit" className="btn btn-primary">Save</button>
</Form>
);
}
}
Path: StudentTable
export default class StudentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.handleTableUpdate = this.handleTableUpdate.bind(this);
}
handleTableUpdate = () => (event) => {
// Do stuff
}
render() {
return (
<div>
<Table hover bordered responsive>
<thead>
<tr>
<th className="border-left border-top-0 border-right-0 pt-0">#</th>
<th className="border-left-0 border-top-0 border-right-0 pt-0">First name</th>
</tr>
</thead>
<tbody>
{this.state.userProfile.map((studentUserProfile, idx) => (
<tr>
<React.Fragment>
<th rowSpan={studentUserProfile.classes.length} className="border-left aling-double-row">{idx + 1}</th>
<td rowSpan={studentUserProfile.classes.length} className="aling-double-row">{studentUserProfile.firstName}</td>
</React.Fragment>
</tr>
))}
</tbody>
</Table>
</div>
);
}
}

Way 1:
You can achieve this either by using flux or redux
Way 2:
You can send a call back form child1(ie page with form) to parent and send it as a prop from parent to child2(ie page with table).

Related

props.names.map is not a function

I have a react component called App that contains 2 components: Form and Table. And both of them are controlled component.
In the form, there is an input element and a button.
The input element has an onChange attribute; whenever the value changes it changes the value in the App's state.
The button has an onClick attribute that is provided by the App component; Whenever the button is clicked, the state's firstNames (which is an array) will be added with the state value of firstname.
The problem is when I clicked the button, it will throw an error saying that I didn't pass in an array and that it doesn't have a map method, even though in the call back, the updated state does show an array.
Below is the code:
function Form(props) {
return (
<form>
<label>
Item: <input value={props.value} onChange={props.handleChange} />
</label>
<button onClick={props.handleClick}>Submit</button>
</form>
);
}
function Table(props) {
let firstNames = props.names.map((item, index) => {
return (
<tr key={index}>
<td>{item}</td>
</tr>
);
});
return (
<table>
<thead>
<tr>
<th>First Name</th>
</tr>
</thead>
<tbody>{firstNames}</tbody>
</table>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputField: "",
firstNames: ["Joey", "Chloe"],
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ inputField: event.target.value });
}
handleClick() {
this.setState(
{
firstNames: this.state.firstNames.push(this.state.inputField),
},
console.log(this.state.firstNames)
);
}
render() {
return (
<div>
<Form
value={this.state.inputField}
handleChange={this.handleChange}
handleClick={this.handleClick}
/>
<Table names={this.state.firstNames} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
push mutates the original array, but it returns the length of the updated array, therefor, after the initial push, your firstNames inside state will be a number, which doesn't have map
You shouldn't mutate state variables, you should create a new array instead when adding a new name, for example, like this:
this.setState({
firstNames: [...this.state.firstNames, this.state.inputField]
})
The full sample code would then look something like this:
function Form(props) {
return (
<form onSubmit={props.handleClick}>
<label>
Item: <input value={props.value} onChange={props.handleChange} />
</label>
<button>Submit</button>
</form>
);
}
function Table(props) {
let firstNames = props.names.map((item, index) => {
return (
<tr key={index}>
<td>{item}</td>
</tr>
);
});
return (
<table>
<thead>
<tr>
<th>First Name</th>
</tr>
</thead>
<tbody>{firstNames}</tbody>
</table>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputField: "",
firstNames: ["Joey", "Chloe"],
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ inputField: event.target.value });
}
handleClick( e ) {
event.preventDefault();
this.setState(
{
firstNames: [...this.state.firstNames, this.state.inputField],
inputField: ''
}, () => console.log(this.state.firstNames) );
}
render() {
return (
<div>
<Form
value={this.state.inputField}
handleChange={this.handleChange}
handleClick={this.handleClick}
/>
<Table names={this.state.firstNames} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Following 2 things I still updated in your code:
Added the type="button" so that a submit doesn't happen
<button type="button" onClick={props.handleClick}>Submit</button>
Changed the callback of the setState which you used for logging
this.setState({
firstNames: [...this.state.firstNames, this.state.inputField],
inputField: ''
}, () => console.log(this.state.firstNames) );
When you did it in your original way, the console.log would be trigger before you could be sure that the setState call has actually happened.
Another note perhaps could be that using index for keys can lead to problems afterwards (be it sorting or removing items), a key should be unique. In this code, it's merely a hint

TypeError: this.state.edit is not a function

I want to create update operation on ReactJS
first I set edit button as
export default class Viewcustomer extends React.Component{
constructor(props) {
super(props)
this.state = {
customers:[]
}
}
componentDidMount() { /* lifecycle method*/
axios.get(`http://localhost:5001/customers/customerView`)
.then(res => {
const customers = res.data;
this.setState({customers});
})
}
onChange = (e) => {
this.setState(
{[e.target.name]: e.target.value}
)
}
edit=personId=>{
console.log(personId);
}
render(){
return(
<div>
<br/><br/>
<div className='container' style={container}>
<h1 style={h1}>Customer Details</h1>
<div className='col-md-12' style={colmd12}>
<br/><br/>
<div className="tbl-header" style={tblheader}>
<table className="table" style ={table} >
<thead className='thead' >
<tr className='tr' >
<th >Id</th>
<th>name</th>
<th>NIC</th>
<th>type</th>
<th>Delete</th>
<th>Update</th>
</tr>
</thead>
</table>
</div>
<div className="tbl-content" style={tblcontent}>
<table className="table" style ={table} >
<tbody>
{ this.state.customers.map(person =>
<tr className='td' style={td}>
<td>{person.Id}</td>
<td>{person.name}</td>
<td>{person.NIC}</td>
<td>{person.type}</td>
<td><Link to="update"><i class="fa fa-trash-o" style={iconstyle}></i></Link></td>
<td><Link to="update"><i class="fa fa-file" style={iconstyle} onClick={()=>this.state.edit(person.Id)}></i></Link></td>
</tr>)}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}
}
the update icon routes to Updatecustomer.jsx file.
then I set the edit fuction on Update.jsx file
import React from "react";
export default class Updatecustomer extends React.Component{
constructor(props){
super(props)
this.state={
update:[]
}
}
onChange = (e) => {
this.setState(
{[e.target.name]: e.target.value}
)
}
edit=personId=>{
console.log(personId);
}
render(){
return(
<div>
</div>
)
}
}
then my browser gives the following error: (TypeError: this.state.edit is not a function)
It is very big help if you have some ideas to fix this.
The edit function is not part of your state. Use onClick={()=>this.edit(person.Id)} instead.
Just remove state part and try
this.edit(person.id)

Passing data and events between siblings in React

I'm trying to pass data from a search component to a result component. The idea is that the input from the search field in the search component will be sent to the result component and used as a parameter for an API-call when the search button is clicked.
I've based the data-flow structure on this article: https://codeburst.io/no-redux-strategy-for-siblings-communication-3db543538959, but I'm new to React and it's a bit confusing.
I tried using the parameter by directly getting it from props like so vinmonopolet.searchProducts({this.props.data}, but I got a syntax error.
I'm also unclear on how one would go about directing onClick events from one component to another.
Parent
class App extends Component {
constructor(){
super();
this.state = { data: '' }
}
fromSearch(param){
this.setState({
data: param
});
}
render() {
return (
<div className="App">
<Navbar />
<Searchbar callback={this.fromSearch.bind(this)} />
<ResultTable data={this.state.data}/>
</div>
);
}
}
Child1
class Searchbar extends React.Component{
getContent(event){
this.props.callback(event.target.value);
}
Searchbar.protoTypes = {
callback: ProtoTypes.func
}
render(){
return(
<div className="search-container">
<div className="search-field">
<input type="text" placeholder="Hva leter du etter?"
onChange={this.getContent.bind(this)}/>
<button type="button" onClick={???}>Search</button>
</div>
...
Child2
class ResultTable extends React.Component{
constructor(){
super();
this.state = {products: []}
}
searchAllProducts(){
const vinmonopolet = require('vinmonopolet')
vinmonopolet.searchProducts({this.props.data}, {sort: ['price', 'asc']}).then(response => {
const data = response.products;
const listItems = data.map((d) =>
<tr key={d.name}>
<td>{d.productType}</td>
<td>{d.name}</td>
<td>{d.price}kr</td>
</tr>
);
this.setState({products: listItems});
})
}
render(){
if(!this.state.products) return <p>Henter data...</p>;
return(
<div className="result-container">
<div className="result-table-header">
<table>
<th>Type</th>
<th>Varenavn</th>
<th>Pris</th>
</table>
</div>
<div className="result-table-body">
<table className="result-table">
{this.state.products}
</table>
</div>
</div>
);
}
}

React JS table body component is not Updaing

I have these two simplified React components,
Parent component :- SecondaryLeader
Child component :- SecondaryLeaderDialog
In parent Component I'm loading the table and in child component i'm using to the insert form. when I inert the form table is not fetching the data. when i refresh the page only it's fetching.
without refreshing I want to load the table
Data is storing into the database but not updating the table.
I would appreciate the help in figuring out why table is not updaing, as expected, and how to fix the code so that it is.
Here is the code I am currently working with:
Parent component
import React from 'react';
class SecondaryLeader extends React.Component {
constructor(props) {
super(props);
this.state={
fixedHeader: true,
secondaryleader_details:'',
user_type: localStorage.getItem('type'),
api_token: localStorage.getItem('user_token'),
};
}
//This will update the states when component will receive new props
componentWillReceiveProps(props) {
let SecondaryLeader = props.SecondaryLeader;
this.setState({
secondaryleader_details:props.secondaryleader_details == null ? '' :props.secondaryleader_details,
});
}
render() {
const secondaryleader_details_list =this.state.secondaryleader_details;
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
</tr>
<tbody>
secondaryleader_details_list.map((item, index) => (
<tr>
<td>{item.secondaryleader} </td>
<td>{item.role_name} </td>
</tr>
))
</tboady>
</thead>
</table>
);
}
}
Child Component
class SecondaryLeaderDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
secondaryleader: '',
role_name: '',
api_token:localStorage.getItem('user_token'),
};
//This binds the context with the functions
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
const form_information = {
secondaryleader_details: {
secondaryleader: this.state.secondaryleader,
role_name: this.state.role_name,
},
api_token: this.state.api_token,
id: this.props.id,
};
self = this;
{/**
*This will call the add API of the form details
*/
}
APIS.add(form_information, 'conduct-session')
.then(function (response) {
if (response.statusCode == '0') {
self.setState({errors: response.message});
}else{
alert('Your Details Added successfully');
}
});
}
render() {
return(
<div
<form id="submit" onSubmit={this.handleSubmit}>
<label className="formset-label">Name</label>
<input type="text" name="secondaryleader" id="secondaryleader" value={this.state.secondaryleader} onChange={this.handleChange}>
<label className="formset-label">Role</label>
<input type="text" name="role_name" id="role_name" value={this.state.role_name} onChange={this.handleChange}>
<RaisedButton label="Save" primary onTouchTap={this.handleSubmit}/>
</form>
</div>
);
}
}

why is this.state.records.amount undefined?

I am in the process of learning React and having some trouble with my state. I am trying to get a function to log this.state.records.amount to my console when the component is rendered but it shows up as undefined. If someone can figure this out it would be VERY much appreciated.
Records component:
class Records extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
}
this.handleNewRecord = this.handleNewRecord.bind(this);
this.handleDeleteRecord = this.handleDeleteRecord.bind(this);
this.surplus = this.surplus.bind(this);
this.debt = this.debt.bind(this);
this.total = this.total.bind(this);
}
componentDidMount() {
this.setState({
records: this.props.records
})
}
handleNewRecord(record) {
let records = this.state.records.slice();
records.push(record)
this.setState({
records: records
})
}
handleDeleteRecord(record) {
let records = this.state.records.slice();
let index = records.indexOf(record)
records.splice(index, 1)
this.setState({
records: records
})
}
surplus() {
console.log(this.state.records[0].amount)
}
debt() {
console.log("debt")
}
total() {
console.log("total")
}
render() {
const records = this.state.records.map((record) =>
<Record record={record} key={record.id} handleDeleteRecord={this.handleDeleteRecord}/>
)
return (
<div className="container">
<h1>Records</h1>
<div className="row">
<AmountBox panelClass="panel panel-primary" panelHeader="Surplus" calculatedAmount={this.surplus()} />
<AmountBox panelClass="panel panel-warning" panelHeader="Debt" calculatedAmount={this.debt()} />
<AmountBox panelClass="panel panel-success" panelHeader="Total" calculatedAmount={this.total()} />
</div>
<RecordForm handleNewRecord={this.handleNewRecord}/>
<table className="table">
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{records}
</tbody>
</table>
</div>
)
}
}
Amount Box component:
class AmountBox extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div className="col-md-4">
<div className={this.props.panelClass}>
<div className="panel-heading">
<h3 className="panel-title">{this.props.panelHeader}</h3>
</div>
<div className="panel-body">
<p>
{this.props.calculatedAmount}
</p>
</div>
</div>
</div>
)
}
}
this.state.records[0].amount is undefined because on first render you are setting records to [] (in the constructor).
setState will trigger a second render, but in the first render the changes to state by setState will not apply.
So, you need some defensive code that makes sure that this.state.records have items.
surplus() {
this.state.records.length ? this.state.records[0].amount : 0;
}

Resources