I'm using React to import a function with a useState hook and that seems to break it. I have a version of react with hooks:
npm ls react => react#16.10.2
npm ls react-dom => react-dom#16.10.2
I can use components fine. When I include a hooks, I get the "Invalid hook call" screen.
In my library project I have:
/**
* #class ExampleComponent
*/
import * as React from 'react'
import styles from './styles.css'
export default function ThingyDefault() {
return <p>hi</p>
}
export type Props = { text: string }
export class ExampleComponent extends React.Component<Props> {
render() {
const {
text
} = this.props
return (
<div className={styles.test}>
Example Component: {text}
</div>
)
}
}
////////////////// THIS DOESN'T SEEM TO WORK //////////////////
export function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = React.useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In my project that uses that library:
import React from 'react';
import './App.css';
import ThingyDefault, {ExampleComponent, Example} from 'thingy';
const App: React.FC = () => {
return (
<div>
<ThingyDefault />
<ExampleComponent text='hello' />
{/* commenting this component out makes it work */}
<Example />
</div>
);
}
export default App;
What am I doing wrong here?
You are not adhering to the Rules of Hooks, specifically in your case, calling a hook from a standard javascript function.
Only Call Hooks from React Functions
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.
Since it seems to be a import/export issue, try changing your export to this:
const Example = () => {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = React.useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export { Example };
You're not doing anything wrong there.
It should work as intended. Check the example Stackblitz here, with the same version of React that you have.
I might re-check the app for any duplicate dependencies messing up the functionality of the hooks. Especially, it's unable to determine that your function Example(), is indeed a functional component.
Related
I want to create a component(comp) with onclick event handler. which should be handled by the component itself (i.e. inside the comp.js file).
if I use it inside the parent component we don't need to specify the event but it is handled by the component element(comp element).
is this possible. Any idea to develop this one.
in ParentComponent.js current behavior.
<NewComponent onClick={clickBehaviour}/>
I want like,
In NewComponent.js
const NewComponent.js = ()=>{
// Some code
const clickBehaviour = () =>{
// click behaviour
}
}
Is it possible in the current standards?
why you want to write your onClick event in parent component?
you can do it inside NewComponent.js easily.
just do this:
import React from 'react'
function NewComponent() {
const clickBehaviour = () =>{
// click behaviour
}
return (
<div onClick={clickBehaviour}>
//some jsx here
</div>
)
}
export default NewComponent
and use in anywhere you want to use without onClick event :
< NewComponent />
i cant understand well you situation but you can use forwardRef if you want (also can use old getElementById but using forwardRef is recommended).
import React, { useRef } from "react";
const NewComponent = React.forwardRef((props, ref) => (
<div onClick={() => alert("div 2 clicked")} ref={ref}>
div 2
</div>
));
export default function App() {
const compRef = useRef(null);
return (
<div>
<NewComponent ref={compRef} onClick={() => {
compRef && compRef.current && compRef.current.click();
}} />
</div>
);
}
In this component I trigger post loading using postsActions.getPost('1') and put it into the redux store. useSelector catches it and triggers PostPage rerender, now with header and button with onClickUse function attached that uses post.header along with the post object that it uses:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { postsActions } from '../../store/Posts';
import styles from './PostPage.module.scss';
const PostPage = () => {
const dispatch = useDispatch();
const post = useSelector((state) => state.post);
const onClickUse = () => {
console.log(`See? I am used only when post is rendered`);
console.log(`So it's fine if I use it here: ${post.header}`);
}
useEffect(() => {
dispatch(postsActions.getPost('1'));
}, []);
return (
<div className={styles.container}>
{
post &&
<div>
<h1>{post.header}</h1>
<button onClick={onClickUse}>Click me</button>
</div>
}
</div>
);
};
export default PostPage;
The problem is that typescript yells inside onClickUse at me that post can be undefined. How do I then synchronize React conditional rendering functionality and typescript without hacks from this question, like !, as etc.
You can inline
<div className={styles.container}>
{
post &&
<div>
<h1>{post.header}</h1>
<button onClick={() => {
console.log(`See? I am used only when post is rendered`);
console.log(`So it's fine if I use it here: ${post.header}`);
}}>Click me</button>
</div>
}
</div>
or if you don't want inline functions in render, you should create a component with not falsy post in props and conditionally render it.
Typescript (in handler) knows nothing about render logic in your example
So I have a component that looks like this:
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
And then I have another component that looks like this:
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container ;
Now, the thing is the {children} of the 1st component is sometimes the 2nd component, and sometimes it's not. Therefore I can't just put the CSS and HTML from the 2ndcomponent into the 1st component - which in turn would fix my problem.
But as you might be able to see, there is an onClick event in the first component. I would like it so that when that is clicked, the state from the click is send to the 2nd component and toggles the className-toggle.
Can this be achieved by doing this, or do I have to set everything up differently ?
And yes, I am quite new to React, so please don't be harsh.
Css
I would look into better methods of applying styling with css. Not sure about your project scope/tools but typically all the css files are imported in the dom root and loaded in there. This avoids creating css files for every component.
Here's 9 ways of implementing css for react.
Passing HTML
In react if you want to render component in another component instead of passing it as a child you should import it as follows.
// replace container path with actual path of Container file
// ex './Container.js'
import Container from 'container_path.js';
Now Rendering the Component is as simple as including it in the html code.
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
<Container/>
</div>
</>
);
Here's a Stack Overflow post of users importing components using react + es6 + webpack. More information on importing components is available there.
State management
In react if you have a state that is being accessed by multiple components the standard is to keep the state in the parent component.
This way you can pass the state as a prop to any children components. You can also create a function which updates this state and pass that function as a prop to the children.
ex:
import React, { useState } from "react";
import Container from "./Container.js";
import Navigation from "./Navigation.js"
const Parent = props => {
const [toggle, toggleState] = useState(false);
return (
<div>
<Container toggleState={toggleState} toggle={toggle} />
<Navigation toggleState={toggleState} toggle={toggle} />
</div>
)
}
Before continuing working on your project I would recommend researching functional components vs class components. Here's a helpful article.
Try to wrap second component to function with state from first component as argument.
Wrapper for your second component and using for first component
const putInnerComponent = (stateFromOuterComponent) => <Container toggle={stateFromOuterComponent}/>;
<Navigation children={putInnerComponent}/>
Your first component
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children(toggle)}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
Your second component
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children, toggle }) => {
//const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container;
Code Sandbox
import React, { useState, useEffect } from "react";
const Foo = () => {
console.log("render foo");
return <div> foo</div>;
};
const App = () => {
const [value, setValue] = useState(1);
useEffect(() => {
console.log("effect", value);
}, [value]);
console.log("rendering");
return (
<div>
{" "}
<Foo /> <button onClick={() => setValue(value)}>Click To Render</button>
</div>
);
};
export default App;
Now according to the React Documentation
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (emphasis mine)
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
In the example I've given, we can see that the useEffect hook doesn't fire, as described by the documentation, but my Foo component is rendering.
Why is this?
I thought that maybe the inline function causes a render - but if I change that to a memoized function using useCallback the same behaviour happens:
const handleClick = useCallback(() => setValue(value), [value]);
console.log("rendering");
return (
<div>
{" "}
<Foo /> <button onClick={handleClick}>Click To Render</button>
</div>
The bail out logic was implemented in v16.8.0 of react-dom in which react also introduced hooks, whereas your demo uses the alpha version of hooks which is why you still see a re-render being triggered even when you update with the same state
According to v16.8.0 release notes
Bail out of rendering on identical values for useState and useReducer Hooks. (#acdlite in #14569)
I'm trying to prevent a re-render when using custom hook for hours now -.-, need some help ;O|
(Dont know if I should call this custom hook or functional hoc though)
I have a MessageList component that display a SimpleMessage wrapped in WithAvatarHeader.
Here is my profiler result:
Every time I add a message to the list, all messages are rendered again.
This isn't happening when I only use SimpleMessage in MessageList
Is there a way to memo(WithAvatarHeader) ?
MessageList :
import React from "react";
import SimpleMessage from "./SimpleMessage";
import WithAvatarHeader from "./WithAvatarHeader";
const MessageList = props => {
const Message = WithAvatarHeader(SimpleMessage);
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
SimpleMessage:
import React, { memo } from "react";
const SimpleMessage = props => {
return (
<div className="simple-message">
{props.message}
</div>
);
};
export default memo(SimpleMessage);
WithAvatarHeader:
import React from "react";
const WithAvatarHeader = WrappedComponent => props => {
return <WrappedComponent {...props} />;
};
export default WithAvatarHeader;
Thanks for the help :-)
You should not declare component inside another component.
Once you move declaration outside:
const Message = WithAvatarHeader(SimpleMessage);
const MessageList = props => {
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
you will be fine.
Reason is reconciliation process that decides what's to drop, what to create and what to update.
Besides your JSX says it still same element <Message> React checks component's constructor(it does not work with text representation from JSX). And it will referentially different(since you re-declare this constructor on next render). So React drops every <Message> and create them from scratch. Keeping declaration outside your MessageList means constructor is referentially the same so React will not re-create <Message> till key is the same.