I have one parent and one child component.
Child component is basically a navigation bar. While Parent renders the navigation bar.
I would like to change parent state when click on the children component.
Parent Constructor
constructor(props) {
super(props); // Must call
this.state = {show: "login", user: "guest"};
}
render() {
contents = <><Menu/><Activities /></>;
return (
contents
);
}
Child Component
constructor(props) {
super(props); // Must call
// a member variable called "state" to hold the state as a JS object
this.state = {show: "login", user: "guest",
};
render() {
contents = contents = <ul>
<li><a >Home</a></li>
<li><a >Activities</a></li>
<li><a >Membership</a></li>
</ul>;
return (
contents
);
}
As in React, the Data Flows Down, for the child component to change state of its parent, it can only be done via callback passed the parent.
You can pass the callback via props, Context API, or use any state management library.
For example:
class App extends Component {
state = { show: "login", user: "guest" };
render() {
return (
<>
<Menu />
<Activities onClick={this.setState} />
</>
);
}
}
class App extends Component {
state = { show: "login", user: "guest" };
render() {
return (
<ul>
<li onClick={() => this.props.onClick({ show: "Home" })}>
<a>Home</a>
</li>
<li onClick={() => this.props.onClick({ show: "Activities" })}>
<a>Activities</a>
</li>
</ul>
);
}
}
Related
Inside of my react application there are two components
Navbar
import React, { Component } from 'react';
import NavLink from './navlink';
class Navbar extends Component {
state = {
links: [
{
title: "Music",
active: false
},
{
title: "Home",
active: false
},
{
title: "Discord",
active: false
}
]
}
updateNavlinks = title => {
const links = this.state.links
for (const link in links){
if (links[link].title != title){
links[link].active=false;
}
else{
links[link].active=true;
}
}
console.log(links);
this.setState({links})
};
render() {
return (
<div id="Navbar">
{this.state.links.map(link => <NavLink key={link.title} title={link.title} active={link.active} onClickFunc={this.updateNavlinks}/>) }
</div>
);
}
}
export default Navbar;
Navlink
import React, { Component } from 'react';
class NavLink extends Component {
state = {
className: "navlink"+ (this.props.active?" active":"")
}
render() {
return (
<div className={this.state.className} onClick={() => this.props.onClickFunc(this.props.title)}>
{this.props.title}
</div>
);
}
}
export default NavLink;
My intention is to create a navbar where if the user selects a page, that <Navlink /> has its state changed. Once its state is changed (active=true), I want the classname to change, adding the "active" class and giving it the styles I want.
When updateNavlinks() is called, the state in <Navbar /> is changed, but it doesn't cause a visual change in the associated <Navlink />
Where did I go wrong with this? Is there a more simple way to accomplish this?
Here, you're mutating the existing state:
updateNavlinks = title => {
const links = this.state.links
for (const link in links){
if (links[link].title != title){
links[link].active=false;
}
else{
links[link].active=true;
}
}
console.log(links);
this.setState({links})
};
Never mutate state in React - that can make the script behave unpredictably. You need to call setState with a new object instead, so React knows to re-render:
updateNavlinks = titleToMakeActive => {
this.setState({
links: this.state.links.map(
({ title, active }) => ({ title, active: title === titleToMakeActive })
)
});
};
Another problem is that you're assigning state in the constructor of the child component in NavLink:
class NavLink extends Component {
state = {
className: "navlink"+ (this.props.active?" active":"")
}
render() {
return (
<div className={this.state.className} onClick={() => this.props.onClickFunc(this.props.title)}>
{this.props.title}
</div>
);
}
}
This assigns to the state own-property when the component is mounted, but the component doesn't get un-mounted; the instance doesn't change, so state doesn't get assigned to again, even when the props change.
To fix it, reference the props inside render instead of using state:
class NavLink extends Component {
render() {
return (
<div className={"navlink"+ (this.props.active?" active":"")} onClick={() => this.props.onClickFunc(this.props.title)}>
{this.props.title}
</div>
);
}
}
I have a page displaying user's books.
On this MyBooks page, React component mount. When it's mounted it fetch user's books through API. Then it update component's state with user's books.
mount component
fetch books through API
when we have results, update component's state
render again BooksList component (but it's not happening)
Here is my code for MyBooks component :
class MyBooks extends Component {
// TODO: fetch user info
constructor(props) {
super(props);
this.state = {
books: [],
errors: []
};
this.fetchBooks = this.fetchBooks.bind(this);
}
componentDidMount() {
console.log('component mounted!');
this.fetchBooks();
}
fetchBooks() {
let _this = this;
BooksLibraryApi.getBooks().then(foundBooks => {
console.log('books found:', foundBooks);
_this.setState({
books: foundBooks
});
});
}
render() {
console.log('MyBooks state:', this.state);
return (
<Section>
<Container>
<h1>My books</h1>
<BooksList books={this.state.books} />
</Container>
</Section>
);
}
}
export default withRouter(MyBooks);
Here is the result for console.log('books found:', foundBooks):
Here is my code for BooksList component :
class BooksList extends React.Component {
render() {
console.log('BooksList props:', this.props);
return (
<Columns breakpoint="mobile">
{this.props.books.map((book, i) => {
console.log(book);
return (
<Columns.Column
key={i}
mobile={{ size: 'half' }}
desktop={{ size: 2 }}
>
<BookCard book={book} />
</Columns.Column>
);
})}
</Columns>
);
}
}
export default BooksList;
Here is the code for BookCard component:
class BookCard extends React.Component {
constructor(props) {
super(props);
console.log('props', props);
this.readBook = this.readBook.bind(this);
this.addBook = this.addBook.bind(this);
this.deleteBook = this.deleteBook.bind(this);
this.wantBook = this.wantBook.bind(this);
}
readBook() {
BooksLibraryApi.readBook(this.props.book.id);
}
addBook() {
BooksLibraryApi.addBook(this.props.book.id);
}
wantBook() {
BooksLibraryApi.wantBook(this.props.book.id);
}
deleteBook(e) {
BooksLibraryApi.deleteBook(this.props.book.id, e);
}
render() {
return (
<div className="card-book">
<Link to={`/book/${this.props.book.id}`}>
{this.props.book.doHaveThumbnail ? (
<Image
alt="Cover"
src={this.props.book.thumbnailUrl}
size={'2by3'}
/>
) : (
<div className="placeholder">
<span>{this.props.book.title}</span>
</div>
)}
</Link>
<Button fullwidth color="primary" size="small" onClick={this.wantBook}>
Add to wishlist
</Button>
</div>
);
}
}
export default withRouter(BookCard);
The console.log in BooksList component is not called. Which means that the component is render only one time, when the this.props.books array is empty.
I don't understand why BooksList is not rendered again when his props are updated (when MyBooks component has his state updated).
Strange behavior: I'm using React Router, and when I first click on the link "My books" (which go to my MyBooks component), it doesn't work, but when I click again on it, everything works fine. Which means that something is wrong with rendering / component's lifecyles.
Thanks.
Here is the parent class
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
word: Words,
};
}
changeTheWord(i) {
this.state.word.changeWord(i);
}
render() {
return (
<div className="game">
<ul>
<li><a href="#" onClick={() => this.changeTheWord('hey')}>Home</a></li>
<li>News</li>
<li>Contact</li>
<li>About</li>
</ul>
<this.state.word />
</div>
);
}
}
And here is the child class
class Words extends React.Component {
constructor(props) {
super(props)
this.state = {
data: "read"
}
}
changeWord(i) {
this.state.data = i;
}
render() {
var sentence = "testing";
if (this.state.data != null) {
sentence = this.state.data;
}
return (
<div class="center">
<div class="words">
<p>{sentence}</p>
</div>
</div>
);
}
}
What I am trying to do, is call the child's changeWord method from the parent class Root, but for some reason it doesn't work and React gives me an error, TypeError: this.state.word.changeWord is not a function.
This it the line responsible for calling the function
<li><a href="#"onClick={ () => this.changeTheWord('hey')}>Home</a></li>
How do I approach this problem?
You are using React's logic somehow wrong. Why do you want to keep a whole React component (child here) in your state and mutate it with complex and confusing methods? React's logic is very simple and clean. Use state and props, render child components and pass those to it where necessary. Before going any further I strongly suggest reading the basic documentation.
Probably you want to do something like this.
class Parent extends React.Component {
constructor( props ) {
super( props );
this.state = {
data: "default sentence",
};
}
changeTheWord = ( i ) => {
this.setState( { data: i } );
}
render() {
return (
<div className="game">
<Child sentence={this.state.data} changeTheWord={this.changeTheWord} />
</div>
);
}
}
const Child = props => (
<div>
<ul>
<li>
<a href="#" onClick={() => props.changeTheWord( "hey" )}>
Home
</a>
</li>
<li>
News
</li>
<li>
Contact
</li>
<li>
About
</li>
</ul>
{props.sentence}
</div>
);
ReactDOM.render( <Parent />, document.getElementById( "root" ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
In your example, the word state property is initialized with the class Words, not an instance of it.
Instead, try initializing your state as follows:
this.state = {
word: new Words()
}
I have a dropdown menu by using Semantic-UI CSS Framework. I want to select an item on drowdown menu and know which item selected. I can know which is selected and set state in child component but I cannot send parent component. Actually I sent it by using callback function but it happened loop and exceed memory while setting parent's state. I followed this way for that.
My parent component is "SorguView" and also child component is "DropDownItem"
Thanks for helps.
Sorgu Class:
export class Sorgu {
_id:string;
userName:string;
anaSorgu:string;
aciklama:string;
sName:string;
constructor(id:string, username:string, anaSorgu:string, aciklama:string, sName:string) {
this._id = id;
this.userName = username;
this.anaSorgu = anaSorgu;
this.aciklama = aciklama;
this.sName=sName;
}
}
Interface SorguProps:
export interface SorguProps {
sorgu:Sorgu;
}
Interface SorguProps:
export interface SorguStates {
sorguList:Array<Sorgu>;
selectedName:string;
}
DropDownItem component (child):
class DropdownItem extends React.Component<SorguProps,SorguStates> {
constructor(props: SorguProps, context: any) {
super(props, context);
this.state = {
selectedName: 'no-data'
} as SorguStates;
this.calis = this.calis.bind(this);
}
calis = () => {
this.setState({selectedName: $('.item.active.selected').text()},() => console.log(""));
}
render() {
console.log("states",this.state);
console.log("props",this.props);
this.props.myFunc(this.state.selectedName);
return (
<div className="item" data-value={this.props.id} onClick={this.calis}>
{this.props.name}
</div>
);
}
}
SorguView (Parent):
export class SorguView extends React.Component<SorguProps,SorguStates> {
constructor(props: SorguProps, context: any) {
super(props, context);
this.state = {
sorguList: [],
selectedName:''
} as SorguStates;
this.hello=this.hello.bind(this);
}
hello(data){
console.log("data=>"+data);
//this.setState({selectedName: data} as SorguStates); //Exceed memory
console.log("=>>>>"+ this.state.selectedName);
}
render(){
return (
<div className="ui selection dropdown" ref="dropSorgu">
<input type="hidden" name="selSorgu"/>
<div className="default text">Seçiniz</div>
<i className="dropdown icon"></i>
<div className="menu">
<DropdownItem name={this.state.sorguList[0].sName} id={this.state.sorguList[0].sName} myFunc={this.hello} />
</div>
</div>
);
}
}
Children components should be "dumb" and should not alter the state of the component. They should simply be passed props and pass data back to the parent if the state needs to be altered.
You are passing the hello function as a prop myFunc which is correct. Dropdown item should then call that function and pass it the necessary data so that way the parent can set the state of the selected item.
calis = () => {
this.props.myFunc($('.item.active.selected').text());
}
This will call the hello function in the parent component and then you can set the state from there.
I'm using React and when a user clicks on the <li> tag, the popup method is fired, but the component inside the method is not shown, the popup component does not get fired, why is that?
export default class Main extends React.Component {
constructor(props) {
super(props);
}
popup(value) {
console.log('fired ok');
//call popup component
<Popup value={value} />
}
render() {
return (
<ul>
<li key={0} onClick={() => this.popup(value)} />
</ul>
)
}
}
export default class Popup extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('this is not fired');
const { value } = this.props;
return (
<div class="popup">
<p>{value}</p>
</div>
)
}
}
You would need to actually render the Popup element, something along the lines of:
export default class Main extends React.Component {
constructor(props) {
super(props);
// save the popup state
this.state = {
visible: false, // initially set it to be hidden
value: '' // and its content to be empty
};
}
popup(value) {
console.log('fired ok');
this.setState({
visible: true, // set it to be visible
value: value // and its content to be the value
})
}
render() {
// conditionally render the popup element based on current state
const popup = (this.state.visible ? <Popup value={this.state.value} /> : null);
return (
<ul>
{popup}
<li key={0} onClick={() => this.popup('Hello World')}>Click Me!</li>
</ul>
)
}
}
Here's a fiddle of it in action. Click on the black "Click Me!" text.
I hope that helps!