In ReactJS, How do I update child states from parent class? - javascript

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

Related

How do I access and setState() of a parent component, from a child component onClick

I'm a young dev trying to learn some Reactjs, but I'm having trouble understanding how to configure this Todo app. My goal is to have a button that will add items to the list once entered and submitted. I feel like I'm pretty close to having it figured out.
I've got an App component (parent), button component, and a List component(also a header and item component). the list has a variable that has an empty array for me to add items to, which I reference in my App component.
Here lies the problem. I have an event listener on my button that runs a function that sets the state. I'm logging the list every time I click, which shows that the array is receiving the text inputs and making a new object. However, the DOM is not re-rendering what confuses me even more, is that when I make a slight edit (random semicolon) the DOM renders the items that were entered and logged before I last saved, but remains unresponsive.
What am I missing here? Also, I understand that lifecycle methods like componentDidMount() or componentDidUpdate() may be useful, but I do not fully understand how and where to use them.
export class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.setState(() => {
TodoList.push({id: (TodoList.length+1), name: input})
})
console.log(TodoList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button />
</div>
</div>
)
}
}
export default App
In your App.js, you should pass a function to <Button />, this technique called function as prop in react. The App.js code should look like below:
class App extends React.Component {
constructor() {
super()
this.state = {
todos: TodoList
}
}
addTodo = (todo) => {
this.setState({ todos: [...this.state.todos, todo] })
}
render() {
const todoItems = this.state.todos.map(todo => {
console.log(todo.name, todo.id);
return <Item desc={todo.name} key={todo.id} />
})
return(
<div className='wrapper'>
<div className='card'>
<Header numTodos={this.state.todos.length}/>
<div className='todo-list'>
{todoItems}
</div>
<Button todosList={this.state.todos} addTodo={(todo) => this.addTodo(todo)} />
</div>
</div>
)
}
In the code for Button.js, you get this function via this.props
export default class Button extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const text = document.getElementById('text_field');
const input = text.value;
this.props.addTodo({id: this.props.todosList.length + 1, name: input })
console.log(this.props.todosList)
}
render() {
return (
<div>
<div className='search-container'>
<input className='search' type='text' placeholder='type something...' id='text_field'></input>
</div>
<div className='button-container'>
<button type='submit' className='button-add' onClick={this.handleClick}> New Task </button>
</div>
</div>
)
}

Setting Parent State from Children Component

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

TypeError: this is undefined; can't access its "props" property

My todo app goes like this...and..I'm trying to remove the particular todo item out of the list. I'm calling a function from the child component passing another function as a prop. the problem is, whenever I call the function in child component it fails to access the props in the parent component which is also a function. I tried 'bind'ing the map function called in the parent component. But still in vain.
How can I solve this or is there any better way to delete todo-item?? Need help!
Thanks in advance!
class Todo extends Component {
//initializing state
constructor(props) {
super(props);
this.state = {
todoList: ['wash clothes', 'water the garden', 'buy some flowers', 'something else!']
}
}
//rendering and cycling through the data (toodoList)
render() {
var todoList = this.state.todoList;
todoList = todoList.map(function(item, index) {
return(
<TodoItem item={item} onDelete={this.handleClick.bind(this)} key={index} />
);
}, this);
return(
<div className="component-body">
<AddItem />
<ul>
{todoList}
</ul>
</div>
);
}
//removing item
handleClick(item) {
var updatedList = this.state.todoList.filter(function(val, index){
return item !== val;
});
this.setState= {
todoList: updatedList
}
}
}
class TodoItem extends Component {
render() {
return(
<li>
<div>
<span> {this.props.item} </span>
<span className="handle-delete" onClick={this.handleClick}> x </span>
</div>
</li>
);
}
//Custom function
handleClick() {
this.props.onDelete();
}
}
You have to use arrow function
handleClick = () => {
Or if you cant use it,
Define a constructor in the class where the method is, then inside it:
this.handleClick().bind(this)
This way, this you are refering to, and this the method refers to, is the same. Lets say it's miscommunicate between you and the method :)

Create and return a new component on-click in parent component

I have two components Class and Students. The Class component renders and returns a list of Classes in <li>. I want to add click events to display the Students for each Class in Class component.
I have the following in the render method of the Class component:
render(){
const renderClasses = () =>
this.props.classes.map(class => {
return (
<li>
{class.name}
//class object also has a property of 'students'
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
I want to be able to click on the anchor tags and display the corresponding students for that class. Of course the Student component should receive a prop as follows:
<Students students={this.class.students} />
Thanks in advance!
You can keep a component state to save the class index that should show its students, and then add an onClick handler on the anchor to change that index.
Try the code below:
export default class Test extends Component {
constructor(props)
{
super(props);
this.state = {activeClassIndex : -1}
this.setActiveClassIndex = this.setActiveClassIndex.bind(this);
}
setActiveClassIndex(index){
this.setState({
activeClassIndex : index
})
}
render(){
const renderClasses = () =>
this.props.classes.map( ( currentClass , index ) => {
return (
<li>
<a href="#" onClick={ () => { this.setActiveClassIndex(index) } }>{currentClass.name}</a>
{this.state.activeClassIndex == index ? <Students students={currentClass.students} /> : "" }
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
}

return a variable or constant in a function in an ES6 react class

I have a function which I am returning a variable to create a list item. The function happily returns the unordered list but not the item for which I want to populate it with. I would appreciate some direction to why i have gone wrong.
Header.js
export class Header extends Component {
constructor () {
super();
}
NavMenu () {
let links = () => (
<li>hey</li>
);
return (
<ul className="nav navbar-navs">
{links}
</ul>
);
}
render() {
return (
<div className="container-fluid">
{this.NavMenu()}
</div>
)
}
}
export default Header;
Thank you :)
links is a function that returns a Element. You should call it like links()
class Header extends React.Component {
constructor () {
super();
}
NavMenu () {
let links = () => (
<li>hey</li>
);
return (
<ul className="nav navbar-navs">
{links()}
</ul>
);
}
render() {
return (
<div className="container-fluid">
{this.NavMenu()}
</div>
)
}
}
ReactDOM.render(<Header/>, document.getElementById('app'));
<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="app"></div>
You are using {links} variable which is pointing to a function.
Now, either you call the respective function using links() to return a value
OR
Just assign a direct value to links variable.
NavMenu () {
let links = (<li>hey</li>);
return (
<ul className="nav navbar-navs">
{links}
</ul>
);
}

Categories

Resources