adding class or hooks inside app component - javascript

i'm new to react world, in react sites it says that hooks cannot be used inside class, my question is for example i have app.js can i have components inside it which are class based and also functional (hooks) ? for example in this App.js can 'NavBar' be functional (hooks) component and 'Page' be class component or can those have inside them class/hooks components ?
const store = configureStore({});
const Loader = () => (
<div className="App">
<div>loading...</div>
</div>
);
const theme = createMuiTheme({
palette: {
primary: yellow
secondary: black
action: {
selected: orange[600],
},
},
status: {
danger: 'ooooo',
},
});
const TranslatedApp = () => {
const { t } = useTranslation();
return (
<Provider store={store}>
<ConnectedRouter history={hist}>
<NavBar siteName={t('siteN')} />
<Page />
</ConnectedRouter>
</Provider>
);
};
const SuspendedApp = withTranslation()(TranslatedApp);
const App = () => (
<Suspense fallback={<Loader />}>
<SuspendedApp />
</Suspense>
)
const wrapper = document.getElementById("app");
wrapper ? ReactDOM.render(<App />, wrapper) : null;

Yes, you can. There are no restrictions on what type of Component a child component has to be to the Parent. Your only restrictions are within the component when you declare it as functional or class. Class Components and Functional Components both have their own advantages, and both should be utilized according to what your Component will do (however, it should be noted after version 16.8.0, most of the features that a Class component had prior to that version can be done with Functional Hooks, however there are still situations where Class components may serve your purpose better, like when dealing with a dynamic DOM).

Related

React Hook "useState" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function

I'm new in React Frontend developing. I'm trying to add a temporary drawer to my Material-UI NavBar. I've add a drawer in my code:
class Navbar extends Component {
render() {
const { authenticated } = this.props;
const [open, setOpen] = useState(false);
const handleDrawer = () =>{
setOpen(true)
}
return (
<AppBar position="fixed">
<Toolbar className="nav-container">
{authenticated ? (
<Fragment>
<IconButton edge="start" onClick={handleDrawer} >
<Menu/>
</IconButton>
<Drawer
anchor='left'
open={open}
onClose={()=> setOpen(false)}
>
<h3> Drawer</h3>
</Drawer>
<NewPost/>
<Link to="/">
<MyButton tip="Home">
<HomeIcon color = "disabled"/>
</MyButton>
</Link>
<Link to="/chats">
<MyButton tip="Chats">
<ChatIcon color = "disabled"/>
</MyButton>
</Link>
<Notifications />
</Fragment>
):(
<Fragment>
<Button color="inherit" component={Link} to="/login">Login</Button>
<Button color="inherit" component={Link} to="/signup">Singup</Button>
<Button color="inherit" component={Link} to="/">Home</Button>
{/* <Button color="inherit" component={Link} to="/user">Profile</Button>*/}
</Fragment>
)}
</Toolbar>
</AppBar>
)
}
}
Navbar.propTypes = {
authenticated: PropTypes.bool.isRequired
}
const mapStateToProps = state =>({
authenticated: state.user.authenticated
})
export default connect(mapStateToProps)(Navbar)
But this error appeared:
React Hook "useState" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function
So, I've create a constructor to handle this (before render):
constructor(props) {
super(props);
this.state = {
open: false
};
}
And when I want to change the state, I've use:
this.setState({ open: true })
But, when I triggered the Drawer (clicking the button),It does not open. What can I do?
The state hook (useState) is available with functional React Components, but in your case you are using a Class component. Functional React components normally don't have states out of the box, but useState is a new feature that will let you work around that
You can either change this to functional component, or keep it as a class component and use the state differently
E.g :
!this.state.open instead of !open
and
this.setState({open: false}) instead of setOpen(false)
https://reactjs.org/docs/hooks-state.html
Change this line const [open, setOpen] = useState(false); to this.state = { open: false };
and when setting to true just call this.setState({ open: this.state.open: true })
You have to use a functional component. Currently your component is a class component, so instead you should have something like:
const Navbar = (props) => {
const { authenticated } = props;
const [open, setOpen] = useState(false);
const handleDrawer = () =>{
setOpen(true)
}
return (
<AppBar position="fixed">...</AppBar>
);
};
I also noticed that your class component has the state parts defined in the render method. When using class components, state should be defined in the constructor instead, otherwise your state will be overwritten every render.
What about functional component instead of class component?
Functional component is recommended as you know.
I think it's easy to code and it improves performance.
const Navbar = () => {
//useState, define functions here
return (//the same as render method of your class component)
}
use function Navbar(props) in place of class Navbar extends Component
Its because, There is a rule that : React Hook "useState" cannot be called in a class component e.g. class Navbar extends Component . React Hooks must be called in a React function component e.g. function Navbar(props) or a custom React Hook function

why when using hooks do i get this error "Hooks can only be called inside of the body of a function component."?

Every time I run yarn start with the following line of code const context = useContext(GlobalContext); I run into this "Error: Invalid hook call. Hooks can only be called inside of the body of a function component." How can I fix this it's driving me mental. here's a screenshot showing the error, the dependencies and my code
here is the code for my globalState if that's whats causing it
import React, { createContext, useReducer } from "react";
import AppReducer from './AppReducer';
// initial state
const initialState = {
healthData:
{ age: "38", gender: "male", goal: "Lose", height: "180.34", weight: 80 }
}
export const GlobalContext = createContext(initialState);
// provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
return (<GlobalContext.Provider value={{
healthData: state.healthData
}}>
{children}
</GlobalContext.Provider>);
}
export default (state, action) => {
switch(action.type) {
default:
return state;
}
}
return (
<div>
<Router>
<div className='App'>
{this.state.user ? (<Nav drawerClickHandler={this.drawerToggleClickHandler} />) : (<Login_bar />)}
<SideDrawer sidedrawerClickHandler={this.sidedrawerToggleClickHandler} show={this.state.sideDrawerOpen} />
{backdrop}
<GlobalProvider>
{this.state.user ?
(< Switch >
<Route path='/settings_page' component={settings_page} exact />,
<Route path='/setup_page' component={setup_page} exact />,
<Route path='/' component={Main_page} />
</Switch>) : (<Login_page />)}
</GlobalProvider>
</div>
</Router>
</div >
);
So I've done some research into this and apparently it's likely that either my react or react-dom are not up to date. Iv tried updating both multiple time and running npm install. didn't fix my issue, I'm also almost certain that I'm calling on hooks correctly. Another thing is my global state is set up correctly because I can see it in the react chrome extension.
Thats because Hooks can not be used in a class based component. Alternatively you can create a HOC component an use it in your class based component, or covert your class component into functional component.

How to mock <AppContenxt.Consumer> in React using Jest and Enzyme

component which I'm exporting and want to test:
export default connectToStore(DefaultComponent);
connectToStore wrapper around component:
import React from 'react';
import AppContext from '../components/context/AppContext';
const connectToStore = Component => props => (
<AppContext.Consumer>
{({ state }) => (
<Component {...props} state={state} />
)}
</AppContext.Consumer>
);
export default connectToStore;
unit test calling component
it('should render view', () => {
const wrapper = render(<DefaultComponent />);
expect(wrapper.html()).toBeTruthy();
});
Error which I get:
Cannot destructure property state of 'undefined' or 'null'.
How do you test a component in general if it has a wrapper around it when being exported? How can I have the state injected hence being present in the wrapper.
Just to offer an alternative that's less complicated from a test perspective, you could have simply included a named export for the component itself as a test harness
export { DefaultComponent }
export default connectToStore(DefaultComponent)
That way your original test would still stand, you would just need to import as
import { DefaultComponent } from './defaultComponent'
And the of course when mounting mock the state prop provided by your context
const wrapper = render(<DefaultComponent state={{ ... }} />);
AppContext value property needs to be mocked:
Solution:
const wrapper = mount(
<AppContext.Provider
value={{
data,
callbackList: {}
}}
>
<DefaultComponent />
</AppContext.Provider>
);

How does container approach really works on React

I am developing an app and initially I wanted to make sure I understand correctly container paradigm in React before doing any further development.
I understand that container component or smart component is more like a data handler, no representation here. While a dump Component is just a stateless component that is mostly the UI.
An example so far what I have, currently my index.js looks as below:
ReactDOM.render(
<Provider store={store}>
<Router>
<Switch>
<Route exact path="/" component={AppComponent}/>
<Route exact path="/products" component={ListProductComponent} />
<Route path="/products/:productId" component={AddProductComponent} />
</Switch>
</Router>
</Provider>
, document.getElementById('root')
);
Container Component:
class ProductContainer extends React.Component {
constructor(props) {
super(props);
this.fetchProducts = this.fetchProducts.bind(this)
}
fetchProducts = () => {
this.props.dispatch(fetchProductsBegin());
getSitesApi.getAll(1)
.then(response => {
if (response.data) {
this.props.dispatch(fetchProductsSuccess(response.data._embedded.companies));
} else {
this.props.dispatch(fetchProductsFailure({message: "Fetching products failed"}));
}
});
};
addProduct = () => {
};
componentDidMount() {
this.fetchProducts();
}
render() {
const {products} = this.props;
return (
<div className="ProductComponent">
<h2>ProductComponent</h2>
<Button color="danger" onClick={this.fetchProducts}>Load Products</Button>
<ListProductComponent products={products}/>
<AddProductComponent/>
</div>
);
}
}
const productSelector = createSelector(
state => state.products,
items => items,
loading => loading,
error => error,
);
const mapStateToProps = createSelector(
productSelector,
(products) => ({
products,
})
);
export default connect(mapStateToProps)(ProductContainer);
Dump Component:
import React from 'react';
import './product.style.scss';
import LoadingBar from "../LoadingStatus/loading.component";
const ListProductComponent = (props) => (
<div className="ListProductComponent">
<h2>ListProductComponent</h2>
<LoadingBar loading={props.products.loading}/>
<div>
<ul>
{
props.products.items.map((item, index) =>
<li key={index}>{item.name}</li>
)
}
</ul>
</div>
</div>
);
export default ListProductComponent
Another dump component:
import React from 'react';
import './product.style.scss';
const AddProductComponent = (props) => (
<div className="AddProductComponent">
<h2>AddProductComponent</h2>
</div>
);
export default AddProductComponent
First of all, I am not sure in the router in index.js do I have to pass always container components or dump components in the component attribute. Secondly do I need to have for each list products, add product a separate containers? Since currently I am having a single product container that would for ex do the crud operations. One thing that this makes me confusing is that if I only want to show add product dump component, how can I do this? or show only list products?
I am not sure if I understand correctly the objective of containers, can anyone show a good example why would I go with container approach over normal way?

React pass props to children edge case?

My app has the structure below and I would like to pass a prop based in the Header state to the RT component. I can pass it easily with Context but I need to call an API when the prop is a certain value and Context doesn't seem to be designed for this use due to it using the render pattern.
In Header.js I render the children with this.props.children. To pass props I've tried the following patterns but nothing is working. I'm missing a concept here. What is it?
(1) React.Children.map(children, child =>
React.cloneElement(child, { doSomething: this.doSomething }));
(2) {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
(3) <Route
path="/issues"
render={({ staticContext, ...props }) => <RT {...props} />}
/>
Structure:
App.js
<Header>
<Main />
</Header>
Main.js
const Main = () => (
<Grid item xl={10} lg={10}>
<main>
<Switch>
<Route exact path="/" component={RT} />
<Route path="/projects" component={Projects} />
<Route path="/issues" component={RT}/>
<Route path="/notes" component={Notes} />
</Switch>
</main>
</Grid>
);
I would personally recommend using the React Context API to handle the user state rather than manually passing it via props. Here's an example of how I use it:
import React from 'react';
export const UserContext = React.createContext();
export class UserProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
onLogin: this.login,
onLogout: this.logout,
};
}
componentWillMount() {
const user = getCurrentUser(); // pseudo code, fetch the current user session
this.setState({user})
}
render() {
return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
)
}
login = () => {
const user = logUserIn(); // pseudo code, log the user in
this.setState({user})
}
logout = () => {
// handle logout
this.setState({user: false});
}
}
Then you can use the User context wherever you need it like this:
<UserContext.Consumer>
{({user}) => (
// do something with the user state
)}
</UserContext.Consumer>

Categories

Resources