I'm trying to update (setState) a React functional component from within "regular" (vanilla) JavaScript.
I searched through StackOverflow but all the answers deal with passing data from React to (vanilla) JavaScript, and not the other way around.
Let's take the example from the docs:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
To render it in JavaScript, I do:
let example = ReactDOM.render(
<Example />,
document.getElementById('example-wrapper')
);
Now suppose I want to manually update the count from the vanilla JavaScript code, outside of react. Like:
function updateExampleCount(newCount) {
example.setCount(newCount); // ???
}
I can't access the component state, as setCount is a private variable inside the function, and example returned from render is null.
If I use a class component, then render returns a reference to the component and then I can call example.setState. But I prefer not to convert my component into a class if I can avoid it.
The docs for render say:
Render a React element into the DOM in the supplied container and return a reference to the component (or returns null for stateless components).
But my component does have a state (count), it just doesn't recognize it.
If it's not possible to use the return value from render, is there another way to "get" the component and then use setCount (or some other way to set the state)?
Or do I just have to use a class component for this?
Thanks.
There is no way to access the state from outside the component. It's like trying to access a locally scoped variable from outside a function.
Using a class component wouldn't help either since you wouldn't be able to get hold of the instance of the class created inside the React app.
If you want to trigger a state change from outside the application, then the application needs to provide an event handler.
For (a really quick and dirty) example:
const outside = {
value: 2,
callbacks: [],
addCallback: function (callback) { this.callbacks.push(callback); },
setValue: function (value) {
this.value = value;
this.callbacks.forEach(
callback => callback(this.value)
);
}
};
function Component = () => {
const [val, setVal] = useState(outside.value);
useEffect(() => {
outside.addCallback((value) => setVal(value));
}, []);
return <p>{val}</p>
}
It is possible. You could pass your setCount function as a parameter to use it in your JS outside of React - but I would not really recommend this.
I would recommend that you keep your business logic and React logic separate.
The only things that need to be aware of state and will be using it are React components themselves. I would structure your code in a way that it is not coupled to React, and does not have to use or depend on React state in any way.
This is easier said than done at the beginning. If you need help with it, maybe provide a use case that you are trying to solve in this way, and a better answer might be provided.
It can be done by extending Example so it will pass a reference to the setCount function back to the parent code, see below. (This might be the same as what Oli mentioned, if so then I had the same idea and made a working implementation before answering)
const { useState } = React;
// functionFromComponent will store the function from Example.
let functionFromComponent = undefined;
const setter = (someFn) => functionFromComponent = someFn;
const Example = ({ setFunction }) => { // take `setFunction` from props
const [count, setCount] = useState(0);
setFunction(setCount); // pass setCount to the parent code
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
ReactDOM.render(
<Example setFunction={setter} />,
document.getElementById('example-wrapper')
);
function buttonClicked() {
if (functionFromComponent) {
functionFromComponent(777);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="example-wrapper"></div>
<button id="regularButton" onclick="buttonClicked()">Regular button</button>
Related
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)
I want to do something like this:
const GreetingWithCounter = (props) => {
const { name, count } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>
{count}
</button>
</div>
);
}
<GreetingWithCounter name="Alice" count={0} />
Ie. I want to re-render a component with new values for its props. Is there a way to do that? Looking through these three questions, I'm seeing ways to re-render a component but not with new values for props (1, 2, 3).
Context
I'm thinking about a way to simplify React. I really like the mental model of React being the view layer in MVC, where UI = F(state). But things can get confusing when "state" can come from so many different places: props, useState, useReducer, "raw" useContext, Redux (which uses useContext I think), whatever else.
What if everything was just based off of props?
For local state you'd do what I did in that example above. You'd initialize the local state of count when doing <GreetingWithCounter name="Alice" count={0} /> and then update it by re-rendering. This means less DRYness because you'd have to repeat the count={0} code instead of only having it once inside of GreetingWithCounter.
You'd have to do prop drilling instead of useContext stuff.
This approach would probably make React slower.
Still, I hypothesize 1) that the mental model of having everything coming from props is simpler and 2) that pro outweighs the cons in a non-trivial amount of apps.
Props are not supposed to be mutated in React. That is precisely the difference between props and state. The React way to do this is to use state for the count. You can pass the initial state of the count as a prop and do this: const [count, setCount] = useState(initialCount). Your onClick handler would then increment count, which again is state. I realize that this is not what you want but it's how React works.
In React Props values cannot be changed in child component but we can do it in parent component.
const GreetingWithCounter = (props) => {
const { name, count, updateCount } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={updateCount}>{count}</button>
</div>
);
};
function App() {
const [count, setCount] = useState(0);
const updateCount = () => {
setCount(count + 1);
};
return (
<div className='App'>
<h1>Greeting With Counter:</h1>
<GreetingWithCounter
name='Alice'
count={count}
updateCount={updateCount}
/>
</div>
);
}
Appreciate the change you want to point out and value you want to add but there might be some points that you're missing what React conceptually trying to provide with seperation between props and state.
The props within components coming with React are specifically conceptually designed to be immutable as per the documentation
here.
So what you're trying to do is conceptually not ok for that purpose and violating what React tries to accomplish.
Infact you may mention about creating another library/framework which successfully getting it done while introducing props are the new state concept but in this specific case, there's no possible way to succeed on it in a React way.
You cannot change value of props in child but you have 2 ways to handle it
first, I assume that you only want to use count in child component and you don't need count value in parent, in this case you can use props.count as initial state, sth like this :
const GreetingWithCounter = props => {
const [count, setCount] = useState(props.count);
const { name } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => setCount(prevState => prevState + 1)}>{count}</button>
</div>
);
};
<GreetingWithCounter name="Alice" count={0} />;
but if you wanna access it's value from parent, it's better to pass setter to child
sth like this :
const GreetingWithCounter = ({name,count,setCount}) => {
return (
<div>
<div>Hello {name}</div>
<button onClick={() => setCount(prevState => prevState + 1)}>{count}</button>
</div>
);
};
const App = ()=>{
const [count, setCount] = useState(0);
return (<GreetingWithCounter name="Alice" count={count} setCount={setCount} />)
}
or if it's child is so deep that you need to send props to all it's tree, its better to use state management like Redux,Context or ...
Is this the way you want to do ? :
import React from 'react'
import ReactDOM from 'react-dom'
export default function renderComponent(Component, props, container) {
ReactDOM.render(<Component {...props} />, container)
}
What you are trying to do goes against the philosophy of state management of react. For correct way to do it, you can check other answers, and even you yourself have posted it in the questions.
But if you really want to do it, behind its magic, React is also just JavaScript. Therefore, we just need to implement the render function outside of React way of thinking. We know that React re-renders on state change magic or on props change. We need to just somehow connect the render method you asked for with set state. Something like the below should work.
const ParentStuff = () => {
const [props, setProps] = useState({ name: "Alice", count: 0 });
render = setProps;
return (<GreetingWithCounter name={props.name} count={props.count} />);
}
let render;
const GreetingWithCounter = props => {
const { name, count } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>{count}</button>
</div>
);
};
A lot of people will scream though at code above. It definitely strays away from the intended use.
If you want to go further, you can also just have one state for the entire app, and pass this state fo every component. And voila! You just created a singleton state and an uni directional data flow, which is a poor man version of the redux and this will probably kill performance of the webapp, as things like typing each letter in a textbox will re-render the entire page.
As others already mentioned, component is either controlled or uncontrolled (or mix of both) in react.
If you keep state in component itself - it's uncontrolled. You can reset its state to internal by changing key prop from parent though.
If you keep state in parent - it's controlled component and changes it's state through props/callbacks.
What you have shown in your example, you want to achieve uncontrolled component with some syntactic sugar on top.
Example implementation:
const usePropsWithRender = (props) => {
const [currentProps, setCurrentProps] = useState(props);
return {
...currentProps,
render: setCurrentProps,
};
};
const GreetingWithCounter = (props) => {
const { name, count, render } = usePropsWithRender(props);
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>
{count}
</button>
</div>
);
};
You can reuse usePropsWithRender through all you project, but it's nothing more than a thin wrapper around useState. I don't see how it is better than using useState directly.
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).
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>
)
}
My code has a component that takes both props and has its own internal state.
The component should rerender ONLY when its props change. State changes should NOT trigger a rerender.
This behaviour can be implemented with a class based component and a custom shouldComponentUpdate function.
However, this would be the first class based component in the codebase. Everything is done with functional components and hooks.
Therefore I would like to know whether it is possible to code the desired functionality with functional components.
After a few answers that didn't approach the real problem, I think I have to reformulate my question. Here is a minimal example with two components:
Inner takes a prop and has state. This is the component in question. It must not rerender after state changes. Prop changes should trigger a rerender.
Outer is a wrapper around inner. It has no meaning in the scope of this question and is only there to give props to Inner and to simulate prop changes.
To demonstrate the desired functionality I have implemented Inner with a class based component. A live version of this code can be found on codesandbox. How can I migrate it to a functional component:
Inner.tsx:
import React, { Component } from 'react'
interface InnerProps{outerNum:number}
interface InnerState{innerNum:number}
export default class Inner extends Component<InnerProps, InnerState> {
state = {innerNum:0};
shouldComponentUpdate(nextProps:InnerProps, nextState:InnerState){
return this.props != nextProps;
}
render() {
return (
<button onClick={()=>{
this.setState({innerNum: Math.floor(Math.random()*10)})
}}>
{`${this.props.outerNum}, ${this.state.innerNum}`}
</button>
)
}
}
Outer.tsx:
import React, { useState } from "react";
import Inner from "./Inner";
export default function Outer() {
const [outerState, setOuterState] = useState(1);
return (
<>
<button
onClick={() => {
setOuterState(Math.floor(Math.random() * 10));
}}
>
change outer state
</button>
<Inner outerNum={outerState}></Inner>
</>
);
}
The official docs say to wrap the component in React.memo. But this doesn't seem to work for preventing rerenders on state change. It only applies to prop changes.
I have tried to make React.memo work. You can see a version of the code with both Outer and Inner being functional components here.
Related questions:
How to use shouldComponentUpdate with React Hooks? : This question only deals with prop changes. The accepted answer advises to use React.memo
shouldComponentUpdate in function components : This question predates stateful functional components. The accepted answer explains how functional components don't need shouldComponentUpdate since they are stateless.
React memo do not stop state changes
React.memo only checks for prop changes. If your function component
wrapped in React.memo has a useState or useContext Hook in its
implementation, it will still rerender when state or context change.
Ref:- https://reactjs.org/docs/react-api.html#reactmemo
Your Inner component depends on the property num of the Outer component, you can't prevent it from rendering on property change as React.memo makes properties comparison:
// The default behaviour is shallow comparison between previous and current render properties.
const areEqual = (a, b) => a.num === b.num;
export default React.memo(Inner, areEqual);
By memoizing the Inner component and removing the num dependency, it won't render on Outer rendering, see sandbox attached.
export default function Outer() {
const [outerState, setOuterState] = useState(1);
return (
<>
...
// v Inner is memoized and won't render on `outerState` change.
<Inner />
</>
);
}
If you want to implement shouldComponentUpdate with hooks you can try:
const [currState] = useState();
// shouldUpdateState your's custom function to compare and decide if update state needed
setState(prevState => {
if(shouldUpdateState(prevState,currState)) {
return currState;
}
return prevState;
});
React is by design driven by setState -> re-render loop. Props change is in fact a setState somewhere in parent components. If you don't want the setState to trigger a re-render, then why in the first place use it?
You can pull in a const state = useRef({}).current to store your internal state instead.
function InnerFunc(props) {
const state = useRef({ innerNum: 0 }).current;
return (
<button
onClick={() => {
state.innerNum = Math.floor(Math.random() * 10);
}}
>
{`${props.outerNum}, ${state.innerNum}`}
</button>
);
}
That said, it's still a valid question to ask: "how to implement shouldComponentUpdate in a react hook fashion?" Here's the solution:
function shouldComponentUpdate(elements, predicate, deps) {
const store = useRef({ deps: [], elements }).current
const shouldUpdate = predicate(store.deps)
if (shouldUpdate) {
store.elements = elements
}
store.deps = deps
return store.elements
}
// Usage:
function InnerFunc(props) {
const [state, setState] = useState({ innerNum: 0 })
const elements = (
<button
onClick={() => {
setState({ innerNum: Math.floor(Math.random() * 10) });
}}
>
{`${props.outerNum}, ${state.innerNum}`}
</button>
);
return shouldComponentUpdate(elements, (prevDeps) => {
return prevDeps[0] !== props
}, [props, state])
}
Noted that it's impossible to prevent a re-render cycle when setState is called, the above hook merely makes sure the re-rendered result stays the same as prev rendered result.
you should use the event that provide the browser and capture in the function before setState, like this
function setState = (e) =>{ //the e is the event that give you the browser
//changing the state
e.preventDefault();
}