I'm trying to build a notification React component for my application that I can call from anywhere. I have the below code where I'm trying to do some kinda hack for exporting the showNotif function
function FloatNotification() {
const [show, setShow] = useState(false);
const showNotif = () => {
console.log(`showNotif is called`);
setShow(true);
};
if (!FloatNotification.showNotification) {
FloatNotification.showNotif = showNotif;
}
return <>{show ? `Showing notification` : `not showing notification`}</>
}
export default FloatNotification;
On another file I'm trying to call showNotif like below
import FloatNotification from "./FloatNotification";
function MyComponent() {
const {showNotif} = FloatNotification;
return <><button onClick={() => showNotif()}>Click Me</button></>
}
but the setState isn't getting called unexpectedly. I'm getting the showNotif is called message in the console. so logically the setState should also get called.
I kinda understand it's happening because of how javascript handles reference data type. but I'm not sure what's actually happening behind the scene and how to get my goal
Suggest me if you have any other ideas to build this notification component (something I can call from anywhere in my component tree). Any kind of help will be kudos to me
[NOTE: I'm actually using NextJS and I've added this FloatNotification in the _app.js. So it's available in all the pages
useState is a special function called "hooks". React hooks are only available when the component is rendered in the VDOM tree.
Since you dosen't render FloatNotification as a element, calling setState is unexpected and may has no effects.
There are several ways to achieve what you want without hacking.
First is, lift up notification state to the parent component and inject only the dispatch that changes the state is through the context.
const NotificationContext = React.createContext(() => {});
function FloatNotification({ children }) {
const [show, setShow] = useState(false);
return (
<NotificationContext.Provider value={setShow}>
{children}
<>{show ? `Showing notification` : `not showing notification`}</>
</NotificationContext.Provider>
);
}
function MyComponent() {
const setShow = useContext(NotificaitonContext);
return (
<button onClick={() => setShow(true)}>
Show Notification
</button>
);
}
function App() {
return (
<FloatNotification>
<MyComponent />
</FloatNotification>
);
}
Or, you can exposing the handler by React.useImperativeHandle (commonly not recommended)
Related
I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).
So I have a mounted React application that is rendered like this (it's via another framework on top):
const componentRender = (entryComponent, initialProps, targetNode) => {
root.render(wrapper);
}
On top level, I call a 3rd party library and wrap my React application inside of it.
What's the best way to integrate React inside of a 3rd party library that its supposed to render into?
The best practice is to update props within a component, not outside of the render function. Otherwise, as you've said, it will cause a full remount.
So the solution would be to call your Ext.extend function from within the App component.
function App() {
const [ready, setReady] = useState(false);
const [props, setProps] = useState({});
// Calls once on first mount.
useEffect(() => {
Ext.extend(Ext.Container, {
listeners: {
afterrender: function () {
setProps(this.myProps);
setReady(true);
},
},
constructor: function (...args) {
Object.assign(this, args);
},
onServerSuccess: function(data) {
// here is where I receive new data
setProps(data);
}
});
}, [])
return (
ready ? <Component {...props} /> : null
);
}
ReactDOM.render(App, rootNode);
If there is more interaction between Ext and the react component, for example you need to pass this.root into a function, you can still do that with the normal DOM APIs like document.getElementById. Or you can add it like this:
// use this reference to do stuff with the react object
this.root = <Component {...props} />
return this.root;
but changing the react component object directly would be an anti-pattern. All state/props updates should happen using APIs provided by the react library.
Also if for some reason you need to render the react component directly inside the Ext.Container node, then you should use ReactDOM.createPortal instead of render.
Here's how that would look.
import { createPortal } from 'react-dom';
const Portal = memo(({ children, domNode }) => createPortal(
children,
domNode,
));
function App() {
const [ready, setReady] = useState(false);
const [props, setProps] = useState({});
// Calls once on first mount.
useEffect(() => {
Ext.extend(Ext.Container, {
listeners: {
afterrender: function () {
setProps(this.myProps);
setReady(true);
},
},
constructor: function (...args) {
Object.assign(this, args);
},
onServerSuccess: function(data) {
// here is where I receive new data
setProps(data);
}
});
}, [])
return (
ready ?
<Portal domNode={extComponentNode}>
<Component {...props} />
</Portal> : null
);
}
Where extComponentNode is the node you want it rendered in. createPortal allows the react component to share state with your react App even though it's rendered in a different place, without the issue of calling render again.
I suppose you want to re-render the component with updated states without unmounting-mouting it whenever the prop(s) passed to the component changes.
Create a useEffect inside this component to re-render the component whenever the required prop(s) changes. To do this, add the prop(s) for which you want to re-render the component in the dependency array of useEffect and pass a function that will update the states of the component.
useEffect(
<function which will update the states, causing re-rendering of the component>,
[ <all props for which you want to run the function separated by a comma> ]
)
I'm trying to simulate a link in react js clicking on a div.
This is my code:
function handleClick(myLink){
window.location.href=myLink;
}
and here where I call it:
<Col className="aslink" onClick={handleClick('/path/to/myUrl')}>
<div>...</div>
</Col>
But it goes directly to the URL without clicking, so it starts an infinite loop.
How can I solve it?
Many thanks in advance!
This is because you are calling the function in this part <Col className="aslink" onClick={handleClick('/path/to/myUrl')}> instead of providing reference to it to be used on users click action. What you can do is define it like this:
const handleClick = (myLink) => () => {
window.location.href=myLink;
}
then it will work as you want it.
handclick('your path')
is already running the code. Try
onClick = {() => handlick('your path')}
This will stop it from automatically running
First off, I would recommend using React Router's Link over history.location.href, as it uses the routers history api, using this declarative, accessible navigation instead of history means that you are safe to any changes to the history api in the future.
You can import it like this:
import { Link } from 'react-router-dom'
Secondly, you were calling the handleClick function instead of executing the function.
If you use react-router*(which is most possible - if not - then you should research value of this)* then you can get access to browser-history via react router provider
pass router api to your component
if you use modern react version - use hook useHistory -
const Comp = () => {
const history = useHistory()
const handleRedirect = useCallback((path) => {
return () => {
history.push(path);
}
}, [])
return <div onClick={handleRedirect('path-to-page')}>Navigate</div>
}
export default Comp;
or 2. extract history object from taken props in your component
you can wrap you component by HOC - withRouter.
const Comp = ({history}) => {
const handleRedirect = useCallback((path) => {
return () => {
history.push(path);
}
}, [])
return <div onClick={handleRedirect('path-to-page')}>Navigate</div>
}
export default withRouter(Comp)
I made custom hook and used in interceptors which is not react component.it's javascript function so i got this error-
Invalid hook call. Hooks can only be called inside of the body of a function component.
And this is unfortunate because it prevents us from using newer hook-based modules in our older class-based components and regular javascript functions.
So now i want to be convert custom hook into HOC.
const useLoader = props => {
const [loading, setLoading] = useState(props);
return [
loading ? <Loader /> : null,
() => setLoading(true), // show loader
() => setLoading(false) // hideLoader
];
};
export default useLoader;
How can i do that i have never write HOC.
Here is the code
So the requirement is when i click on proceed button loader will be visible on the screen on api call after getting the response then only loader will disappear and navigate to the next page.
Here is updated code where i have updated with HOC but
loader seems works only first time when i load the page.
what i am missing here ?
Updated with HOC - codeSandboax
You can try like this.
const LoaderHOC = ({ Component }) => {
const [loading, setLoading] = useState(props);
const toggleLoading = () => {
setLoading(!loading)
}
return (
loading ? <Loader/> : <Component toggleLoading={toggleLoading}/>
)
};
export default useLoader;
I think you will get an idea.
Well, i have this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I tried alot of different options to fix this, but i failed.
Here is my code
export const DataInput = () => {
const Post = (testTitle, testText) => {
useFirestore().collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}
return(
<Button
variant="primary"
onClick={()=> Post(testTitle, testText)}>
POST data
</Button>
Deleted some of code that does not matter
Hooks can only be called while rendering a component, so they need to be in the body of your component.
export const DataInput = () => {
const firestore = useFirestore();
const Post = (testTitle, testText) => {
firestore.collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}
// etc
}
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, explanation available here)
According to you code samle I may suggest that testTitle, testText available in DataInput in some way, thus you may create onClick handler with useCallback. React will create callback for use as handler, and re-create only when testTitle, testText changed.
import {useCallback} from 'react';
export const DataInput = () => {
const makePost = useCallback(() => {
useFirestore().collection('schedule-data').doc('test').set({
testTitle: testTitle,
testText: testText
})
}, [testTitle, testText]);
return (
<Button
variant="primary"
onClick={makePost}
{/* Avoid inline callback declaration */}
>
POST data
</Button>
)
}