Sending props two times from parent component to child component - javascript

I am getting map undefined when i am sending props Two times as separate components
import React, { Component } from 'react'
import Todo from './Todo';
export default class App extends Component {
state = {
todos: [
{id : 1 , content: "lets sleep"},
{id: 2, content:"lets eat "}
]}
deletTodo = (id) => {
console.log(id)
}
render() {
return (
<div className="App container">
<h1 className="center blue-text">Todo's</h1>
<Todo todo = {this.state.todos} />
{ <Todo deletTodo = {this.deletTodo}/> }
</div>
)
}
}
It is throwing me map of undefined but the following code does the trick i don't know why any one explain
<Todo todo = {this.state.todos} deletTodo= {this.deletTodo}/>
The following is my Todo.js where i am getting the props
import React, { Component } from 'react'
export default class Todo extends Component {
render() {
return (
<div className= "todos collection">
{
this.props.todo.map((td)=>{
return (
<div className="collection-item" key ={td.id} >
<span>{td.content}</span>
</div>
)})}
</div>
)
}
}

Both the usage of component will create seperate instances. Only the props that you provide in that instance will be available as this.props.
in <Todo todo = {this.state.todos} /> only todo prop is available and deletTodo is not available. In { <Todo deletTodo = {this.deletTodo}/> } only deletTodo is available and todos prop is not available. This is the reason you will get the error Cannot read property 'map' of undefined. You can fix this by providing a default prop so that none of the props are ever undefined.
Todo.defaultProps = {
todo: [],
deletTodo: () => null,
}

Set your state in a constructor
constructor() {
super();
this.state = {
//set state here
}

Related

React trying to assign props to state variable doesn't work

I'm trying to assign props that I get from parent component and assign it to state in child component(because I want to manipulate the props data I assign it to state first).
When I log the state variable it comes out as an empty array but when I make a new variable in render and assign props to it and log it. It does show the data I need. Also, when I just log this.props I can definitely see that props holds the data I need.
I've assigned props to state a couple of times before, so I'm not sure what is so different this time for it not to work.
Parent component where I pass props to child:
<ShowAvailableTimeslots onClick={this.getSelectedTimeslot} allTimeSlots={this.state.AvailabletimeSlots} />
Child component where I try to assign props to state:
class ShowAvailableTimeslots extends Component {
constructor(props) {
super(props)
this.state = {
sliceEnd: 5,
sliceStart:0,
selectedSlotValue: "",
timeSlotArr: this.props.allTimeSlots,
// timeSlotSlice: timeSlotArr.slice(this.state.sliceStart, this.state.sliceEnd)
}
}
handleTimeSlotClick = (timeSlot) => {
this.setState({ selectedSlotValue: timeSlot }, () => {
this.props.onClick(this.state.selectedSlotValue)
console.log('time slot value', timeSlot)
});
}
previousSlots =()=>{
var test;
}
forwordSlots =()=>{
var test;
}
render() {
var timeSlotArrRender = this.props.allTimeSlots;
return (
<React.Fragment>
{console.log("state", this.state.timeSlotArr)} // --> doesn't show data
{console.log("props", this.props)} // --> does show data
{console.log("render variable", timeSlotArrRender )} // --> does show data
<button className="button btn" onClick={() => this.previousSlots()} disabled={this.state.sliceStart === 0}>left</button>
{/* {this.state.timeSlotArr.map(timeSlot => <a className="timeslot btn " key={timeSlot} value={timeSlot} onClick={() => this.handleTimeSlotClick(timeSlot)}>{timeSlot}</a>)
} */}
<button className="button btn">right</button>
</React.Fragment>
)
}
}
export default ShowAvailableTimeslots
the constructor is called when the component life cycle begins.
You are passing the this.state.AvailabletimeSlots from the parent and by then the constructor have already been called and the assignment for timeSlotArr is already done, so
timeSlotArr: this.props.allTimeSlots // will not work
You have to get help of life cycle methods or hooks
componentWillReceiveProps(nextProps){
this.setState({timeSlotArr: nextProps.allTimeSlots })
}
According to new changes you have to use
static getDerivedStateFromProps(nextProps, prevState){
return {
timeSlotArr: nextProps.allTimeSlots
};
}
I have it working just fine. https://jsfiddle.net/85zc4Lxb/
class App extends React.Component {
render() {
return (<Child passing="I am being passed to child" />);
}
}
class Child extends React.Component {
constructor(props) {
super(props)
this.state = {
passedProp: this.props.passing,
}
}
render() {
return (
<React.Fragment>
<button>{this.state.passedProp}</button>
</React.Fragment>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
I try to recreate the scenario and it work try saving all your files again and then check
parents component
import React, { Component } from "react";
import TestOne from "./Components/TestOne/TestOne";
export class App extends Component {
state = {
x: "x data",
y: "y data",
};
render() {
return (
<div>
<TestOne x={this.state.x} allTimeSlots={this.state.y}/>
</div>
);
}
}
export default App;
Child component
import React, { Component } from "react";
export class TestOne extends Component {
constructor(props) {
super(props);
this.state = {
sliceEnd: 5,
sliceStart: 0,
selectedSlotValue: "",
timeSlotArr: this.props.x,
};
}
render() {
var timeSlotArrRender = this.props.allTimeSlots;
return (
<React.Fragment>
{console.log("state", this.state.timeSlotArr)}
{console.log("props", this.props)}
{console.log("render variable", timeSlotArrRender)}
<button className="button btn">right</button>
</React.Fragment>
);
}
}
export default TestOne;
Result:
I think you are missing this.setState({})

How do I pass a React prop from Parent to Child, to another Child?

I currently have my Parent set up as follows, which I'm then passing props to
class WorkoutPlan extends React.Component {
constructor() {
super();
this.state = {
workoutPlan: {}
};
}
componentDidMount() {
axios
.get("/api/workout-plan")
.then(response => {
this.setState({ workoutPlan: response.data });
})
.catch(error => {
console.log(error);
});
}
render() {
const { workoutPlan } = this.state;
// const workoutPlan = this.state.workoutPlan;
return (
<div>
<h1>{workoutPlan.Name}</h1>
<button className="button" onClick={this.handleClick}>
Click Me
</button>
<Workout {...workoutPlan.workout} />
</div>
);
}
}
Then in my child, I'm wanting to pass those same props to another Child
import React from "react";
import Exercise from "./Exercise";
const Workout = props => {
return (
<div>
<h2>"Workout for {props.day}"</h2>
<Exercise {...workoutPlan.workout} />
</div>
);
};
export default Workout;
I can't seem to figure out how I would go about doing this. I'm being told that the setup is exactly the same as the 1st child, but when I enter in the same code, it's not working.
You can pass {...props} to your Exercise component so your Workout component should look like this
import React from "react";
import Exercise from "./Exercise";
const Workout = props => {
return (
<div>
<h2>"Workout for {props.day}"</h2>
<Exercise {...props} />
</div>
);
};
export default Workout;
When you pass props destructuring it, the effect it's the same as you were passing props one by one.
You can't achieve your goal because in your Workout component there is no "workout" prop.
Try to pass props to Exercise component like this:
<Exercise {...props} />

How do I access some data of a property I passed from parent component to child component?

I am learning React and I am trying to call a function in a child component, that accesses a property that was passed from parent component and display it.
The props receives a "todo" object that has 2 properties, one of them is text.
I have tried to display the text directly without a function, like {this.props.todo.text} but it does not appear. I also tried like the code shows, by calling a function that returns the text.
This is my App.js
import React, { Component } from "react";
import NavBar from "./components/NavBar";
import "./App.css";
import TodoList from "./components/todoList";
import TodoElement from "./components/todoElement";
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo(input) {
const newTodo = {
text: input,
done: false
};
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" />
<button
onClick={() => this.addNewTodo(document.getElementById("text"))}
>
Add new
</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
export default App;
This is my todoElement.jsx
import React, { Component } from "react";
class TodoElement extends Component {
state = {};
writeText() {
const texto = this.props.todo.text;
return texto;
}
render() {
return (
<div className="row">
<input type="checkbox" />
<p id={this.writeText()>{this.writeText()}</p>
<button>x</button>
</div>
);
}
}
export default TodoElement;
I expect that when I write in the input box, and press add, it will display the text.
From documentation
Refs provide a way to access DOM nodes or React elements created in the render method.
I'll write it as:
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.textRef = React.createRef();
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo() {
const newTodo = {
text: this.textRef.current.value,
done: false
};
const todos = [...this.state.todos, newTodo];
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" ref={this.textRef} />
<button onClick={this.addNewTodo}>Add new</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
In your approach, what you got as an argument to the parameter input of the method addNewTodo is an Element object. It is not the value you entered into the text field. To get the value, you need to call input.value. But this is approach is not we encourage in React, rather we use Ref when need to access the html native dom.

Why is my react component not re rendering when props comes into the component?

I am currently in a project, and I have had to do null checks on every single props that has come in to children components wether through redux or passing it in myself. I feel like that is not normal with react? Isn't a huge plus side of React is automatic re-rendering? If I try to put anything into state, I can't because There has to be a null check in the render before I do anything with the data. Thanks in advance!!
PARENT COMPONENT =
class App extends Component {
componentDidMount(){
//where I load the data
this.loadCardsFromServer();
this.props.GetAllData();
}
render() {
//NEED TO DO A NULL CHECK FROM THIS COMING FROM REDUX
const filteredData = !!this.state.data ? this.state.data.filter(card =>{
return card.name.toUpperCase().includes(this.state.input.toUpperCase())
}) : null;
return (
//MAKES ME DO ANOTHER NULL CHECK
<div>
{!!this.state.data ? filteredData.map(i => <Card person={i} key={i.created} planet={this.props.planets} />) : null}
</div>
))}
CHILD COMPONENT OF CARD
class Card extends Component {
//WHERE I WANT TO PUT THE PROPS
constructor(){
super();
this.state={
edit: false,
name: this.props.person.name,
birthYear: this.props.person.birth_year
}
}
render() {
let world = null;
//ANOTHER NULL CHECK
if(this.props.planet){
this.props.planet.map(i => {
if(i.id === this.props.person.id){
world = i.name
}
})
}
return (
<div>
//THIS IS WHERE I WANT THE VALUE TO BE STATE
{this.state.edit ? <input label="Name" value={this.state.name}/> : <div className='card-name'>{name}</div>}
</div>
You need to update state when data arrive.
You can do it like this:
import React, { Component } from 'react';
import './App.scss';
import Card from './Components/Card/Card.js';
class App extends Component {
constructor(){
super();
this.state = {
loading:true,
cards:[]
};
}
componentDidMount(){
this.loadCardsFromServer();
}
loadCardsFromServer = () => {
let cardsResponseArray = [];
// fetch your cards here, and when you get data:
// cardsResponseArray = filterFunction(response); // make function to filter
cardsResponseArray = [{id:1,name:'aaa'},{id:2,name:'bbb'}];
setTimeout(function () {
this.setState({
loading:false,
cards: cardsResponseArray
});
}.bind(this), 2000)
};
render() {
if(this.state.loading === true){
return(
<h1>loading !!!!!!!!</h1>
);
} else {
return (
<div>
{this.state.cards.map(card => (
<Card key={card.id} card={card}></Card>
))}
</div>
);
}
}
}
export default App;
And then in your Card component:
import React, { Component } from 'react';
class Card extends Component {
constructor(props){
super(props);
this.props = props;
this.state = {
id:this.props.card.id,
name:this.props.card.name
};
}
render() {
return (
<div className={'class'} >
Card Id = {this.state.id}, Card name = {this.state.name}
</div>
);
}
}
export default Card;
For those interested about React state and lifecycle methods go here
Okay, in this case i craft a little helper's for waiting state in my redux store. I fetch data somewhere (app) and i render a connected component waiting for fetched data in store:
const Loader = (props) => {
if (!props.loaded) {
return null;
}
<Card data={props.data}/>
}
const CardLoader = connect (state => {
return {
loaded: state.data !== undefined
data: state.data
}
})(Loader)
<CardLoader />

React access and update variable outside of render

I'm currently learning React and am following this guy Youtube Videos.
However, they are outdated and it's difficult to get along with the old codes. I already tried some Stackoverflow and Google solutions, but they didn't help.
Right now am I struggling on how I can access my variable todo which I declared inside render() and update it with a my function handleDelete outside of render()?
My goal is to delete the item i clicked on.
I already tried to set my variable inside constructor() but then it wasn't possible to give it the value of this.props.todos.
My code:
import React from 'react';
import ReactDom from 'react-dom';
export default class TodoItem extends React.Component {
handleDelete(item){
let updatedTodos = this.props.todos;
updatedTodos = updatedTodos.filter((val,index) => {
return item !== val;
})
todos = updatedTodos;
};
render() {
//Add all this.props items
let todos = this.props.todos;
todos = todos.map((item, index) => {
return (
<li>
<div className="todo-item">
<span className="item-name">{item}</span>
<span className="item-remove" onClick={this.handleDelete.bind(this, item)}> x </span>
</div>
</li>);
});
return (<React.Fragment>{todos}</React.Fragment>)
};
}
(This code is later exported to index.js where it is transpiled with Babel)
Thanks for your time taken!
Update:
Here is index.js:
import React from 'react';
import ReactDom from 'react-dom';
import TodoItem from './todoItem';
class TodoComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: ["clean up", "walk doggo", "take nap"]
};
}
render() {
return (<div>
<h1>The todo list:</h1>
<ul>
<TodoItem todos={this.state.todos}/>
</ul>
</div>);
}
}
ReactDom.render(<TodoComponent/>, document.querySelector(".todo-wrapper"));
TodoComponent
import React from 'react';
import ReactDom from 'react-dom';
import TodoItem from './todoItem';
class TodoComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: ["clean up", "walk doggo", "take nap"]
};
}
handleDelete(item){
let todos = this.state.todos;
todos= todos.filter((todo) => todo !== item);
this.setState((prevState) => ({
todos: todos
}));
};
render() {
return (<div>
<h1>The todo list:</h1>
<ul>
<TodoItem todos={this.state.todos} handleDelete={this.handleDelete}/>
</ul>
</div>);
}
}
ReactDom.render(<TodoComponent/>, document.querySelector(".todo-wrapper"));
Todo Item
import React from 'react';
import ReactDom from 'react-dom';
export default class TodoItem extends React.Component {
render() {
return (
this.props.todos.map((item) => {
return (
<li>
<div className="todo-item">
<span className="item-name">{item}</span>
<span className="item-remove" onClick={() => this.props.handleDelete(item)}> x </span>
</div>
</li>
)
}))
}
}
Your code is having the problem in TodoItem that you are trying to delete items in TodoItem where you do not have access to state. And moreover If you do some actions in component and you want to get the change reflected the your components must re render. And this is possible when your state is changed. And component related to corresponding changes will re render itself
I have not tested it so there might be some typos but you have to do it like this
import React from 'react';
import ReactDom from 'react-dom';
export default class TodoItem extends React.Component {
handleDelete(item){
this.props.updateTodos(item)
};
render() {
//Add all this.props items
let todos = this.props.todos;
todos = todos.map((item, index) => {
return (
<li>
<div className="todo-item">
<span className="item-name">{item}</span>
<span className="item-remove" onClick={this.handleDelete.bind(this, item)}> x </span>
</div>
</li>);
});
return (<React.Fragment>{todos}</React.Fragment>)
};
}
import React from 'react';
import ReactDom from 'react-dom';
import TodoItem from './todoItem';
class TodoComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: ["clean up", "walk doggo", "take nap"]
};
this.updateTodos =this.updateTodos.bind(this);
}
updateTodos(item){
this.setState({todos :this.state.todos.filter((val,index) => {
return item !== val;
})})
}
render() {
return (<div>
<h1>The todo list:</h1>
<ul>
<TodoItem todos={this.state.todos} updateTodos ={this.updateTodos}/>
</ul>
</div>);
}
}
ReactDom.render(<TodoComponent/>, document.querySelector(".todo-wrapper"));
I'm suggesting a different approach for you. There are some issues you need to think better. First of all, I suggest keeping your todos as objects and let have them id and text properties.
Second, you have a separate component but you are passing to it whole todos. Instead of that, map your todos and then pass each todo to your component. In this way your management over everything will be easier.
For your situation, you need a delete handler to pass your TodoItem, then by using this handler you will do the action.
Lastly, your TodoItem does not need to be a class component, so I've changed it.
Here is a working example. I've changed your code according to my suggestions.
class Todos extends React.Component {
state = {
todos: [
{ id: 1, text: "foo" },
{ id: 2, text: "bar" },
{ id: 3, text: "baz" },
]
}
deleteTodo = ( todo ) => {
const newTodos = this.state.todos.filter( el => el.id !== todo.id );
this.setState({ todos: newTodos });
}
render() {
const { todos } = this.state;
return (
<div>
<ul>
{
todos.map( todo => <TodoItem key={todo.id} todo={todo} deleteTodo={this.deleteTodo} /> )
}
</ul>
</div>
)
}
}
const TodoItem = props => {
const handleDelete = () => props.deleteTodo(props.todo);
return (
<li onClick={handleDelete}>{props.todo.text} x</li>
)
};
ReactDOM.render(<Todos />, 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>
Also, last night I wrote a small example for another question. But it got bigger and bigger and I did not post it. But here is a small, simple Todo example. Of course there must be some improvements but you can examine it as an example.
https://codesandbox.io/s/01v3kmp9vv
Edit
If you don't want to use class-fields and install another Babel plugin, just change the related part with this:
class Todos extends React.Component {
consturctor( props ) {
super( props );
this.state = {
todos: [
{ id: 1, text: "foo" },
{ id: 2, text: "bar" },
{ id: 3, text: "baz" },
],
}
}
....rest of the code

Categories