Passing data and events between siblings in React - reactjs

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>
);
}
}

Related

Why is my class component not rendering correctly?

I've been at this awhile and I can't get my head around it. I feel so stupid. Can anyone tell me what's wrong?
The console log works currently, and the console.log of the Object states that the state has been updated from false to true.
class ChoiceBar extends React.Component {
constructor() {
super();
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
handleFirstQuestion() {
console.log("Start Rendering First Question")
this.setState({handleFirstQuestion: true}, () => {console.log(this.state);
});
}
render() {
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion.bind(this)}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
}
}
The first thing is that you are binding the function twice, one in constructor and one in onClick event handler.
Second pass props component in constructor and super
constructor(props) {
super(props);
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
I don't think you are exporting the class component
write this after the class component
export default ChoiceBar;
you should probably use aero functions and useState hook instead of the class component
for example:
const ChoiceBar = () => {
const [handleFirstQuestion, setHandleFirstQuestion] = useState(false)
}

the Cardlist component is not getting called upon changing state

I'm new to react js and is having some problem in my code. I have a Cardlist component which is returning my contact list layout when i pass my contactlist into it. I added a searchbox to search for a particular contact. The list updated upon entering some text in search box (I logged it out in console and it is working finely). The render and return methods are also working well upon changing search text ( i.e they are getting called everytime after changing state, I logged them out) but the Cardlist component is not getting called again. It still shows the same old list. The console.log inside this cardlist component is also not logging anything which simply implies it is not called again.
Here is the main component (not whole code shown, only the necessary part) :
constructor(props){
super(props);
this.state = {
friendslist: this.props.data.friendslist,
searchfield:''
}
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
}
render(){
var filterfriendslist = this.state.friendslist.filter(friendslistitem => {
return friendslistitem.name.toLowerCase().includes(this.state.searchfield.toLowerCase())
});
{console.log( filterfriendslist )}
return(
<div>
<input id="name" onChange={this.onSearchChange} className="input-reset ba b--black-20 pa2 mv2 db w-100 bg-near-white" type="text" placeholder='Search' />
<Scroll>
<div>
{console.log( filterfriendslist )}
<Cardlist friendlist={ filterfriendslist } loadChattingUser={ this.loadChattingUser } />
</div>
</Scroll>
</div>
);
}
}
export default Contacts;
Here is the Carlist component :
import React from 'react';
import Card from './Card';
class Cardlist extends React.Component {
constructor(props){
super(props);
console.log("Clicked");
}
cardComponent = this.props.friendlist.map((user, i) => {
return <Card key={i} id={this.props.friendlist[i].id} name = {this.props.friendlist[i].name} imageURL={this.props.friendlist[i].imageurl} email={this.props.friendlist[i].email} msgDatabase={ this.props.friendlist[i].msgdata } loadChattingUser={ this.props.loadChattingUser } />
})
render(){
return (
<div>
{this.cardComponent}
</div>
);
}
}
export default Cardlist;
Here is the card component :
import React from 'react';
class Card extends React.Component {
constructor(props){
super(props);
this.state = {
name: this.props.name,
imageURL: this.props.imageURL,
email: this.props.email,
msgDatabase: this.props.msgDatabase
}
}
fillChat = () => {
this.props.loadChattingUser(this.state);
}
render(){
return (
<div className="dt w-100 bb b--black-05 pb2 mt2 pa2 bg-near-white pointer" onClick={ this.fillChat }>
<div className="dtc w2 w3-ns v-mid">
<img alt="Profile" src={this.props.imageURL} className="ba b--black-10 db br-100 w2 w3-ns h2 h3-ns"/>
</div>
<div className="dtc v-mid pl3">
<h1 className="f6 f5-ns fw6 lh-title black mv0">{this.props.name}</h1>
<h2 className="f6 fw4 mt0 mb0 black-60">{this.props.email}</h2>
</div>
<div className="dtc v-mid">
<form className="w-100 tr">
<button className="f6 button-reset bg-white ba b--black-10 dim pointer pv1 black-60" type="submit">+ Follow</button>
</form>
</div>
</div>
);
}
}
export default Card;
I want the Cardlist layout to change according to input in the search but it remain same as the initial list
The issue on your implementation of Cardlist. In your implementation cardComponent field is precomputed field. It supposed to be like this:
class Cardlist extends React.Component {
constructor(props) {
super(props);
console.log("Clicked");
}
render() {
const cardComponent = this.props.friendlist.map((user, i) => {
return (
<Card
key={i}
id={this.props.friendlist[i].id}
name={this.props.friendlist[i].name}
imageURL={this.props.friendlist[i].imageurl}
email={this.props.friendlist[i].email}
msgDatabase={this.props.friendlist[i].msgdata}
loadChattingUser={this.props.loadChattingUser}
/>
);
});
return <div>{cardComponent}</div>;
}
}
Here is codesandbox link: https://codesandbox.io/s/chatter-zdljq

Passing props / state back to parent component in React JS

I have two components, a select list with several options and a button component
What I'd like to do in the UI is that the user can select any option from the select list they want and then press the button to reset the list to 'select'
I'm keeping the parent component as the one source of truth - all updated states are passed back to the parent component, I've actually got this working great in the context of one file with the following code;
class SelectList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onSelectListChange(e.target.value);
}
render() {
const selectedValue = this.props.selectedValue;
log.debug('SelectListValue(): ', this.props.selectedValue);
return (
<select value={selectedValue} onChange={this.handleChange}>
<option value='One'>One</option>
<option value='select'>select</option>
<option value='Three'>Three</option>
<option value='Four'>Four</option>
<option value='Five'>Five</option>
</select>
);
}
}
class SelectListReset extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onResetChange('select');
}
render() {
return (
<div>
<button onClick={this.handleChange}>Reset list to select</button>
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state={
selectedValue: 'select'
}
this.handleSelectedListChange = this.handleSelectedListChange.bind(this);
this.handleResetChange = this.handleResetChange.bind(this);
}
handleSelectedListChange(selectedValue) {
this.setState({selectedValue});
}
handleResetChange() {
this.setState({selectedValue: 'select'});
}
render() {
log.debug('render(): ', this.props);
log.debug('ParentListValue(): ', this.state.selectedValue);
return (
<div className="content-area">
<div className="container">
<h1>{LANGUAGES_CONTROLLER.format(this.props.languages, 'TitleSettings')}</h1>
<div>
<SelectList
onSelectListChange={this.handleSelectedListChange}
selectedValue={this.state.selectedValue}
/>
<SelectListReset
onResetChange={this.handleResetChange}
selectedValue={this.state.selectedValue}
/>
</div>
</div>
</div>
);
}
But what I've actually like to do is move the reset button to it's own file and this is where I fall over trying to pass the props / state back to the parent.
So the render method would actually look like this
render() {
log.debug('render(): ', this.props);
log.debug('ParentListValue(): ', this.state.selectedValue);
return (
<div className="content-area">
<div className="container">
<h1>{LANGUAGES_CONTROLLER.format(this.props.languages, 'TitleSettings')}</h1>
<div>
<SelectList
onSelectListChange={this.handleSelectedListChange}
selectedValue={this.state.selectedValue}
/>
<TestComponent
onResetChange={this.handleResetChange}
selectedValue={this.state.selectedValue}
/>
</div>
</div>
</div>
);
}
I import TestComponent and then inside of TestComponent is where I will have my code for the SelectListReset component but the problem I'm having is that now the values are sent to it as props which should be immutable right so can't be updated?
That's where my understand stops .. if someone can point me in the right direction on this I'd be very grateful!
Props received from a parent will change if the relevant state in the parent is changed. There's no problem with this and all the re-rendering is handled by React.
You can pass props down as many levels as you like.

How to remove an element in reactjs by an attribute?

I want to get unknown key attribute using known ID so that i may delete corresponding div.
I tried using document.getElementById("a").getAttribute('key'); , but it isn't working. May be my concept is wrong.
class PostAdded extends Component {
constructor(props) {
super();
this.deletepost = this.deletepost.bind(this);
}
deletepost() {
let ab =document.getElementById("a").getAttribute('key');
console.log(ab)
}
render() {
return (
<div>
{ this.props.posts.map((post, i) =>
<div id="a" key={`i-${post.title}`}>
<span> <h3>{post.title}</h3><p>{post.post}</p></span>
<input type="button" value="Delete" onClick={this.deletepost}/>
</div>
) }
</div>
)
}
}
export default PostAdded;
If you were able to delete the div, that probably wouldn't end up working for you anyway because any state change would cause a re-render and it would appear again. Instead, you could keep track of your posts in state and then remove one of the posts from state in your deletepost method.
class PostAdded extends Component {
constructor(props) {
super();
this.state = {
posts: props.posts
}
this.deletepost = this.deletepost.bind(this);
}
deletepost(index) {
const newPosts = this.state.posts.splice(index)
this.setState({posts: newPosts})
}
render() {
return (
<div>
{ this.state.posts.map((post, i) =>
<div id="a" key={`i-${post.title}`}>
<span> <h3>{post.title}</h3><p>{post.post}</p></span>
<input type="button" value="Delete" onClick={() => this.deletepost(i)}/>
</div>
) }
</div>
)
}
}
export default PostAdded;

React component doesn't get rendered on initial load

I recently started learning React and I'm trying to make my simple app to work.
I have main App component:
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<header>
<h1>Test</h1>
</header>
<nav className="navbar navbar-default">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse-1" aria-expanded="false">
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
</div>
<div className="collapse navbar-collapse" id="navbar-collapse-1">
<ul className="nav navbar-nav">
<li><NavLink to="/vehiclemakes">Vehicle Makes</NavLink></li>
</ul>
</div>
</div>
</nav>
<div className="container">
<Route path="/vehiclemakes" component={VehicleMakes}/>
</div>
</div>
</BrowserRouter>
);
}
}
As you can see I'm using routing and in my navbar I have a link to the Vehicle Makes component which should render table with vehicle makes that I get with api call:
let vehicleMakes = [];
class VehicleMakes extends React.Component{
componentDidMount(){
Axios.get(`http://localhost:15163/api/vehiclemakes`)
.then((result) => {
const vehicleMakesData = result;
vehicleMakes = vehicleMakesData.data.data;
})
}
render(){
return(
<div>
<VehicleMakesTable vehicleMakes={vehicleMakes} />
</div>
);
}
}
VehicleMakesTable component renders VehicleMakesTableHeader and VehicleMakesTableRow components:
class VehicleMakesTable extends React.Component{
render(){
console.log("table");
const rows = [];
this.props.vehicleMakes.forEach((vehicleMake) => {
rows.push(
<VehicleMakesTableRow
vehicleMake={vehicleMake}
key={vehicleMake.id} />
);
});
return(
<table>
<thead>
<VehicleMakesTableHeader />
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class VehicleMakesTableHeader extends React.Component{
render(){
return(
<tr>
<th>Name</th>
</tr>
);
}
}
class VehicleMakesTableRow extends React.Component{
render(){
const vehicleMake = this.props.vehicleMake;
return(
<tr>
<td>{vehicleMake.name}</td>
</tr>
);
}
}
The problem that I have is that on initial load when I click Vehicle Makes link in main App component, my table gets rendered but without any rows. So "VehicleMakesTableHeader" component gets rendered but "VehicleMakesTableRow" component doesn't. When I click the link again (second time) then the "VehicleMakesTableRow" component gets rendered also.
I don't know what I'm doing wrong, why do I have to click link two times in order for table to be rendered correctly?
The problem is here:
let vehicleMakes = [];
class VehicleMakes extends React.Component{
componentDidMount(){
Axios.get(`http://localhost:15163/api/vehiclemakes`)
.then((result) => {
const vehicleMakesData = result;
vehicleMakes = vehicleMakesData.data.data;
})
}
render(){
return(
<div>
<VehicleMakesTable vehicleMakes={vehicleMakes} />
</div>
);
}
}
You keep vehicleMakes as the regular variable. React is not getting notified of any change with this variable and doesn't re-render. That's why you have something like a react state. Every time state changes - react will get notified that state has changed and do re-render. Fix:
class VehicleMakes extends React.Component{
constructor(props) {
super(props);
this.state = { vehicleMakes: [] }
}
componentDidMount(){
Axios.get(`http://localhost:15163/api/vehiclemakes`)
.then((result) => {
const vehicleMakes = result.data.data;
this.setState({ vehicleMakes })
})
}
render(){
return(
<div>
<VehicleMakesTable vehicleMakes={this.state.vehicleMakes} />
</div>
);
}
}
You need to store vehicleMakes in state in order to trigger a re-render when you update it in componentDidMount:
class VehicleMakes ... {
state = { vehicleMakes: [] }
...
.then(result => this.setState({ vehicleMakes: result.data.data }));
...
<VehicleMakesTable vehicleMakes={this.state.vehicleMakes} />

Resources