How to properly mitigate invalid hook issues in reactjs - 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

Related

Create a removable React component without having to re-create remove function

I am looking to create a "delete-able" / removable React component that I can use in multiple different places.
From researching, I can see it is kind of an anti-pattern to create a component that deletes itself and the correct way to do things is for the parent to manipulate the child components rather than child components modifying themselves.
This has led me to write code somewhat along the following lines:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: [ XXX ]
};
}
removeFunc = (index) => {
const test = this.state.data.filter((_,i) => i !== index);
this.setState({data: test});
}
render() {
return (
<div>
{this.state.data.map((el,i) =>
<ChildComponent removeFunc={() => this.removeFunc(i)}/>
)
}
</div>
);
}
}
export default ParentComponent;
class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
removeFunc: props.removeFunc
};
}
render() {
return (
<div>
<button onClick={this.state.removeFunc}>Delete Me</button>
</div>
);
}
}
export default ChildComponent;
The issue I have with this is that I have to keep re-writing the removeFunc function in every parent component.
I am VERY new to React, so I'm just curious if there is there a better / different way to do this or is this the correct way?

Sharing store change event between same hierarchical level child components

I am developing a simple React JS application for learning purpose. I just started learning React JS a few days ago. Now, I am having a problem with Flux Store. I need to share the change event across two child components on the same hierarchical level.
I have the parent component, called TodoComponent with the following definition
//Create
class TodoComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<div>
<ListComponent />
</div>
<AddItemComponent />
</div>
)
}
}
It has two child components called, ListComponent and the AddItemComponent. Moreover, I have a store with this definition.
import { EventEmitter } from 'events';
class DataStore extends EventEmitter{
constructor()
{
super();
this.todos = [
"Eat",
"Sleep",
"Die",
"Shower"
];
}
getAll(){
return this.todos;
}
addItem(newItem)
{
this.todos.push(newItem);
this.emit("change")
}
}
const dataStore = new DataStore;
export default dataStore;
It has a function for adding new item into the array and a function for fetching the array.
This is the ListComponent that is displaying the array of items from the DataStore flux store.
import React from 'react';
import TodoItem from './TodoItem';
import DataStore from './data.store';
class ListComponent extends React.Component{
constructor(props)
{
super(props)
this.state = { todos : DataStore.getAll() };
}
componentWillMount(){
DataStore.on('change', () => {
//do somethif
this.state = { todos : DataStore.getAll() };
})
}
render()
{
var deleteItem = (item) => {
this.deleteItem(item);
}
var editItem = (item) => {
this.editItem(item);
}
var addItem = (newItem) => {
this.addItem(newItem);
}
var todos = this.state.todos.map((item, index) => {
return (
<TodoItem item={item} addItem={addItem.bind(this)} deleteItem={deleteItem} editItem={editItem} />
)
});
return (
<ul>
{todos}
</ul>
)
}
deleteItem(item)
{
this.setState({ todos: this.state.todos.filter((listItem, index) => {
return listItem !== item;
}) });
}
editItem(item)
{
alert(item)
}
addItem(newItem)
{
DataStore.addItem(newItem);
}
}
module.exports = ListComponent;
It is updating the items in the change event of the DataStore store. But I am not calling the addItem function in the ListComponent. I am calling it in the AddItemComponent.
This is the definition of the AddItemComponent.
import React from 'react';
import DataStore from './data.store';
class AddItemComponent extends React.Component{
constructor(props)
{
super(props)
}
render()
{
return (
<form id="form-todo" onSubmit={this.addItem.bind(this)} action="post">
<input type='text' ref="newItem" />
<button>ADD</button>
</form>
);
}
addItem(e)
{
e.preventDefault();
DataStore.addItem(this.refs.newItem.value);
}
}
module.exports = AddItemComponent;
But when I trigger the addItem function in the AddItemComponent, the change event of the DataStore in the ListComponent is not triggered. Therefore, how can I synchronize the change event of Flux Store between two components exist on the same hierarchical level?
The solution I can think of is having the DataStore in the TodoComponent (parent component) and send the data and functions as props to the child component. I think, the code will become a bit messy in that way. Is that the only solution to do that?
Welcome to React! I recreated your example and your "change" event is firing in ListComponent, but to update the state in a component you should use this.setState(changes) rather than this.state = {changes}. Only use this.state = {} in the constructor to set the initial state. The setState method properly flows through the React lifecycle and causes the component to re-render using the new state. There is an official guide on React's state and lifecycle hooks here.

Passing data into another component

I'm trying to pass data into another component, but I'm having trouble re-rendering state so it's not a blank array when it passes the component.
Movie Card Component:
import React, { Component } from 'react';
import getMovies from './MovieAPI.js';
import MoviePoster from './MoviePoster.js';
class MovieCardII extends Component {
constructor() {
super();
this.state = {
movies: [],
};
}
componentDidMount() {
getMovies().then(results => {
this.setState(results.Search: movies)
console.log("state", this.state);
console.log(results.Search);
});
}
render() {
const { movies } = this.state;
return (
<div>
<h1> Hi </h1>
<MoviePoster movies={movies} />
</div>
);
}
}
export default MovieCardII;
MoviePoster Component
import React from 'react';
import PropTypes from 'prop-types';
const MoviePoster = props => {
const { movies } = props;
console.log(movies);
return (
<div>
{movies.map(movie => (
<div>
<h1> {movie.Poster} </h1>
</div>
))}
</div>
);
};
MoviePoster.propTypes = {
movies: PropTypes.array.isRequired,
};
export default MoviePoster;
I'm using the OMDB API and the MovieCard component is able to get a state with an array list of 10 once I am able to get the get request in.
But in the MoviePoster component, the movie array remains an empty array.
New to React so I'm struggling to understand how to pass data into another component. I am able to get the create the view I want if I don't have to pass data to another array, but I need to create one since the API i'm using is not able to get all the information I need to make another API request using the movie ID in another component later. So basically the set up will be
movieCard is the parent
movie poster, and movieInfo will be the children.
The movieInfo will pull another API request using the imdbID that I get from the movieCard component.
The way you have set the state is wrong
Movie Card Component:
componentDidMount(){
getMovies().then(results=> {
this.setState(results.Search: movies)
console.log("state", this.state);
console.log(results.Search);
});
}
Solution: Movie Card Component
componentDidMount(){
getMovies().then(results=> {
this.setState({movies: results.Search})
console.log("state", this.state);
console.log(results.Search);
});
}
One more change is required to MoviePoster Component
You need to specify key whenever you are looping
const MoviePoster = (props) => {
const { movies } = props;
console.log(movies);
return (
<div>
{movies.map(movie => (
<div key={movies.id}>
<h1> {movie.Poster}</h1>
</div>
))}
</div>
)
}
Hope this solution will help you.

How to avoid writing the same logic in components having a different layout?

How can I avoid writing the same code when two components share some same methods but have a different layout?
The sample components below have a method "renderLastItem" which uses prop "something" passed by the parent components.
I thought about using Higher Order Component Pattern but I'm not sure I I can pass props as an argument to Higher Order Component.
The sample code below is very simple, so in this sample code, I just need to use If statement and change the layout according to the type of components, but in real code, I have more codes and I want to avoid using if statement in order to change the layout according to the type of a component.
How can I avoid writing the same logic in multiple components?
ComponentA
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const propTypes = {};
const defaultProps = {};
class SampleA extends Component {
constructor(props) {
super(props);
}
renderLastItem() {
if(!this.props.something) {
return null;
}
return this.props.something[this.props.something.length - 1];
}
render() {
return (
<div>
<h1>Something</h1>
<p>{this.renderLastItem()}</p>
</div>
);
}
}
SampleA.propTypes = propTypes;
SampleA.defaultProps = defaultProps;
export default SampleA;
ComponentB
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const propTypes = {};
const defaultProps = {};
class SampleB extends Component {
constructor(props) {
super(props);
}
renderLastItem() {
if(!this.props.something) {
return null;
}
return this.props.something[this.props.something.length - 1];
}
render() {
return (
<div>
<ul>
<li>Something</li>
<li>Something else</li>
<li>{this.renderLastItem()}</li>
</ul>
</div>
);
}
}
SampleB.propTypes = propTypes;
SampleB.defaultProps = defaultProps;
export default SampleB;
You absolutely can pass props to a Higher-Order Component! A HOC is simply a function that takes a Component as an argument and returns another Component as a result. So you could create a Higher-Order withLastOfSomething Component just like this:
function withLastOfSomething(Component) {
return function({something, ...otherProps}) {
const item = something ? something[something.length - 1] : null;
return <Component item={item} {...otherProps} />;
}
}
Or with ES6 arrow functions, even more compactly like this:
const withLastOfSomething = (Component) => ({something, ...otherProps}) => {
const item = something ? something[something.length - 1] : null;
return <Component item={item} {...otherProps} />;
}
And then use it like this:
const SampleBWithLastOfSomething = withLastOfSomething(SampleB);
return (<SampleBWithLastOfSomething something={...} />);
You can separate the function that takes the passed props and executes the logic,
export default renderLastItem = (passedProps) => {
if(!passedProps) {
return null;
}
return passedProps [passedProps.length - 1]
}
then import it wherever you need, like this:
import renderLastItem from './somewhere'
export default class SampleA extends Component {
render() {
return (
<div>
<h1>Something</h1>
<p>{renderLastItem(this.props.something)}</p>
</div>
)
}
}

React basic list using class components not working

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

Resources