I have such a component:
import React, { Component } from 'react';
export class TopicsList extends Component {
constructor(props) {
super(props);
this.state = {
topics: [...],
};
this.references = [];
}
getOrCreateRef(id) {
if (!this.references.hasOwnProperty(id)) {
this.references[id] = React.createRef();
}
return this.references[id];
}
render() {
const {
topics
} = this.state;
return (
<div>
<ul>
{topics.map((topic) => (
<TopicItem
key={topic.id}
topic={topic}
ref={this.getOrCreateRef(topic.id)}
/>
)
)}
</ul>
</div>
)
}
}
const TopicItem = React.forwardRef((props, ref) => {
return (
<li
>
<p>{props.name}</p>
<i
className="fal fa-plus"
/>
</li>
);
});
I wrote test to test how much li items will be rendered:
test('should render 3 li items', () => {
console.log(wrapper.debug())
expect(wrapper.find('TopicItem').length).toBe(3);
});
but my test failed because in jest they recognized like:
<ul>
<ForwardRef topic={{...}} />
<ForwardRef topic={{...}} />
<ForwardRef topic={{...}} />
</ul>
How can I test components that are returned with React.forwardRef?
I cannot find appropriate solutions on the internet or here.
It is a bit late, but assigning the displayName property to the wrapped component can help. Enzyme respects displayName and uses it when creating snapshots (rendering it instead of ForwardRef in this case), and find also works with display names.
Related
I want to display a different component with each button click.
I'm sure the syntax is wrong, can anyone help me? The browser doesn't load
I would love an explanation of where I went wrong
One component (instead of HomePage) should display on the App component after clicking the button. Help me to understand the right method.
Thanks!
App.js
import React, {useState} from 'react';
import './App.css';
import Addroom from './components/Addroom.js'
import HomePage from './components/HomePage.js'
function App() {
const [flag, setFlage] = useState(false);
return (
<div className="App">
<h1>My Smart House</h1>
<button onClick={()=>{setFlage({flag:true})}}>Addroom</button>
<button onClick={()=>{setFlage({flag:false})}}>HomePage</button>
{setState({flag}) && (
<div><Addroom index={i}/></div>
)}
{!setState({flag}) && (
<div><HomePage index={i}/></div>
)}
</div>
)
}
export default App;
HomePage
import React from 'react'
export default function HomePage() {
return (
<div>
HomePage
</div>
)
}
Addroom
import React from 'react'
export default function Addroom() {
return (
<div>
Addroom
</div>
)
}
I didn't test it but as i can see it should be something like this:
<button onClick={()=>setFlage(true)}>Addroom</button>
<button onClick={()=>setFlage(false)}>HomePage</button>
{flag && (
<div><Addroom index={i}/></div>
)}
{!flag && (
<div><HomePage index={i}/></div>
)}
You need to call setFlage function with argument of Boolean saying true or false and it changes the flag variable that you want to read.
Try the following.
function App() {
const [flag, setFlage] = useState(false);
return (
<div className="App">
<h1>My Smart House</h1>
<button
onClick={() => {
setFlage(true);
}}
>
Addroom
</button>
<button
onClick={() => {
setFlage(false );
}}
>
HomePage
</button>
{flag ? <Addroom /> : <HomePage /> }
</div>
);
}
You are missing render methods and also you should use setState for reactive rendering.( when you use state variables and once value changed render method will rebuild output so this will load your conditinal component.
https://jsfiddle.net/khajaamin/f8hL3ugx/21/
--- HTML
class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div> In Home</div>;
}
}
class Contact extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div> In Contact</div>;
}
}
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
flag: false,
};
}
handleClick() {
this.setState((state) => ({
flag: !state.flag,
}));
console.log("hi", this.state.flag);
}
getSelectedComp() {
if (this.state.flag) {
return <Home></Home>;
}
return <Contact></Contact>;
}
render() {
console.log("refreshed");
return (
<div>
<h1>
Click On button to see Home component loading and reclick to load back
Contact component
</h1
<button onClick={() => this.handleClick()}>Switch Component</button>
{this.getSelectedComp()}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
I have a class called: QuestionList, which creates Questions children, along with (nested) Alternatives:
QuestionList render:
<Question wording="wording...">
<Alternative letter="a" text="bla ..." />
<Alternative letter="b" text="ble ..." />
<Alternative letter="c" text="bli ..." />
<Alternative letter="d" text="blo ..." />
</Question>
Who is "alternatives" parent? Question (because it is nested) or QuestionList (because it created)?
How can pass a Question event handler to Alternative?
If I use
<Alternative onClick={this.handleClick} (...) />
It will pass QuestionList's handler (and not Question's handler - the desired behavior).
QuestionList
import React, { Component } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import Loader from 'react-loaders';
import Question from './Question';
import Alternative from './Alternative';
export default class QuestionList extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
}
loadItems(page) {
let questions = this.state.questions;
axios.get('https://jsonplaceholder.typicode.com/photos?_start='+ page * 5 +'&_limit=5')
.then(response => {
console.log(response.data);
response.data.map(p => {
questions.push(p);
});
this.setState({questions});
});
}
handleClick() {
alert("QuestionList");
}
render() {
let items = [];
const loader = <Loader type="ball-scale-multiple" />;
this.state.questions.map((p, i) => {
items.push(
<Question
title={p.title}
key={i}
id={p.id}
>
<Alternative onClick={this.props.handleClick} key={1} text={ p.title } letter="a" />
<Alternative onClick={this.props.handleClick} key={2} text={ p.title } letter="b" />
<Alternative onClick={this.props.handleClick} key={3} text={ p.title } letter="c" />
<Alternative onClick={this.props.handleClick} key={4} text={ p.title } letter="d" />
<Alternative onClick={this.props.handleClick} key={5} text={ p.title } letter="e" />
</Question>
)
});
return (
<InfiniteScroll
key={1}
pageStart={0}
loadMore={this.loadItems.bind(this)}
hasMore={true}
loader={loader}
>
<div className="justify-content-center" id="react-app-questions-list">
{items}
</div>
</InfiniteScroll>
);
}
}
Question
import React, { Component } from 'react';
export default class Question extends Component {
constructor(props) {
super(props);
this.state = {
answer_class: "unanswered"
};
}
handleClick(isCorrect, e) {
// alert(this.props.id + ": " + isCorrect);
alert("Question");
}
render() {
return (
<div className={"list-group list-group-bordered mb-3 " + this.state.answer_class}>
<div className="list-group-item">
<div className="list-group-item-body">
<h4 className="list-group-item-title">
{ this.props.title }
</h4>
</div>
</div>
{ this.props.children }
</div>
);
}
}
Alternative
import React, { Component } from 'react';
class Alternative extends Component {
render() {
return (
<a className="list-group-item list-group-item-action react-app-alternative">
<div className="list-group-item-figure">
<div className="tile tile-circle bg-primary">{ this.props.letter }</div>
</div>
<div className="list-group-item-body"> { this.props.text }</div>
</a>
);
}
}
export default Alternative;
Who is Alternatives parent? Question (because it is nested) or QuestionList (because it created)?
Alternative parent is Question. If you check Question.props.children array (remember that Question is just an object), you will see Alternative types there.
function Question({ children }) {
console.log(children); // children[0].type === Alternative
return children;
}
Read more about React elements as objects here.
How can pass a Question event handler to Alternative?
You can inject props to Question children, for example:
function Question({ children }) {
console.log(children);
const injectChildren = React.Children.map(children, child =>
React.cloneElement(child, { letter: `${child.props.letter}-injected` })
);
return injectChildren;
}
For this you need to read about React Top-Level API and refer to React.Children API and cloneElement().
Check out the example:
Handlers are intended to work on context ... where state is managed .. then in <QuestionList/>. Prepare specific, parametrized handlers and use them to update common state.
Chaining 'desired' (more granular or more specific) handlers to pass values through the structure can't be practical. It won't be efficient, either.
Take a look at data/state flow in 'Formik` project - form, validations, fields. It can be a good source of inspiration for this problem.
<Question/> and <Alternative/> should be stateless, functional components - you don't need them to be statefull. KISS, YAGNI...
I am trying to bind a method of a parent component to the state of its child component but I'm unable to get the desired result. I checked the value of 'this' in App component and it still points to the App component. Should it not be pointing to the ItemsList component since its being binded to it using bind()? Can someone please point out the mistake I'm making.
import React from 'react';
import {render} from 'react-dom';
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div> {this.props.value} </div>;
}
}
class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
itemArray: ['Work', 'Learn React']
}
this.props.adder.bind(this);
console.log(this.props.adder)
}
render() {
const items = this.state.itemArray.map(el=><Item key={el} value={el} />);
return (
<div>
<h2> To Do List </h2>
<ul>{items}</ul>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
addElement (data) {
let items = this.state.ItemList;
items.push(<Item value={data} />);
}
render() {
return (
<div>
<input type="text" ref={input=>this.input=input} />
<input type="button" value="Add" onClick={()=>this.addElement(this.input.value)}/>
<ItemList adder={this.addElement} />
</div>
);
}
}
render(<App />, document.getElementById('root'));
Should it not be pointing to the ItemsList component since its being binded to it using bind()?
Well,the step you following in not right one.
In App Component
You need to store the ItemList (child) component reference in App(parent) component.
<ItemList adder={this.addElement} bindChild = {(ref)=>this.itemList = ref}/>
In ItemList component,
you need to call bindChild method when ItemList component mounted.
componentDidMount(){
this.props.bindChild(this);
}
Now, in your App (parent) component, you have reference for ItemList (child) component in this.itemList property.
In App component, you can use this.itemList to update state of ItemList (child) component.
addElement(data) {
let items = this.itemList.state.itemArray;
console.log(items);
const newItem = <Item value={data} />
this.itemList.setState({ itemArray : [...items, newItem]})
}
Please check complete example on codesandbox
Though what you want is technically possible, this is a much more explicit easy to understand way to do it.
I re-factored your code so that the data flow only goes in one direction, from App to `Itemimport React from "react";
import { render } from "react-dom";
I also changed Item and ItemList to stateless components that take value and items as props respectively.
The main change is that App holds the state instead of ItemList
const Item = ({ value }) => <div>{value}</div>;
const ItemList = ({ items }) => (
<div>
<h2>To Do List</h2>
{items.map(item => <Item key={item} value={item} />)}
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: ["Work", "Learn React"]
};
}
addElement(value) {
this.setState(state => ({
items: [...state.items, value]
}));
}
render() {
return (
<div>
<input type="text" ref={input => (this.input = input)} />
<input
type="button"
value="Add"
onClick={() => this.addElement(this.input.value)}
/>
<ItemList items={this.state.items} />
</div>
);
}
}
render(<App />, document.querySelector("#root"));
Here is a CodeSandbox with your working app: https://codesandbox.io/s/4r4v0w5o94
In my React component, I'm displaying a list of items -- each in its own DIV element with a unique id i.e. <div id="abc-123">.
I'm also using react-perfect-scrollbar to make the whole thing nicer looking.
I keep a variable in my reducer named activeElementId and when the value of activeElementId changes, I want to automatically scroll to that item on the screen.
Setting the activeElementId is the easy part but I'm not sure how to scroll to that element and would appreciate some pointers.
This is the parent component that contains the ListComponent.
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.items.length > 0
?
<PerfectScrollBar>
<ListComponent items={this.props.items} />
</PerfectScrollBar>
: null}
</div>
);
}
}
My ListComponent is a presentational component:
const ListComponent = ({ items }) => {
return(
<ul className="pretty-list">
{items.map(item => <ItemComponents item={item} />)}
</ul>
);
}
export default ListComponent;
And the ItemComponent is a presentational component as well:
const ItemComponent = ({ Item }) => {
return(
<li>
<div id={item.id}>
{item.someProperty}
</div>
</li>
);
}
export default ItemComponent;
I really like the idea of keeping ListComponent and ItemComponent separate and as presentational components as that helps keep the code simpler and easier to manage. Not sure if that would make it difficult to implement the auto scroll logic though.
The library you use has a method called setScrollTop. You can use it with getBoundingClientRect. To use getBoundingClientRect you need to have the dom-element. You didn't give any code about how you are setting or getting the active element but I'll try to give you an example. Maybe it will help you to implement on your code.
Example
class MyComponent extends Component {
constructor(props) {
super(props);
}
_onListItemChange = (itemsPosition) => {
this.scrollbar.setScrollTop(itemsPosition.top);
}
render() {
return (
<div>
{this.props.items.length > 0 ?
<PerfectScrollBar ref={(scrollbar) => { this.scrollbar = scrollbar; }}>
<ListComponent
items={this.props.items}
onListItemChange={this._onListItemChange} />
</PerfectScrollBar>
: null}
</div>
);
}
const ListComponent = ({ items, onListItemChange }) => {
return(
<ul className="pretty-list">
{items.map(item => (
<ItemComponents
item={item}
onListItemClick={onListItemChange} />
))}
</ul>
);
}
export default ListComponent;
import { render, findDOMNode } from 'react-dom';
class ListItem extends React.Component {
_onClick = () => {
let domElement = findDOMNode(this.item);
this.props.onListItemClick(domElement.getBoundingClientRect());
}
render() {
const { item } = this.props;
return(
<li>
<div id={item.id} ref={(item) => { this.item = item; }} onClick={this._onClick}>
{item.someProperty}
</div>
</li>
);
}
}
I am sorry for asking this, since I think this has been asked before. However I do not understand react enough, or at all to understand the answers people have given on other questions. Neither to implement them into the code I have.
this is the main code:
import React from 'react';
import ReactDOM from 'react-dom';
import TodoItem from './components/TodoItem';
class App extends React.Component {
componentWillMount() {
this.setState({todoList: [], inputField: ''});
}
handleInput(event) {
this.setState({inputField: event.target.value});
}
addTodo(event) {
if(this.state.inputField.length === 0 || event.keyCode && event.keyCode !== 13) return;
event.preventDefault();
var newTodo = {
text: this.state.inputField,
created_at: new Date(),
done: false
};
var todos = this.state.todoList;
todos.push(newTodo);
this.setState({todoList: todos, inputField: ''});
}
render() {
return (
<div>
<ul>
{
this.state.todoList.map(function(todo, index){
return (
<TodoItem todo={todo} key={index} />
);
})
}
</ul>
<div>
<label htmlFor="newTodo">Add Todo item</label>
<input name="newTodo" value={this.state.inputField} type="text" onKeyUp={this.addTodo.bind(this)} onChange={this.handleInput.bind(this)} />
<button type="button" onClick={this.addTodo.bind(this)} >Add</button>
</div>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
and this is the other part:
import React from 'react';
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.state = {todo: props.todo};
}
toggleDone(event) {
var currentTodo = this.state.todo;
if (currentTodo.done) {
currentTodo.done = false;
} else {
currentTodo.done = true;
}
this.setState({todo: currentTodo});
}
removeTodo(event) {
event.preventDefault();
var todos = this.state.todoList;
todos.remove(this);
}
render() {
return (
<li>
<input type="checkbox" onChange={this.toggleDone.bind(this)} />
<span className={this.state.todo.done ? 'done' : ''} >
{this.state.todo.text}</span>
<button type="button" onClick={this.removeTodo.bind(this)}
>X</button>
</li>
);
}
}
export default TodoItem;
Firstly I had the remove function in the main code, but I got an uncaught type error than because it couldn't find the bind??
And when I put it in the second part of code, I get a cannot read property "remove" of undefined error.
Any help would be awesome!
Thx upfront
Remove removeTodo function from TodoItem component and put it in App component. Pass this function as prop to TodoItem component and call this function at cross button click. Remember, bind removeTodo function to App component after moving it.