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>
]);
Related
After onClick method to splice array, data seems to delete but page isn't updating. How to reRender or update the page to reflect the changes?
Home.js:
import React from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
function handleDelete(id) {
var index = HomeData.map(function (e) {
return e.id;
}).indexOf(id);
HomeData.splice(index, 1);
}
return (
<>
<section className="home_section">
<div className="home_container">
{HomeData.map((item) => {
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Data:
const HomeData = [
{
id: 1,
heading: 'This is first Heading'
},
{
id: 2,
heading: 'This is Second Heading'
},
]
export default HomeData;
I have tried using useNavigate from react-router-dom and history, but it didn't work.
In React functional components you can use a hook called useState. With this hook you can get and set the data however you want it.
const [data, setData] = useState(homeData);
Mutating state however is a big no-no in the React ecosystem because of the fact that it heavily practices the concept of immutability. Splice mutates the state by deleting or adding to the element itself.
Instead of mapping and splicing you can use filter with the setter. Filter is immutable, because it creates a shallow copy. You want to create a shallow copy, but without the item that has the id given as a parameter in your function. This would translate to the following code:
setData(homeData.filter(home => home.id !== id));
Now all you have to do is map through the state "data" instead of the homeData directly.
Maybe you can utilize state for this, can use useState hooks
It will be something like this:
import React, {useState} from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [data,setData] = useState(HomeData)
function handleDelete(id) {
const newData = data.filter((e) => e.id !== id)
setData(newData)
}
return (
<>
<section className="home_section">
<div className="home_container">
[don't forget to use the state here] >>> {data.map((item) => {
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Issue
In the current implementation the code is mutating an object that ins't part of any React state, so React isn't aware that anything needs to be rerendered.
Things to keep in mind:
Array.prototype.splice does an in-place mutation of the array it operates over.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To access part of an array without modifying it, see slice().
React components rerender for one of three reasons:
A local component state update is enqueued, component and sub-ReactTree rerender
A passed props value is updated, component and sub-ReactTree rerender
The parent component rerendered (because state and/or props updated)
Solution
To correctly render and update the HomeData array it necessarily should be part of a React component state. When updating React state, all state, and sub-state, necessarily needs to be a new object reference. This is because React uses a shallow reference equality check. It's far more common to use Array.prototype.filter to filter an existing array and return a new array reference.
Home Example:
import React from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [homeData, setHomeData] = React.useState(HomeData); // <-- initialize state
const handleDelete = (id) => {
setHomeData(data => data.filter(el => el.id !== id)); // <-- filter and return new array
};
return (
<section className="home_section">
<div className="home_container">
{homeData.map((item) => ( // <-- map homeData state
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading}</h1>
<button
button="button" // <-- should be explicit with button type
onClick={handleDelete}
>
Delete
</button>
</div>
))}
<button
className="submit_btn"
type="submit" // <-- should be explicit with button type
>
Submit
</button>
</div>
</section>
);
}
export default Home;
You should use the useState hooks to update the view
import React, { useState } from "react"; //imported useState
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [homeData, setHomeData] = useState(HomeData); //Added here
function handleDelete(id) {
const newData = homeData.filter((e) => e.id !== id)
setHomeData(newData)
}
return (
<>
<section className="home_section">
<div className="home_container">
{homeData.map((item) => { //changed state here
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Halo guys, I would like to ask a frustating problem for me :'(
What if we don't want to trigger useEffect in some events for the same state. For example lets say I have a state called "myState" and I have put myState in useEffect's dependency. In button1 I want to update myState which will trigger useEffect, in button2 I want also update myState but I don't want to trigger the useEffect
Is it possible to prevent useEffect to be fired up such I just described?
Thank you
Write UseEffects separately, you can keep on stacking them on one above others if you want to run them independantly
in your state, you should have some kind of a breaker for useEffect. in my example i named it stopUseEffect. if i don't want the useEffect to run, i will set stopUseEffect to true. then in useEffect, first thing to do is to check the breaker, does breaker allow it to run or not (if(myState.stopUseEffect) return;):
const { useState, useEffect } = React
const Test = () => {
const [myState, setMyState] = useState({content: 0, stopUseEffect: false});
useEffect(() => {
if(myState.stopUseEffect) return;
console.log("useEffect ran! myState's content:", myState.content)
}, [myState])
return <div>
<button onClick={() => setMyState(p => ({...p, content: p.content + 1, stopUseEffect: false}))}>btn 1, this button will trigger useEffect.</button> <br/>
<button onClick={() => setMyState(p => ({...p, content: p.content + 1, stopUseEffect: true}))}>btn 2, this button will NOT trigger useEffect.</button>
</div>
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Test />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I'm building a react website and i'm stuck on a problem that has to do with passing data from a component, back to the App.js file.
so:
In my app.js file, i have a Router, that routes any request to path '/signin' to the Component. What i want to do, is i want to take a boolean from the component and store it in the app.js script, so that whenever that boolean is changed in the Component, it also changes on the app.js script.
Any suggestions on how i can do this? Any help at all would be appreciated!
the changeValue function will change the value in the App component. Since value is passed as props to ChildComponent, as soon as it changes, it will change in ChildComponent as well
const App = () => {
const [value, setValue] = useState('')
const changeValue = (value) => {
setValue(value)
}
return (
<div>
<ChildComponent
changeValue={changeValue}
value={value}
/>
</div>
)
}
const ChildComponent = ({changeValue, value}) => {
return (
<div>
<p>{value}</p>
<input
type='text'
onChange={(e) => changeValue(e.target.value)}
/>
</div>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Store the state at the parent level, and pass down the modifier function to the child via props or context.
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 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)} />