React ref syntax and components as pure functions - reactjs

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
...

Related

How to render a stateless functional component from another component

I'm new on React. I wrote a project on which there is a search component. the search works fine ( I checked on console.log) but I don't know how to call the stateless function component on which the search results should be shown?
class SearchCard extends Component {
// qQuery is a variable for query input
state = { qQuery: "" };
HandleSearch= async (e) => {
e.preventDefault();
const {data:cards} = await cardService.getAllCards();
var searchResults = cards.filter((item) =>
item.qTopic.includes(this.state.qQuery) ||
item.qArticle.includes(this.state.qQuery)
);
this.setState({ cards : searchResults });
// console.log('search results ',searchResults, ' cards ',this.state);
return <CardRender cards={cards}/>
}
render() {
return (
<React.Fragment>
<form className="form" onSubmit={ this.HandleSearch }>
<div className="input-group md-form form-sm form-1 pl-4 col-12">
const CardRender = ({cards,favs,onHandleFavs}) => {
return (
<div className="row">
{cards.length > 0 &&
cards.map((card) =>
<Card key={card._id}
card={card}
favs={favs}
onHandleFavs={() => onHandleFavs(card._id)}
/>
}
</div>
);
}
export default CardRender;
screenshot
You should add the <CardRender cards={cards}/> to the element render returns (at the place you want it to be) and render it if state.cards is not empty.
Something like this
class SearchCard extends Component {
// qQuery is a variable for query input
state = { qQuery: "" };
HandleSearch= async (e) => {
// ...
this.setState({ cards : searchResults });
}
render() {
return (
<div>
...
{cards?.length && <CardRender cards={cards}/>}
</div>
);
}
}

React - Functions are not valid as a React child

I am new to react this is my first application.
I am calling one component inside to another component then those function a moved to app.js
//app.js
class App extends React.Component {
state = {
todos:[
{id:1, title:'get haircut',completed: false},
{id:2, title:'learn react',completed: false},
{id:3, title:'chaaa',completed: false},
]
}
markComplete=(id) =>{
this.setState({
todos: this.state.todos.map((myTodo)=>{
if(myTodo.id === id ){
myTodo.completed = !myTodo.completed;
}
return myTodo
})
})
};
deleteTodo =(id) =>{
this.setState({
todos: [...this.state.todos.filter((myTodo) =>{
return myTodo !==id
})]
})
}
render(){
return (
<div className="App">
<Header/>
<RetrivedTodos todos={this.state.todos}
markComplete={this.markComplete}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
//RetrivedTodos.js
class RetrivedTodos extends Component {
render () {
return this.props.todos.map((retrivedTodos) =>(
<TodosItems key={retrivedTodos.id} todos={retrivedTodos}
markComplete={this.props.markComplete}
deleteTodo={this.props.deleteTodo}
/>
))
}
}
//TodoItems.js
class TodosItems extends Component {
getStrikeMark = () => {
return {
textDecoration:this.props.todos.Completed ? 'line-through': 'none'
}
}
render () {
const { id , title } = this.props.todos
return (
<div className='main-todos-div' style={this.getStrikeMark()}>
<div className='todo-div'>
<input type="checkbox" className='checkbox-round'
onChange={this.props.markComplete.bind(this,id)}/>
<span>{title}</span>
</div>
<div className='btn-div'>
<button onClick={this.props.deleteTodo.bind(this,id)}>
<i className="fas fa-trash"></i>
</button>
</div>
</div>
)
}
}
//header
class Header extends Component {
render () {
const date= new Date();
const todayDate = date.getDate();
const month = date.toLocaleString('default',{month:'long'});
const year = date.getFullYear;
const day = date.toLocaleDateString('default',{weekday:'long'});
return(
<div className='main-header-div'>
<div className='background-div'> </div>
<div className='date-month-div'> </div>
<span>{todayDate}</span>
<span>{month}</span>
<span>{year}</span>
<span>{day}</span>
</div>
)
}
}
What is the problem here? It shows this error
Warning: Functions are not valid as a React child. This may happen if
you return a Component instead of from render. Or maybe
you meant to call this function rather than return it.
thanks in advance
Check the sandbox link:
https://codesandbox.io/s/affectionate-goodall-mh0t7?file=/src/Header.js
The problem is with Header componentnt, it should be :
const year = date.getFullYear();
instead of
const year = date.getFullYear;
getFullYear is a function, that's the reason you were getting the error.
RetrivedTodos seems invalid to me. You are returning a map function instead of a React component. This map function should be executed inside the return value instead of being the return value itself.
Here is how it should look like:
class RetrivedTodos extends Component {
render () {
return (
<div>
{this.props.todos.map((retrivedTodos) => (
<TodosItems key={retrivedTodos.id} todos={retrivedTodos}
markComplete={this.props.markComplete}
deleteTodo={this.props.deleteTodo}
/>
))
}
</div>
)
}
EDIT: Inside Header you are returning a function instead of it's value:
const year = date.getFullYear;
Should be:
const year = date.getFullYear();

My search input and pagination aren't triggering anything in Reactjs

I'm fairly new to react.
My search input and pagination buttons aren't triggering anything and nothing comes up in the console, what is wrong with my code ?
I tried putting every functions in App.js to get it cleaner.
App.js
import React, { Component } from "react";
import List from './List';
let API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
this.updateSearch = this.updateSearch.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({
search: event.target.value.substr(0, 20)
});
}
render() {
return (
<div>
<List data={this.state} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results, search, updateSearch, handleClick, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, number) => {
return (
<Person item={item} key={number} />
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search}
onChange={updateSearch}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
The value of the input doesn't change one bit if I type in it and if I right click on a page number, the console gets me Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '#4' is not a valid selector.
Any idea ?
The issue is that in the List class you attempt take updateSearch and handleClick out of data (which in turn comes from this.props). But updateSearch and handleClick are never placed inside data. If you log either of these methods to the console you'll see they are undefined.
To fix this, you need to pass updateSearch and handleClick from App to List. You can do this either by including the methods inside the data prop, or by passing them directly as their own props (which I would recommend).
For example, you can change the render method of App to look something like this:
render() {
return (
<div>
<List
data={this.state}
updateSearch={ this.updateSearch }
handleClick={ this.handleClick }
/>
</div>
);
}
Then in the render method of List you can do this:
const { data, updateSearch, handleClick } = this.props;
and remove the definitions of the two methods from the destructuring of data below.

Enumerate through a component's state in React

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

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

Resources