I have a React component (functional) that contains a child component modifying the state of the parent component. I am using the hook useState for this.
After the state change, there is a "Next" button in the parent component that executes a function referencing the updated state. The problem is this next function uses the old state from before the state was modified by the child component.
I can't use useEffect here as the function needs to execute on the click of the "Next" button and not immediately after the state change. I did some digging about JavaScript closures, but none of the answers address my specific case.
Here's the code
const ParentComponent = () => {
const [myState, setMyState] = useState(0);
const handleNext = () => {
console.log(myState); // prints 0 which is the old value
}
return (
<ChildComponent modifyState = {setMyState} />
<Button onClick={handleNext} > Next </Button>
)
}
export default ParentComponent;
BTW there are no errors.
It's a little difficult to understand without your ChildComponent code. setMyState suggests that you need to update the increase the state by one when you click the next button, but you can't do that without also passing in the state itself.
An easier (imo) solution is to pass a handler down to the child component that is called when the next button is clicked. The handler then sets the state.
const {useState} = React;
function ChildComponent({ handleUpdate }) {
function handleClick() {
handleUpdate();
}
return <button onClick={handleClick}>Click</button>
}
function Example() {
const [myState, setMyState] = useState(0);
function handleUpdate() {
setMyState(myState + 1);
}
function handleNext() {
console.log(myState);
}
return (
<div>
<ChildComponent handleUpdate={handleUpdate} />
<button onClick={handleNext}>Next </button>
</div>
)
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<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="react"></div>
try to modify like this
<ChildComponent modifyState={(value) => setMyState(value)} />
Related
I am totally blank on how to use a function that is inside a component and needs to be used in another component.
Here is a simple program:
Test.js
export default function Test(){
const testFunc = () => {
console.log("it is working")
}
return(
<div>
Hi
</div>
)
}
Test2.js
export default function Test2(){
return(
<button onClick={}> // Here I want to use testFunc() from Test file
Click
</button>
)
}
Could someone please explain how can it be achieved to access the function in Test2 file.
Thanks in advance!!
You will want to pass the function as a prop to the child component. You can't or I should say shouldn't pass a prop to a parent, you can do this but is not a react way and never recommended. What you would do in this case is but the logic in the parent because both siblings are needing access to it.
const App = () => {
const clickHandler = () => {
alert("Click event")
}
return (
<div className="App">
<ChildOne clickHandler={clickHandler}/>
<ChildTwo clickHandler={clickHandler}/>
</div>
)
}
}
You can either pass it down from a parent component, shown below, or you can use a custom hook
Parent Component:
import Child from './Child.js'
export default function Parent() {
const functionToBePassed = () => { ... }
return (
<Child func={functionToBePassed}>
)
}
Or you can do it via a custom hook
Two files, first one is the hook
export default function useFunc() {
const functionToBeShared = () => {...}
return { functionToBeShared }
}
//this is any component that wants to use the hook
import useFunc from ./useFunc;
export default function ComponentThatUsesHook() {
const {functionToBeShared} = useFunc();
}
Welcome to the React community.
To use a function that is inside a component and needs to be used in another component.
You need a common parent, that handles the function.
Let's say you have the following structure.
export const ParentComponent = () => {
return <>
<Test1 />
<Test2 />
<>
}
If you want some function in Test1 to affect Test2, then you do what in react is called lifting state up https://reactjs.org/docs/lifting-state-up.html
ParentComponent
export const ParentComponent = () => {
const [value, setValue] = useState('')
return <>
<Test1 setValue={setValue} />
<Test2 value={value} />
<>
}
Test1
export const Test1 = (props) => {
return <>
<input onChange={(e) => props.setValue(e.target.vale} />
<>
}
Test2
export const Test2 = (props) => {
return <p>{props.value}</p>
}
When a component renders another component, it is called the parent of the rendered child. Imagine React as a tree data structure where the App.tsx/jsx will be the tree's root.
Inspecting the code above, we can see that we have a function held in the parent. This is the function you would probably consider putting in Test1. However, if you need to use it in another component, that is not a child of the current element. You will need to find the nearest common parent and pass the functionality down like in the example above.
I hope it makes sense. If not, I recommend glancing at the Main Concepts part of the official React documentation. https://reactjs.org/docs/getting-started.html
As Konrad said in the comments, this can't be possible since these 2 components lack no relationship (Neither Components are rendering or calling each other within)
Something you could do is Render the Test2.js component within Test.js and add a callback like so:
Test.js
import Test2 from '...';
export default function Test(){
const testFunc = () => {
console.log("it is working")
}
return(
<div>
Hi
<Test2 callbackProp={testFunc} />
</div>
)
}
Test2.js
export default function Test2({callbackProp}){
return(
<button onClick={() => {callbackProp();} }> // Here I want to use testFunc() from Test file
Click
</button>
)
}
Now whenever Test.js is rendered, it will also render Test2 (Since Test is rendering a Test2 Component) but whenever you click the button within Test2, it will execute the callback which is a function passed from Test
Nonetheless though, it's impossible to call any functions from another Component without passing down a prop like this (for future reference)
Solution
Usually, context is used to share the same state between many components that aren't in parent-children relations.
codesandbox
Creating context
First, create a context:
const MyContext = createContext();
And context provider:
const MyContextProvider = ({ children }) => {
const [myState, setMyState] = useState(0);
return (
<MyContext.Provider value={{ myState, setMyState }}>
{children}
</MyContext.Provider>
);
};
And context hook (for convenience):
const useMyContext = () => useContext(MyContext);
Using context
Remember to use the provider in the common ancestor of the components:
function App() {
return (
<MyContextProvider>
<Component1 />
<Component2 />
</MyContextProvider>
);
}
Create your components:
function Component1() {
// as you can see, you can access the function here
const { setMyState } = useMyContext();
return (
<button onClick={() => setMyState((state) => state + 1)}>click me</button>
);
}
function Component2() {
// and the value here
const { myState } = useMyContext();
return myState;
}
Hello thanks for the help, with the following code in react I want to replicate the click button every time I click on it. Currently nothing happens, but if the console shows that the array grows. Here the sandbox:
https://codesandbox.io/s/charming-glitter-0ntxmj?file=/src/App.js
const { useEffect, useState } = React;
function App() {
const btn = [
<button
onClick={() => {
btn.push(btn[0]);
console.log(btn);
}}
>
Click
</button>
];
/* useEffect(()=>{
btn
},[btn])*/
return (
<div className="App">
<div>
{btn.map((e) => {
return e;
})}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Updating a local variable in a function component doesn't work that way. It behaves just like a normal function, btn only exists during the execution of App().
In order to persist values across renders you need to use state. However, updates to state and props are the only things that cause rerenders in the first place, so App is probably only being rendered one time at the beginning.
If you convert this directly to use state, you will run into the anti-pattern of storing components in state. To avoid that, we should modify the logic to only store some generic items in the state array so that our rendering logic can us it to determine how many buttons to render.
Consider the following option:
const { useState } = React;
function App() {
const [btns, setBtns] = useState(['value'])
function handleAdd() {
setBtns((prev) => ([...btns, 'value']));
}
return (
<div className="App">
<div>
{btns.map((e) => (
<button
onClick={handleAdd}
>
Click
</button>
))}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can simplify even more by only storing the count of buttons in your state:
const { useState } = React;
function App() {
const [btnCount, setBtnCount] = useState(1)
function handleAdd() {
setBtnCount(btnCount + 1);
}
return (
<div className="App">
<div>
{Array.from({ length: btnCount}).map((e) => (
<button
onClick={handleAdd}
>
Click
</button>
))}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Yes, changes are made to your component, and yes you can see them in the console, but
App function is just a function and each time it run it will create the array and it will always have the same value (one element).
React will not update unless state or props has changed for a component.
even if your App function update, in react an update means React will re-run the same function again (you endup always with one element in the array).
So to solve this issue you can use state, even thow App is still a function, the useState will garenty that if the value of the array change, React will remeber the changes for you, and React also will re-render your component when state changes.
Keep in mind also that you can change the value of the state only using the provided function setBtns and mutating the btns will not update your component, and mutation of state is always source of bugs.
const [btns, setBtns] = useState([
<button
onClick={() => setBtns((prev) => [...prev, prev[0]])}
>
Click
</button>
]);
I'm new to react and have encountered an issue which I haven't found any solution to for a while now.
The NEXT button is a child component of Form1 and is declared in App.js. The parameters of Form1 file is (props, {transform, fram}). The intention with props is to declare {props.children} in Form1 so that it allows the Next button to be shown by being implemented in App.js.
When implemented in this manner, the next button only seem to execute 1 function rather than 2 - handleNext() but not fram(). Fram() sets translateX value to form1. handleNext controls the state of CustomizedSteppers.
However, if the "props" is deleted from Form1, and the button is moved out of the tags and put for example above CustomizedSteppers tag, it executes both functions. I want the button to be implemented in the manner that is shown below but it does not work as intended
My App.js:
import {Form1, Form2, Form3, Form4} from './components';
import {Header} from './containers';
import {Section} from './containers';
import {Forms} from './containers'
import CustomizedSteppers from './components/stepper/demo';
const App = () => {
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
const [transform, transformit] = React.useState("TranslateX(0px)");
const fram = () => {
transformit("TranslateX(-900px)");
};
return (
<>
<Header />
<Section class="section">
<CustomizedSteppers activeStep={activeStep} handleNext={handleNext}/>
<Forms>
<Form1 transform={transform} fram={fram}>
<button id="B "onClick={() => {handleNext();fram();}}>NEXT</button>
</Form1>
<Form2 />
<Form3 />
<Form4 />
</Forms>
</Section>
</>
);
}
export default App;
My Form1.js:
export default function Form1(props, {transform, fram}){
return (
<div id='Form1' style={{transform: transform}}>
<p id="demo"></p>
<div class="btn-box-f1">
{props.children}
</div>
</div>
)
}
Instead of trying to call two functions, call the function that updates the state first, and then use useEffect to monitor that state change.
The useEffect:
useEffect(() => {
transformit('TranslateX(-900px)');
}, [activeStep]);
And the updated button:
<button id="B" onClick={handleNext}>NEXT</button>
Oh, and there's no need to have double parentheses in your setActiveStep function:
setActiveStep(activeStep + 1);
As far as I can tell, the issue is with the way you've declared the Form1 component's props. React components take only a single argument, the props object. function Form1(props, { transform, fram }) is invalid, the transform prop isn't accessible in the component, it's undefined.
Here's the working version:
function Form1({ children, transform }) {
return (
<div id="Form1" style={{ transform: transform }}>
<p id="demo"></p>
<div className="btn-box-f1">{children}</div>
</div>
);
}
I've dropped logs in both callbacks and correctly see both triggered and see the transform applied to the id="Form1" div element.
Here the "next" button was clicked, both callbacks logged, the active step state updated to 1, and the transform was applied.
I have created a toggle button which will show and hide the value variable. But I can't see the changes on the screen, Although console shows the value of 'show' is changing every time I click the 'Change Me' button.
import React from 'react'
export default function State(){
let val = 4;
let show = true;
function changeMe(){
show = !show;
console.log(show);
}
return(
<div>
{show ? <span>{val}</span> : null}
<br></br>
<button onClick = {changeMe}>Change Me</button>
</div>
)
}
What I understand about functional component is that they are stateless component and we can only present the state/props of them. Is this is the reason I can't create toggle button without hooks to render the changes. Please correct me If I am wrong or add on your answer/thought to clear my concept.
PS: I am new to React and learning concepts of React. So, it might be a silly question.
What I understand about functional component is that they are stateless component and we can only present the state/props of them. Is this is the reason I can't create toggle button without hooks to render the changes.
Yes. If you don't use hooks, function components are stateless. To have a stateful component, either:
Use hooks, or
Use a class component instead
Note that function components can have props without using hooks (and usually do). Props are basically state the parent element manages. The parent can even pass your function component a function it calls in response to an event that may make the parent component change the prop the function component uses (using state in the parent, via hooks or a class component). But props are distinct from state.
For instance, here's a function component with a ticks property updated by the parent:
const {Component, useState, useEffect} = React;
function Child({ticks}) {
return <div>{ticks}</div>;
}
class ClassParent extends Component {
constructor(props) {
super(props);
this.state = {
ticks: 0
};
this.onTick = this.onTick.bind(this);
}
componentDidMount() {
this.timer = setInterval(this.onTick, this.props.interval || 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
onTick() {
this.setState(({ticks}) => {
++ticks;
return {ticks};
});
}
render() {
return <Child ticks={this.state.ticks} />;
}
}
function FunctionParent({interval = 1000}) {
const [ticks, setTicks] = useState(0);
useEffect(() => {
const timer = setInterval(() =>{
setTicks(t => t + 1);
}, interval);
}, []);
return <Child ticks={ticks} />;
}
function Example() {
return <div>
<ClassParent interval={800} />
<FunctionParent interval={400} />
</div>;
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
I have a simple App component
export default function App() {
const [count, changeCount] = useState(0)
const onIncreaseClick = useCallback(() => {
changeCount(count + 1)
}, [count])
const onPress = useCallback(() => {
alert('pressed')
}, [])
return (<>
<button onClick={onIncreaseClick}>Increase</button>
<ButtonPressMe onClick={onPress} />
</>);
}
I expect that onPress variable contains always the same link since parameters never change
And i expect that my ButtonPressMe component will be rendered just once - with the first App component rendering... because it has just one prop and value of this prop never change... therefore no need to rerender component. Correct?
Inside my ButtonPressMe component i check it with console.log
const ButtonPressMe = ({ onClick }) => {
console.log('Button press Me render')
return <button onClick={onClick}>Press me</button>
}
And against my expectations it rerenders each time when parent component rerenders after Increase button is pressed.
Did i misunderstood something?
sandbox to check
And against my expectations it rerenders each time when parent component rerenders after Increase button is pressed.
Did i misunderstood something?
That's the default behavior in react: when a component renders, all of its children render too. If you want the component to compare its old and new props and skip rendering if they didn't change, you need to add React.memo to the child:
const ButtonPressMe = React.memo(({ onClick }) => {
return <button onClick={onClick}>Press me</button>
})
By default, when a parent component re-renders, all of its child components re-render too.
useCallback hook will preserve the identity of the onPress function but that won't prevent a re-render of the ButtonPressMe component. To prevent a re-render, React.memo() is used. useCallback hook is used to avoid passing a new reference to a function, as a prop to a child component, each time a parent component re-renders.
In your case, combination of React.memo and useCallback hook will prevent a re-render of ButtonPressMe component.
function App() {
const [count, changeCount] = React.useState(0);
const onIncreaseClick = React.useCallback(() => {
changeCount(count + 1);
}, [count]);
const onPress = React.useCallback(() => {
alert("pressed");
}, []);
return (
<div>
<button onClick={onIncreaseClick}>Increase</button>
<ButtonPressMe onClick={onPress} />
</div>
);
}
const ButtonPressMe = React.memo(({ onClick }) => {
console.log("Button press Me render");
return <button onClick={onClick}>Press me</button>;
});
ReactDOM.render(<App/>, document.getElementById("root"));
<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="root"></div>
The default behavior in React is to change everything in the App when anything changes, in your case you're changing the state of the parent of your custom button, therefore React re-renders everything including your button.
You can find an explanation on how React decides to re-render components here:
https://lucybain.com/blog/2017/react-js-when-to-rerender/#:~:text=A%20re%2Drender%20can%20only,should%20re%2Drender%20the%20component.