I have a system in which when the user presses a button, a new component is pushed to a list of components and the state is updated. I render the list of components using {}, but only the first element is rendered.
I've used console.log to ensure that my list is actually updating. All the questions I've seen so far for this problem involve using a class that extends React.Component. Since I'm using a function to render, I don't see how I can use those solutions
export default function ExampleManager() {
const [examples, setExamples] = React.useState([<Example key={0}/>);
function handleClick() {
examples.push(<Example key={examples.length}/>);
setExamples(examples);
}
return (
<>
{examples}
<Button variant="outlined" color="primary" onClick={handleClick}>
Add Example
</Button>
</>
);
}
If the button was to be clicked multiple times, I would expect there to be multiple Example components, however at the moment only the first element works
As far as I know, examples is immutable and isn't updated by using examples.push().
Change your handleClick to the following code, to remove the reference of your example variable:
function handleClick() {
// create a new array to prevent referencing the old on
setExamples([
...examples,
<Example key={examples.length}/>
]);
}
Nonetheless you shouldn't add components per se into your array. Try to split values and its representation like the following:
function ExampleManager() {
const [examples, setExamples] = React.useState([0]);
const handleClick = () => setExamples([...examples, examples.length])
return (
<>
{examples.map((item, key) => <Example key={key} data={item} />)}
<Button variant="outlined" color="primary" onClick={handleClick}>
Add Example
</Button>
</>
)
}
Related
When I click on the 'Rerender UI' button then it prints in the console for the first two clicks. I think it should print in the console only for the first time when I click on the 'Rerender UI' button because on the button click the component state is changed so UI will re-render and the console log will be printed in the console. Why is it printing for the second click? StrictMode is off. See code:
export default function UseCallbackComp() {
const [stateVar, setStateVar] = useState<any>()
console.log("Parent Rerendered!")
return (
<>
<div>UseCallbackComp content</div>
<div>
<button
onClick={() => {
setStateVar(1)
}}
>
Rerender UI
</button>
</div>
</>
)
}
When I put the console log line inside useEffect like below it prints only for the first time 'ReRender UI' button is clicked which is the expected behaviour.
useEffect(() => {
console.log("Parent Rerendered!")
})
From the below two links I got to know whats the behaviour of react when useState is used:
stackoverflow question
gitHub discussion
There are two different cases for which useState behaves differently for same value.
Case 1: When useState used with same value as the initial value.
Result: No rerendering at all.
export default function LifecycleEvents() {
const [stateVar, setStateVar] = useState<any>(0)
console.log("Parent Rerendered!")
return (
<>
<button
onClick={() => {
setStateVar(0) //same value as the initial value
}}
>
Rerender UI
</button>
</>
)
}
Case 2: When useState used with different value first and then with the same value.
Result: First both Parent and child will be re-rendered. But for the second time, only the render function of Parent component will get called, Nothing else.
See code:
export default function LifecycleEvents() {
const [stateVar, setStateVar] = useState<any>(0)
console.log("Parent Rerendered!")
return (
<>
<button
onClick={() => {
setStateVar(1) //different value then initial value.
}}
>
Rerender UI
</button>
</>
)
}
Conclusion: Nothing to worry unless you have an expensive render method of the component. In that case use memo.
I want to make one reusable component name: ABC 5 times in which all ABC component should have different data and i want to add a functionality where 1st and 5th ABC should have true state by default and the remaining 3 should have false and then i want to close and open particular component with an arrow but the problem is that all component are opening and closing instead of the particular one
because you should add functionality as a prop from the parent of the component you are using it in. as in
const CustomButton = ({handleClick, buttonText, otherProps}) => {
return <button onClick={handleClick}>{buttonText}</button>
}
const App = () => {
return (
<div>
<CustomButton buttonText={"SEND"} handleClick={() => {/*fetch something or do something*/}} />
<CustomButton buttonText={"DELETE"} handleClick={() => {/*do another thing*/}} />
<CustomButton buttonText={"DO SOMETHING"} handleClick={() => {/*say hi to your users*/}} />
<CustomButton buttonText={"DO SOMETHING ELSE"} handleClick={() => {/*you can do anything*/}} />
</div>
)
}
if you need to render buttons according to the data count(example: length of an array) you can use Array.map method to render components and use its index to open and close the elements by passing the index as a prop to CustomButton and using the handleClick function as () => {handleClick(index)} and handle the operation in just one function in the parent component.
I've done this in Vue before with v-for. But in React I want to add a TextField based on a number in my state. I have a state:
constructor(...props){
super(...props)
this.state = {
questions: 1
}
}
My component will have a button which will add 1 to questions if pressed. And I want to render TextFields based on the number in the state (questions). By that same logic I also plan on doing a remove button which will remove one from the total, and will also remove one of the TextFields. I tried this:
render(){
let category_rows = []
for(var i = 0; i < this.state.questions; i++){
category_rows.push(<TextField variant='outlined' />)
}
return(
<Card>
<CardContent>
<Typography>
Add Question Categories
</Typography>
{category_rows}
</CardContent>
<Button onClick={() => {
this.state.questions += 1
console.log(this.state)
}}>Add</Button>
</Card>
)
}
}
And it renders the first time but when pressing my button and adding to the state, it doesn't add a new field like I want.
While I'm at it, if someone could tell me if it's possible to take the code on the Button's onClick and put it in a function, then call the function on the onClick. I tried it but when I log this it says it is undefined.
you are changing the state variable wrong
inside your Button instead of doing this
<Button onClick={() => {
this.state.questions += 1
console.log(this.state)
}}>Add</Button>
do this
<Button onClick={() => this.setState({questions : this.state.questions +1 })}>Add</Button>
Because in react we shouldn't update the state variable directly. otherwise it will not trigger re render and wont update.
You are updating the state wrong way. State should always be immutable and should only be updated by using setState function
<Button onClick={() => {
this.setState({questions: this.state.questions += 1})
console.log(this.state)
}}>Add</Button>
How come the following React Button Emitter is not working? It should display the word Apple, with button click.
function App() {
return (
<div>
<button onClick={handleClick('apple')}>
Test Button
</button>
</div>
)
}
function handleClick(props) {
console.log(props)
}
In order for it to get called on click you need to pass a function. Right now your code is invoking the function:
function App() {
return (
<div>
{/* Creates an anonymous function that calls `handleClick` */}
<button onClick={() => { handleClick('apple'); }}>
Test Button
</button>
</div>
)
}
By doing onClick={handleClick('apple')} what you are doint is to put the result of handleClick('apple') at rendering time, not onClick time.
onClick={() => handleClick('apple')} will work because you are creating a function and assign it to onClick, without executing it yet.
This is how React works, because what you are writing is actually just javascript (not html, even if it looks like so).
Your way would instead be perfectly ok if you were using Vue, for example, because in that case you are working in an html template (unless you don't want to use jsx..)
I am trying to list person by clicking onClick function to add empty object so that map can loop
over and show div. I see prop onclick function calling works but i map is not looping over it.
a little help would be appreciated.
// functional component
const listing = ({list=[]}) => (
<>
{
<div id='addPerson' onClick={() => list.push({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
);
export default listing;
// test case
it('renders list-items', () => {
const wrapper = shallow(<listing />);
wrapper.find('#addPerson').prop('onClick')();
console.log(wrapper.find('.listItems').length); // showing zero, expected 1
});
Updating List is not going to cause a re-render in your component. I am not sure where List is coming from, but I am assuming it is coming from a local state in the parent, or probably redux store. From inside your component, you will have to call a function in your parent to update the list array there.
Below is a version of your component, assuming the list in your local state. You will have to adjust the code to update the state in the parent or redux store, but the below should be enough to give you an idea of what to do. Also, don't push the element to array, as it mutates the object and will not contact re-render.
const Listing = ({list = [], addToList}) => {
return <>
{
<div id='addPerson' onClick={() => addToList({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
};