I'm trying to create a simple component and publish it to npm. Before publishing, I want to test the component out; however, whenever I consume my component in my test application, I'm getting the following: Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Here is the offending component:
MyComponent.tsx:
import React, { useState } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<div>
<pre></pre>
<button onClick={increment} name='test1'>
Increment
</button>
<button onClick={decrement} name='test2'>
Decrement
</button>
</div>
);
};
export default MyComponent;
index.ts:
import MyComponent from './MyComponent';
import './index.css';
export { MyComponent };
And this is the test application:
import React from 'react';
import './App.css';
import { MyComponent } from 'one-select';
function App() {
return (
<div className="App">
<div>
<MyComponent />
</div>
</div>
);
}
export default App;
MyComponent is imported into the test app via npm install /path/to/mycomponent-project
I've created other react apps before (but not a stand-alone component) and I've never had this issue. Any advice would be greatly appreciated.
Related
Having this error when running the program:
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 ap
import React, { Component } from "react";
import {useEffect,useState} from "react";
import ReactDOM from 'react-dom';
const App = () => {
const APP_ID = "";
const APP_KEY = "";
const exapmle = "https://api.edamam.com/search?
q=chicken&app_id=${APP_ID}&app_key=${APP_KEY}";
useEffect(()=>{
console.log("effect has been");
});
const [counter,setCounter] = useState(0);
return (
<div>
<h1>hello world</h1>
<form classname="search-form">
<input classname="search_bar" type="text"></input>
<button classname="search-button" type="submit">Search</button>
</form>
<h1 onClick = {()=> setCounter(counter+1)}> {counter}</h1>
</div>);};
export default App();
Try export default App instead of export default App().
I made a few other tweaks you can test in CodeSandbox, namely:
Using backticks to embed template literals in example
Limiting useEffect to a single execution by adding a second argument of []
import React from 'react';
import { useEffect, useState } from 'react';
const App = () => {
const APP_ID = '';
const APP_KEY = '';
const example = `https://api.edamam.com/search?q=chicken&app_id=${APP_ID}&app_key=${APP_KEY}`;
useEffect(() => {
console.log('useEffect will run once if I pass it a second argument of []');
console.log(example);
// eslint-disable-next-line
}, []);
const [counter, setCounter] = useState(0);
return (
<div>
<h1>hello world</h1>
<form className='search-form'>
<input className='search_bar' type='text'></input>
<button className='search-button' type='submit'>
Search
</button>
</form>
<h1 onClick={() => setCounter(counter + 1)}> {counter}</h1>
</div>
);
};
export default App;
I want to write an integration test to assert that a when a parent component drills certain values or properties to a child component, that component receives said values and renders them properly. Below I have two component examples and an example test. Of course, the test is not accurate, but I'm wondering how I can use enzyme to accomplish this? Thanks!
sampleComponent.js:
import React from 'react';
const SampleComponent = () => (
<div test-attr="div">
<SampleChildComponent title="Sample title" />
</div>
);
export default SampleComponent;
sampleChildComponent.js:
import React from 'react';
const SampleChildComponent = ({ title }) => <h3 test-attr="h">{title}</h3>;
export default SampleChildComponent;
sampleComponent.test.js:
import React from 'react';
import { shallow } from 'enzyme';
import SampleComponent from './sampleComponent';
import SampleChildComponent from './sampleChildComponent';
test('renders component without errors', () => {
const wrapper = shallow(<SampleComponent />);
const childWrapper = shallow(<SampleChildComponent />);
expect(childWrapper.text()).toEqual('sample title');
});
To render child components you should use mount instead of shallow:
import { mount } from 'enzyme'
import React from 'react'
import SampleChildComponent from './sampleChildComponent'
import SampleComponent from './sampleComponent'
test('renders component without errors', () => {
const wrapper = mount(<SampleComponent />)
expect(wrapper.find(SampleChildComponent).text()).toEqual('sample title')
})
Adding redux in React project (Refactor a simple project with Redux)
Consider a simple project, a counter application that works with two buttons, once for Increment and another for Decrement counter value.
In an actual scenario, we use the state for holding counter value like this:
in App.js:
import React, {Component} from 'react';
import CounterApp from './CounterApp'
class App extends Component {
render() {
return (
<CounterApp/>
);
}
}
export default App;
in CounterApp.js:
import React, {Component} from 'react';
class CounterApp extends Component {
state = {
counter: 0
};
handleIncrement = () => {
this.setState(prevState => ({
counter: prevState.counter + 1
}))
};
handleDecrement = () => {
this.setState(prevState => ({
counter: prevState.counter - 1
}))
};
render() {
return (
<div>
<button onClick={this.handleIncrement}>Increment</button>
<p>{this.state.counter}</p>
<button onClick={this.handleDecrement}>Decrement</button>
</div>
);
}
}
export default CounterApp;
A simple and basic example that implement with react class component and handled by two function handler (handleIncrement and handleDecrement)
And a state with a value, counter
I'm using prevState because of it's a best practice when you forced to use this.state. in setState!
Now, what would be this implementation with Redux?
First of all, you need to install redux and react-redux packages to your project via npm or yarn.
You can simply install them with one line of code:
npm install redux react-redux --save
or with yarn:
yarn add redux react-redux
now back to project and create 3 files with these names:
action.js, reducer.js and store.js
open action.js file. We should implement two actions for this app. One for increment and one for decrement.
in action.js
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
const increment = () => ({type: INCREMENT_COUNTER});
const decrement = () => ({type: DECREMENT_COUNTER});
export {
INCREMENT_COUNTER,
DECREMENT_COUNTER,
increment,
decrement
}
actions are simple functions that dispatched from component to redux
for changing the store(state) via reducers.
so we should change reducer.js:
import {INCREMENT_COUNTER, DECREMENT_COUNTER} from "./action";
const initialState = {
counter: 0
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case(INCREMENT_COUNTER):
return {
...state,
counter: state.counter + 1
};
case (DECREMENT_COUNTER):
return {
...state,
counter: state.counter - 1
};
default:
return state
}
};
export default reducer
There are 3 main principles of using redux:
1- Single source of truth. The state of your whole application is
stored in an object tree within a single store.
2- The state is read-only. The only way to change the state is to emit
an action, an object describing what happened.
3- Changes are made with pure functions.
according to second principles, we must assume that the state is immutable, and each case(in switch) must return state individually.
using ...state in the returned state means that if initialState will changing in future, these cases will work properly (in this example it's not necessary).
our functions in actions are pure(3rd principle)
and for last new file store.js:
import {createStore} from "redux";
import reducer from './reducer'
const store = createStore(reducer);
export default store;
now we should apply this store to our App component.
so open App.js file and made these changes:
in App.js:
import React, {Component} from 'react';
import CounterApp from './CounterApp'
import {Provider} from 'react-redux'
import store from './store'
class App extends Component {
render() {
return (
<Provider store={store}>
<CounterApp/>
</Provider>
);
}
}
export default App;
Provider wrapped the CounterApp component and will propagate store to App and CounterApp and all other child components.
finally, change the CounterApp.js:
import React, {Component} from 'react';
import {connect} from "react-redux";
import {increment, decrement} from "./action";
class CounterApp extends Component {
handleIncrement = () => this.props.dispatch(increment());
handleDecrement = () => this.props.dispatch(decrement());
render() {
return (
<div>
<button onClick={this.handleIncrement}>Increment</button>
<p>{this.props.counter}</p>
<button onClick={this.handleDecrement}>Decrement</button>
</div>
);
}
}
const mapStateToProps = state => {
const counter = state.counter;
return {counter}
};
export default connect(mapStateToProps)(CounterApp);
we are using increment & decrement actions to dispatch actions to redux.
the state was removed and instead of state we create a special function mapStateToProps' and useconnect` to connect the state to component props.
That's done!
If you need to use Global State in your project, you also can use a better and easier solution called Master-Hook
First step:
Instalation:
npm i master-hook.
Redux , react-redux , redux-thunk , reselect are already installed in the library and you need to follow the steps.
Second step:
Create ‘src/hooks.js’ file
import MasterHook from 'master-hook'
export const useMyHook = MasterHook({
storage: "myStorage",
initialState: {
myName: 'Vanda',
},
cache: {
myName: 10000,
}
})
You create your component and export it (useMyHook)
Set the initial State (initialState:...)
Set how long the value need has to stay cached in ms (cache:...)
Step 3:
Add Provider to src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import MasterHook from 'master-hook';
ReactDOM.render(
<React.StrictMode>
<MasterHook.Provider>
<App />
</MasterHook.Provider>
</React.StrictMode>,
document.getElementById('root')
);
Import MasterHook
Wrapp your file with MasterHook.Provider
Step 4
Use your hook in src/App.js
import logo from './logo.svg';
import './App.css';
import { useMyHook } from './hooks'
function App() {
const { myName, setMyName } = useMyHook()
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
My name is {myName}
</p>
<a
onClick={() => setMyName('Boris')}
className="App-link"
>
Set my name to 'Boris'
</a>
</header>
</div>
);
}
export default App;
Import your hook
useMyHook
Declare your hook
const { myName, setMyName } = useMyHook()
Use it in your code
{myName}
and
{()=>setMyName('')}
Delete href attribute to prevent it from changing the page. setMyName action is created automatically.
No need to connect to the store. It’s already connected.
Step 5
Start your project and enjoy! (npm run start)
You are connected to Redux. myName from myStorage is cached for 10 seconds. You can click the link, reload the page and make sure it is.
How would I make my component work inside another component without repeating code?
In other words, inside NextPage.js file, the <LogoutButton/> component won't carry out its function? I want the <LogoutButton/> to carry out the exact same function as it does inside the <Home/> component.
Is there a way to do this without making NextPage.js a class based component and repeating the same logic inside NextPage.js component?
Here's the Home.js file:
import React, {Component} from 'react';
import fire from '../../config/Fire';
import classes from './Home.css';
import Aux from '../../hoc/Aux';
import NextPage from '../../components/nextpage/NextPage';
import LogoutButton from '../../UI/buttons/LogoutButton';
class Home extends Component {
state = {
flag: false
}
logout() {
fire.auth().signOut();
}
goToNextPage() {
this.setState({flag: true});
}
render() {
const flag = this.state.flag;
if(flag) {
return <NextPage/>;
}
return (
<Aux>
<button
type="button"
className="btn btn-outline-primary btn-lg btn-block"
onClick={this.goToNextPage.bind(this)}>some button
</button>
<LogoutButton clicked={this.logout.bind(this)}/>
<NextPage/>
</Aux>
);
}
}
export default Home;
Here's the NextPage.js file:
import React from 'react';
import Aux from '../../hoc/Aux';
import LogoutButton from '../../UI/buttons/LogoutButton';
const nextPage = () => {
return(
<Aux>
<LogoutButton/>
</Aux>
);
}
export default nextPage;
Here's the LogoutButton.js file:
import React from 'react';
import classes from '../../UI/buttons/LogoutButton.css';
import Aux from '../../hoc/Aux';
const logoutButton = (props) => (
<Aux>
<button
className={classes.LogoutButton}
onClick={props.clicked}>Logout
</button>
</Aux>
);
export default logoutButton;
You can move your logout logic into the logout button component. Function handler is better when your logout logic becomes complex.
import React from 'react';
import classes from '../../UI/buttons/LogoutButton.css';
import Aux from '../../hoc/Aux';
import fire from '../../config/Fire';
const handleLogout = () => {
fire.auth().signOut();
}
const logoutButton = (props) => (
<Aux>
<button
className={classes.LogoutButton}
onClick={handleLogout}
>
Logout
</button>
</Aux>
);
export default logoutButton;
You don't need to duplicate all logic since your logout logic does not depend on Home component.
// NextPage.js
import React from 'react';
import fire from '../../config/Fire';
import Aux from '../../hoc/Aux';
import LogoutButton from '../../UI/buttons/LogoutButton';
const nextPage = () => {
return(
<Aux>
<LogoutButton onClick={fire.auth().signOut} />
</Aux>
);
}
export default nextPage;
Or, if fire.auth() is potentially an expensive function you can write it like this:
onClick={() => fire.auth().signOut()}
why don't you move the fire.auth().signOut(); line inside the logout button ?
import React from 'react';
import classes from '../../UI/buttons/LogoutButton.css';
import Aux from '../../hoc/Aux';
import fire from '../../config/Fire';
const logoutButton = (props) => (
<Aux>
<button
className={classes.LogoutButton}
onClick={() => fire.auth().signOut()}>Logout
</button>
</Aux>
);
export default logoutButton;
Unless I am missing something, the only usage of fire in Home component is that one. Why don't you put it in the logout component ?
Furthermore, you don't need to pass that as prop now, and it will behave the same everywhere you place it
You can pass functions that are in Home into NextPage as props e.g.
<NextPage goToNextPage={this.goToNextPage} />
then inside NextPage you can use that by calling props.NextPage()
Also if you write your goToNextPage function as an arrow function then you dont need to bind it
goToNextPage = () => this.setState({flag: true});
Problem:
I can't display the value from the state of redux, which is delivered by mapStateToProps function to the component.
Project structure:
Create-react-app CLi application built the project.
Inside of the src/ I have the following code structure
Necessary code:
The main page which we are interacting with looks like this:
Underneath it is planned to post the result of the clicking on the buttons.
So how do I bind the redux state and actions to those two components: Calculator and ResultLine?
Let me show the index.js code, where I create the store:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from './reducers/';
import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.getElementById("root")
);
There are only three actions:
import {CALCULATE, ERASE, PUT_SYMBOL} from "./types";
export const putSymbol = (symbol) => {
return {
type: PUT_SYMBOL,
payload: symbol
}
};
export const calculate = () => {
return {
type: CALCULATE
}
};
export const erase = () => {
return {
type: ERASE
}
};
And in the App.js I pass reducers, which are binded to those actions to the Calculator component:
import React, {Component} from 'react';
import Calculator from './Calculator';
import ResultLine from "./ResultLine";
import {calculate, erase, putSymbol} from "../actions/index";
import {connect} from "react-redux";
class App extends Component {
render() {
return (
<div>
<Calculator
onSymbolClick={this.props.onSymbolClick}
onEqualsClick={this.props.onEqualsClick}
onEraseClick={this.props.onEraseClick}/>
<br/>
<ResultLine result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapState', state.calc.line);
return {
result: state.line
}
};
const mapDispatchToProps = {
onSymbolClick: putSymbol,
onEqualsClick: calculate,
onEraseClick: erase
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
And that works fine. Whenever I click the button the state changes, and I observe it in the console log, called in mapStateToProps function.
So I expect, that I can deliver result prop to the Result line easily, and I pass it into the ResultLine component as a parameter. So, let's look at that element:
import React from 'react';
const ResultLine = ({result}) => {
return (
<p>{result}</p>
);
};
export default ResultLine;
And I can see no changes in a result line. Maybe, something wrong with the React/Redux lifecycle management and ResultLine component just does not update on changes in state?
There's an error on mapStateToProps.
Instead of:
const mapStateToProps = (state) => {
return {
result: state.line
}
}
Please use:
const mapStateToProps = (state) => {
return {
result: state.calc.line // calc was missing here
}
}