Enumerate through a component's state in React - reactjs

so I wanted to have a component iterate through an object within it's state and pass the data down to it's child. My parent component looks basically like this:
class ListContainer extends React.Component{
constructor(props){
super(props);
this.state = {"stuff": {
"pie": ["bread", "apple"],
"fries": ["potatoes", "oil"]
}
};
render(){
let rendArr = [];
for(recipe in this.state.stuff){
let newRecipe = <Child tableName={recipe} recipeData={this.state.stuff[recipe]} />;
rendArr.push(newRecipe);
}
return(
<div id="11"> I work
{rendArr}
</div>
);
}
However, I get an error saying that the "recipe" placeholder I used in the for loop isn't defined. I'm guessing I'm using the for loop here wrong with JSX, but I don't know the right way to iterate through an object. I know I could probably just convert it into an array of objects or something, but right now I'd like to understand why this for loop doesn't work in React.

In ReactJS: typical practice is to render lists using Array.prototype.map().
Object.entries() and Destructuring Assignment can be combined to reach a convenient form.
See below for a practical example.
// List.
class List extends React.Component {
// State.
state = {
recipes: {
pie: ['bread', 'apple'],
fries: ['potatoes', 'oil']
}
}
// Render.
render = () => (
<div id="11">
<h1>Map</h1>
{this.map()}
<h1>For Loop</h1>
{this.forLoop()}
</div>
)
// Map.
map = () => Object.entries(this.state.recipes).map(([name, recipe]) => <Recipe key={name} name={name} recipe={recipe}/>)
// For Loop.
forLoop = () => {
const list = []
for (let name in this.state.recipes) {
const recipe = this.state.recipes[name]
const line = <Recipe key={name} name={name} recipe={recipe}/>
list.push(line)
}
return list
}
}
// Recipe.
const Recipe = ({name, recipe}) => (
<div>
<h3 style={{textTransform: 'capitalize'}}>{name}</h3>
{recipe.map(ingredient => <div>{ingredient}</div>)}
</div>
)
ReactDOM.render(<List/>, document.querySelector('#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>

Instead of pushing component in an array, push the object and pull that in the render using map method:
render(){
let rendArr = [];
for(recipe in this.state.stuff){
rendArr.push({tn: recipe, rd: this.state.stuff[recipe]});
}
return(
<div id="11"> I work
{
rendArr.map(el=> {
<Child tableName={el.tn} recipeData={el.rd} />
})
}
</div>
);
}

use map instead of for..in loop:
render(){
const rendArr = this.state.stuff.map((recipe, i) => <Child
key={i}
tableName={recipe}
recipeData={recipe}
/>);
return(
<div id="11"> I work
{rendArr}
</div>
);
}

Related

React Pass Array Through Props

I have an array that I want to pass to a grandchild component.
class App extends Component {
constructor(props) {
super(props);
...
}
componentDidMount() {
array = ['a', 'b', 'c'];
}
render() {
return (
<Child arrayData={this.props.arrayData} /> ???
)}
I know that I have to pass the data to the child first (and then to the grandchild from there) via a prop but how do I format it? And how does the child receive the prop?
Here is an example of passing a value from an array from parent to grandchild. Notice how salutations is declared and assigned in the in the parent prop and propagated through the child to the grandchild.
This example be amended easily to pass the entire array instead of a single value within it.
render() {
return (
<div>
<h1>Passing props through the component tree...</h1>
<Parent greeting={salutations[0]}/>
</div>
);
}
}
const Parent = (props) => {
return (
<div>
<h2>Parent</h2>
<Child greeting={props.greeting} />
</div>
);
};
const Child = (props) => {
return (
<div>
<h3>Child</h3>
<Grandchild greeting={props.greeting} />
</div>
);
};
let salutations = ["hey", "what's up", "hello"]
const Grandchild = (props) => <h4>Grandchild - {props.greeting}</h4>;
ReactDOM.render(<App />, document.getElementById('app'));
Hello
Dear, You can try to use this code
import ClientImg1 from "../../assets/static/img/client/client_img_1.png";
import ClientImg2 from "../../assets/static/img/client/client_img_2.png";
const themes = {ClientImg1, ClientImg2};
render() {
return (
<Child arrayData1={themes.ClientImg1} arrayData2={themes.ClientImg2}/>
)
}
and
in the
<Child arrayData1={themes.ClientImg1} arrayData2={themes.ClientImg2}/>
component or function page
you can call them by using this
if function
function ClientSingleItem({arrayData1,arrayData2}) {
return (
<img src={arrayData1} alt="ClientImg" />
<img src={arrayData2} alt="ClientImg" />
.....
)
}

Loop of refs for input default value

I wan't to add a default value to dynamically added inputs. So i want to render refs by loop. I tried something like this
this.state.foods.forEach(el=> this[el.id] = React.createRef() )
but it doesn't work. Any idea to render refs dynamically? I run this loop inside of a constructor.
Instead of storing refs for a dynamic amount of inputs you can store each food as a string in an array and update a string when its corresponding input change. This way you just have to add an empty string to the array to add a new food.
Example
class App extends React.Component {
state = {
foods: ["fish", "beef", "pasta"]
};
onChange = (index, food) => {
this.setState(prevState => {
const foods = [...prevState.foods];
foods[index] = food;
return { foods };
});
};
addFood = () => {
this.setState(({ foods }) => ({ foods: [...foods, ""] }));
};
render() {
return (
<div>
{this.state.foods.map((food, index) => (
<div>
<input
key={index}
value={food}
onChange={event => this.onChange(index, event.target.value)}
/>
</div>
))}
<button onClick={this.addFood}>Add food</button>
</div>
);
}
}
ReactDOM.render(<App />, 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>

Calling Event on Child Component in React [duplicate]

I'm making a very basic React app from teamtreehouse.com, and I'm constantly encountering
"TypeError: Cannot read property 'onPlayerScoreChange' of undefined"
even though I'm binding my functions correctly (I think)
'onPlayerScoreChange' Is a method in the Grandparent component which executes when a user hits a '+' or '-' button to change a player's score.
It would be really helpful if someone could explain what is wrong, because I think I am setting this.onPlayerScoreChange = this.onPlayerScoreChange.bind(this) in the great grandparent's constructor.
Parent component:
class App extends React.Component {
constructor(props) {
super(props);
this.onPlayerScoreChange = this.onPlayerScoreChange.bind(this)
this.state = {
initialPlayers: props.initialPlayers,
};
}
onPlayerScoreChange(delta, index) {
this.setState((prevState, props) => {
return {initialPlayers: this.prevState.initialPlayers[index].score += delta}
})
}
render() {
return(
<div className = "scoreboard">
<Header title = {this.props.title}/>
<div className = "players">
{this.state.initialPlayers.map(function(player, index) {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
})}
</div>
</div>
)
}}
(Component has default props for title)
Child component:
class Player extends React.Component {
render() {
return(
<div className = "player">
<div className = "player-name">
{this.props.name}
</div>
<div className = "player-score">
<Counter score = {this.props.score} onChange = {this.props.onScoreChange} index = {this.props.index}/>
</div>
</div>
)
}}
Grandchild component:
class Counter extends React.Component {
constructor(props) {
super(props)
this.handleDecrement = this.handleDecrement.bind(this)
this.handleIncrement = this.handleIncrement.bind(this)
}
handleDecrement() {
this.props.onChange(-1, this.props.index)
}
handleIncrement() {
this.props.onChange(1, this.props.index)
}
render() {
return(
<div className = "counter">
<button className = "counter-action decrement" onClick = {this.handleDecrement}> - </button>
<div className = "counter-score"> {this.props.score} </div>
<button className = "counter-action increment" onClick = {this.handleIncrement}> + </button>
</div>
)}}
Thank you!
You have not done binding for the map function where you are using onScoreChange = {this.onPlayerScoreChange},
you can either use bind or arrow functions for binding
P.S. Binding is needed because the context of the map function is different from the React Component context and hence this inside this function won't be Referring to the React Components this and thus you can't access that properties of the React Component class.
With Arrow function:
{this.state.initialPlayers.map((player, index)=> {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
})}
With bind
{this.state.initialPlayers.map(function(player, index) {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
}.bind(this))}
Can also be done by passing second argument as this to map function as onClick event uses local this of map function which is undefined here and this currently refers to the global object.
{this.state.initialPlayers.map(function(player, index) {
return(
<Player
name = {player.name}
score = {player.score}
key = {player.id}
index = {index}
onScoreChange = {this.onPlayerScoreChange}
/>
)
}),this}
sometimes it is quite easy and it all depends on how you declare your loop
example you will get this error if you try to do something like this var.map(function(example, index) {}
but if you call the new function within the map with this
this.sate.articles.map(list =>
{<a onClick={() => this.myNewfunction()}>{list.title}</a>)}
the second loop will get you out of the undefined error
and dont forget to bind your new function
//This should be declared befor map function
const thObj = this;
this.sate.articles.map(list =>
{<a onClick={() => thObj.myNewfunction()}>{list.title})}

State not updating in Component

Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010

React ref syntax and components as pure functions

I have the following TodoApp written in React:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://unpkg.com/react#15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15.3.2/dist/react-dom.js"></script>
<title>React! React! React!</title>
</head>
<body>
<div class="container">
<div id="container" class="col-md-8 col-md-offset-2"> </div>
</div>
<script type="text/babel">
console.clear();
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
}
const TodoForm = ({addTodo}) => {
// Input Tracker
let input;
// Return JSX
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={() => {remove(todo.id)}}>{todo.text}</li>);
}
const TodoList = ({todos, remove}) => {
// Map through the todos
const todoNode = todos.map((todo) => {
return (<Todo todo={todo} key={todo.id} remove={remove}/>)
});
return (<ul>{todoNode}</ul>);
}
// Contaner Component
// Todo Id
window.id = 0;
class TodoApp extends React.Component{
constructor(props){
// Pass props to parent class
super(props);
// Set initial state
this.state = {
data: []
}
}
// Add todo handler
addTodo(val){
// Assemble data
const todo = {text: val, id: window.id++}
// Update data
this.state.data.push(todo);
// Update state
this.setState({data: this.state.data});
}
// Handle remove
handleRemove(id){
// Filter all todos except the one to be removed
const remainder = this.state.data.filter((todo) => {
if(todo.id !== id) return todo;
});
// Update state with filter
this.setState({data: remainder});
}
render(){
// Render JSX
return (
<div>
<Title />
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.getElementById('container'));
</script>
</body>
</html>
Questions:
What is this syntax:
const TodoForm = ({addTodo}) => {
// Input Tracker
let input;
// Return JSX
return (
<div>
<input ref={node => {
input = node;
}} />
I think I get what ref is but what is that node just inside the curly braces? If it's a function declaration, where are the parenthesis around node? What is going on?
Also, at the end, we render the TodoApp which renders TodoForm like this:
<TodoForm addTodo={this.addTodo.bind(this)}/>
Does that just pass addTodo to the functionally declared component, not as props, but merely an argument?
const TodoForm = ({addTodo}) => {
Is this correct? addTodo comes in merely as an argument and not as props?
So in the following function
const TodoForm = ({addTodo}) => {
// Input Tracker
let input;
// Return JSX
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
The first line is an example of destructuring in ES6 What happens is that in const TodoForm = ({addTodo}) => { props gets passes to the TodoForm Component which is stateless and in the props you have addTodo as a prop so out of all the props we are extracting addTodo
Also for refs a callback approach is being followed. It is an ES6 style to write a function. Here node is an argument and it doesn't contain any parenthesis because it is a single argument and ES6 gives you flexibility to omit the parenthesis. Also inside the {} you have the body of the function
In your code node refers to the DOM element and you are assigning its reference to the variable input that you have defined. Now you can refer the DOM with input rather than assigning ref as <input ref="myValue"/> and then refering it as this.refs.myValue.
I hope was able to explain it properly.
Read the following documentation on React ref callback approach for a detailed explaination.
let's say we have this one :
const TodoForm = (data) => {
const addTodo = data.addTodo //can be const myFunc = data.addTodo
// Input Tracker
...
as an enhancement we can do it like that :
const TodoForm = (data) => {
const {addTodo} = data //can be const {addTodo as myFunc} = data
// Input Tracker
...
once more !
as an enhancement we can do it like that :
//notice that we moved the {addTodo} directly to replace data
const TodoForm = ({addTodo}) => {
//can be ({addTodo: myFunc}) => {
// Input Tracker
...

Resources