React basic list using class components not working - reactjs

New to React and trying to follow this page: https://facebook.github.io/react/docs/lists-and-keys.html
I have two components:
App.js
var React = require('react');
var NumberList = require('./NumberList');
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<h1>Hello {this.props.name} from React!</h1>
<NumberList numbers={[1,2,3,4,5]} />
</div>;
}
}
module.exports = App;
NumberList.js
var React = require('react');
class NumberList extends React.Component {
constructor(props) {
super(props);
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
}
render() {
return <ul>test {listItems}</ul>;
}
}
module.exports = NumberList;
index.js
var React = require('react');
var ReactDOM = require('react-dom');
var App =
require('./components/App');
ReactDOM.render(
<App name="Sarah"/>,
document.getElementById('app')
);
I am getting a Javascript error:
listItems not defined
Do I need to store listItems as a state somehow?
Update
I tried changing:
return <ul>test {listItems}</ul>;
to
return <ul>test {this.listItems}</ul>;
I just get a blank "test" word and no list items...

Because in your constructor, if you write
const listItems = numbers.map((number) =>
<li>{number}</li>
);
then listItems is a local variable, the render function cannot aware of it.
if you want to access via this.listItems in render, you will need to write:
this.listItems = numbers.map((number) =>
<li>{number}</li>
);
but this may not work because when your component is being constructed, the props may not have been passed down yet. You will need to write this in componentWillReceiveProps
Usually you can just write the .map in the render function, and don't forget the key:
render() {
return (
<ul>
{this.props.numbers.map((number, i) =>
<li key={i}>{number}</li>)}
</ul>
)
}
If you don't like this style, simply extract this to a function or in a variable, for example:
render() {
const listItems = this.props.numbers.map((number, i) =>
<li key={i}>{number}</li>
)
return (
<ul>{listItems}</ul>
)
}

Related

How to properly mitigate invalid hook issues in reactjs

Am trying to display my records from airtable.com using reactjs but it throws error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I have reference solution found here
link
I change the Rec class to function and then try to export it but still cannot get it to work
here is the code
import {initializeBlock, useBase, useRecords} from '#airtable/blocks/ui';
import React, { Component } from "react";
import ReactDOM from 'react-dom';
class Rec extends React.Component{
constructor(props) {
super(props);
this.state = {
id: ''
};
}
myRecords() {
alert('ok');
const base = useBase();
const table = base.getTableByNameIfExists('myfirst_table');
// grab all the records from that table
const records = useRecords(table);
// render a list of records:
return (
<ul>
{records.map(record => {
return <li key={record.id}>{record.id} </li>
})}
</ul>
);
render() {
return (
<div>
<h2>My Records</h2>
{this.myRecords()}
</div>
);
}
}
export default Rec;
UPDATED SECTION WITH SOLUTION BY MR. HAGAI
class Rec extends React.Component{
constructor(props) {
super(props);
this.state = {
id: ''
};
}
myRecords() {
alert('ok');
//const base = useBase();
//const table = base.getTableByNameIfExists('myfirst_table');
// grab all the records from that table
//const records = useRecords(table);
// render a list of records:
return (
<ul>
{records.map(record => {
return <li key={record.id}>{record.id} </li>
})}
</ul>
);
}
render() {
return (
<div>
<h2>Hello welcome to contact page</h2>
{this.myRecords()}
</div>
);
}
}
export default () => {
const base = useBase();
const table = base.getTableByNameIfExists('myfirst_table');
const records = useRecords(table);
return <Rec base={base} records={records} />
}
//export default Rec;
useBase and useRecords hooks can't be called inside a class component, but there is a little workaround you can do for not re-write code by export arrow function that will pass base and records as props
class Rec extends React.Component{
...rest class without useBaes() and useRecords() assiganing...
}
export default (props) => {
const base = useBase();
const table = base.getTableByNameIfExists('myfirst_table');
const records = useRecords(table);
return <Rec {...props} base={base} recoreds={records} />
}
now base available at this.props.base and no hook called inside a class component

React ref.current is null in componentDidMount only in Enzyme test, NOT null in real time

I am fairly new to testing React components and am struggling to test a ref created with React.createRef. I've read through this great response
am not sure that it addresses my issue. componentDidMount called BEFORE ref callback
constructor(props) {
super(props);
this.myRef = React.createRef();
console.log('this.myRef in constructor', this.myRef);
}
This console log returns null and is expected because the component has not yet rendered.
componentDidMount() {
console.log('this.myRef in component did mount', this.myRef);
}
return (
<div className="tab_bar">
<ul ref={this.myRef} className="tab__list direction--row" role="tablist">
{ childrenWithProps }
</ul>
</div>
The console log returns the ul html element in componentDidMount. This also is expected because the component has rendered.
HOWEVER,
When I am testing this component:
const children = <div> child </div>;
describe('Tab component', () => {
it('should render', () => {
const wrapper = mount(<Tab>{children}</Tab>);
const ulElement = wrapper.find('ul');
const instance = ulElement.instance().ref;
console.log('instance', instance);
expect(wrapper).toMatchSnapshot();
});
});
My console log statements in my terminal (this.myRef in my constructor and in componentDidMount) both say {current: null} and instance is undefined.
Can anyone please help me understand what is going on? Thank you!
This is only supported in React 16.3 or later I guess. And should have the 16.3 adapter installed!
Check this for the test implementation example which is copied below.
class Foo extends React.Component {
constructor(props) {
super(props);
this.setRef = createRef();
}
render() {
return (
<div>
<div ref={this.setRef} className="foo" />
</div>
);
}
}
const wrapper = mount(<Foo />);
const element = wrapper.find('.foo').instance();
expect(wrapper.instance().setRef).to.have.property('current', element);
Looking at your code the following fix should probably work. You have
to use the name of your ref, 'myRef', when accessing it from the
instance.
//Component
export default class Tab extends React.Component {
constructor(props) {
super(props);
this.myRef = createRef();
}
componentDidMount() {
console.log("TAB Component Did Mount, REF is : ", this.myRef);
}
render() {
return (
<div className="tab_bar">
<ul
ref={this.myRef}
className="tab__list direction--row"
role="tablist"
>
<li>TEST</li>
</ul>
</div>
);
}
}
//Test
describe("Tab component", () => {
it("Ref should be available in instance", () => {
const wrapper = mount(<Tab />);
const ulElement = wrapper.find("ul");
expect(ulElement.instance()).not.toBeNull();
expect(wrapper.instance()).not.toBeNull();
expect(ulElement.instance()).toBe(wrapper.instance().myRef.current);
});
});
EDIT : Working Sandbox instance
https://codesandbox.io/s/enzyme-instance-3rktt
Make sure your Adapter version and the React version is the same

MobX componentWillReact not firing

I read in the docs that mobx react provides a new lifecycle called componentWillReact. However, it seems that my class only reacts to mobx changes in the render function. componentWillReact is never triggered when my store changes.
I am sending "next" down as a prop. This app does not make use of mobx inject.
import { observer } from 'mobx-react';
#observer class QuickShopNew extends Component {
componentWillReact() {
console.log(this.props.store.next);
}
render(){
//console.log(this.props.store.next);
return(
<div>
</div>
)
}
}
As I can see your component doesn't dereference observed property in the render method. That's why mobx doesn't know that component should be rerendered and componentWillReact should be called on value change.
You can read how observer component work here
And here is simple working example on codepen
const { Component, PropTypes } = React;
const { observable } = mobx;
const { observer } = mobxReact;
// Store for state
class Store {
#observable next = 0;
increaseNext = () => this.next +=1;
}
let store = new Store();
#observer
class MyComponent extends Component {
componentWillReact() {
console.log(this.props.store.next);
}
render() {
return (
<div>
<h1>{this.props.store.next}</h1>
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<MyComponent
store={store}
/>
<button onClick={store.increaseNext}>
Increase
</button>
</div>
);
}
}
// Insert into container
ReactDOM.render(<App />, document.getElementById('app'));
I think that you should avoid of using the "componentWillReact" and use just standart Mobx services like this example is showing :
If you intended to update the observable variable with action then use computed method to send updated value into UI.
import React from 'react';
import { observable, action, computed } from 'mobx';
import { observer } from 'mobx-react';
class AppStore {
#observable next = 0;
#action updateNext = () => this.next = this.next + 1;
#computed get UI_renderValueNext() {
return this.next ? this.next : 0;
}
}
const appStore = new AppStore();
#observer
class AppComponent extends React.Component {
render(){
return (
<div>
<div>
{this.props.UI_rennderNext}
</div>
<button onClick={this.props.updateNext}>Click ME</button>
</div>
)
}
}
ReactDOM.render(
<AppComponent />, document.getElementById('root')
)

Property for react component is not defined

After running this code - I got the exception that "title" is not defined. I checked that api returns correct data. And on the debug mode I noticed that render() from Idea component is running earlier than getting the data from API. Can you explain why is it working in this way? And what options I have for resolving this issue?
Thanks
'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const client = require('./client');
class App extends React.Component {
constructor(props) {
super(props);
this.state = {map: {}};
}
componentDidMount() {
client({method: 'GET', path: '/api/maps/1'}).done(response => {
this.setState({map: response.entity._embedded.map});
});
}
render() {
return (
<Map map={this.state.map}/>
)
}
}
class Map extends React.Component {
render() {
return (
<div id="map_header">
<AddIdeaButton></AddIdeaButton>
<Idea idea={this.props.map.root}></Idea>
</div>
);
}
}
class AddIdeaButton extends React.Component {
render() {
return (
<a id="btn_add">
</a>
);
}
}
class Idea extends React.Component {
render() {
<div id="root">{this.props.idea.title}</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('react')
);
Asynchronous request for data takes some time during which React still renders Map and Idea components. You can simply render Idea conditionally when data is available:
<div id="map_header">
<AddIdeaButton></AddIdeaButton>
{this.props.map.root && (
<Idea idea={this.props.map.root}></Idea>
)}
</div>

In ReactJS is it possible to dynamically choose the component to render? [duplicate]

I have 2 types of components, for example a <Promo /> and an <Announcement />
One of my components maps over a list of items and creates either promos or announcements, how can I pass an ItemElement, rather than have to repeat the mapping based on an if statement.
current implementation
import React, { Component } from 'react'
import Promo from './Promo'
import Announcement from './Announcement'
class Demo extends Component {
render() {
const { ItemElement } = this.props
let items = null
if(ItemElement === 'Promo'){
items = this.props.items.map((p, i) => (
<Promo item={p} />
))
} else if(ItemElement === 'Announcement') {
items = this.props.items.map((a, i) => (
<Announcement item={a} />
))
}
return (
{ items }
)
}
}
desired implementation not working
import React, { Component } from 'react'
import Promo from './Promo'
import Announcement from './Announcement'
class Demo extends Component {
render() {
// this would be 'Promo' or 'Announcement'
const { ItemElement } = this.props
let items = this.props.items.map((p, i) => (
<ItemElement item={p} />
))
return (
{ items }
)
}
}
This works fine if I pass in say a 'div' or 'a' or 'span' tag, but not for my own components?
Your render() method needs to return a single element. Right now you're returning a javascript object with a single property: items. You need to contain those items in a top level element, either another Component, or a DOM element (<div> or <span> or the like).
As for passing a component in as a prop, there's no reason you shouldn't be able to do that:
class Demo extends React.Component {
render() {
// this would be 'Promo' or 'Announcement'
const { ItemElement } = this.props
let items = this.props.items.map((p, i) => (
<ItemElement item={p} />
))
return <ul>{items}</ul>;
}
}
class Promo extends React.Component {
render() {
return <li>Promo - {this.props.item}</li>;
}
}
class Announcement extends React.Component {
render() {
return <li>Announcement - {this.props.item}</li>;
}
}
const items = [
"foo",
"bar"
];
ReactDOM.render(
<Demo
ItemElement={Promo} // <- try changing this to {Announcement}
items={items}
/>,
document.getElementById('app')
);
Here's a jsbin demonstrating: http://jsbin.com/cakumex/edit?html,js,console,output

Resources