React mapped function running "setState" isn't updating component - javascript

I have an app component that takes one nested component. The nested component returns a number of buttons determined by one of its local state variable's lengths. Each button runs a programmatic this.setState() function to show a new set of data onClick. Here's the code described, and my question below:
class App extends React.Component {
render() {
return (
<div className='container'>
<Buttons />
</div>
)
}
}
class Buttons extends React.Component {
state = {
variableState,
count: 0,
chosen: 0,
}
upOne = x => {
this.setState(prevState => ({
count: prevState.count + 1,
chosen: x,
}))
console.log('running')
}
componentDidUpdate() {
console.log('componentupdated')
}
render() {
const {variableState, count, chosen} = this.state
const {upOne} = this
return (
<div>
{
variableState[count].answers.map((s, t) => <button onClick={() => upOne(t + 1)}>{s}</button>)
}
</div>
)
}
}
const variableState = [
{
answers: [
'one',
'two',
'three',
'four',
]
}
]
ReactDOM.render(<App />, document.getElementById('app'))
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I want to update the <Buttons /> state by incrementing the count by one each time one of the buttons is clicked. This should run setState() which should update the component and run the componentDidUpdate() function. The problem is, the upOne() function runs, but it isn't updating the component and therefore not running the componentDidUpdate() function and I don't know why.
If I get rid of the Array.map() logic and make it a static function like this:
class Buttons extends React.Component {
state = {
variableState,
count: 0,
chosen: 0,
}
upOne = x => {
this.setState(prevState => ({
count: prevState.count + 1,
chosen: x,
}))
console.log('running')
}
componentDidUpdate() {
console.log('componentupdated')
}
render() {
const {variableState, count, chosen} = this.state
const {upOne} = this
return (
<button onClick={() => upOne(1)}>click</button>
)
}
}
It works as I expect it to.
Is this expected behavior or am I missing something?

variableState[count].answers...
Once counts becomes 1, variableState[1] is undefined and undefined.answers does not exists and you'll see an thrown error in your console.
I don't know if the variableStates value that you're showing in your code is the same as you're using on your end, but if you change it to variableState[0].answers..., it works.

Related

React Click Counter: Updating State of just one element

This should be pretty simple, but I can't figure out how to do it.
I have a component with multiple buttons, each with a "count" value, set with state. When a user clicks, the count goes up.
Right now, when I click one of the buttons, both counters change. How can I make it so only the div that was clicked updates, using the same state?
Edit: I don't want to have different counts, as I'd like for this component to render buttons dynamically. What if I don't know how many buttons I'll have at first?
class Block extends React.Component {
state = {
count: 0
};
handleClick = e => {
const count = this.state.count;
this.setState({ count: count + 1 });
};
render() {
return (
<div>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
</div>
);
}
}
This is more of an issue of learning how to think in react.
If you need to be able to reuse a piece of functionality like a counter, you can make it its own component and have it manage its own state. Then you can reuse it wherever you need.
Here's an example:
class Counter extends React.Component {
state = {
count: 0
};
handleClick = () => {
// Use updater function when new state is derived from old
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
);
}
}
// Now you can use it dynamically like this:
class Block extends React.Component {
render() {
return (
<div>
<div>There are 4 counter component instances that each manage their own state.</div>
{[1,2,3,4].map(v => <Counter />)}
</div>
);
}
}
ReactDOM.render(<Block />, document.getElementById('root'));
<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>
<div id="root"></div>
you should define two state and when press each button update the current state and you can render the current state in the dome like this
state = {
firstCount: 0,
secondCount: 0
}
and write your action (function) to handle update state like this
handleUpdateCount = stateName => {
this.setState({
[stateName]= this.state[stateName] + 1
})
}
then you should called this function like this =>
this.handleUpdateCount('firstCount')
If your buttons are dynamic you can set your state to be an array and update the relevant index
class Block extends React.Component {
state = [];
handleClick = index => {
this.setState(state => {
const newState = [...state]; //keep state immutable
!newState[index] && (newState[index] = 0)
newState[index]++
return newState
});
};
render() {
return (
<div>
{[1,2,3].map((value, index) => <button className="block" onClick={() => this.handleClick(index)}>
<div className="counter">{this.state[index]}</div>
</button>)}
</div>
);
}
}
You have to use another value to update function when new state is derived from old state (like increment)
import React, { Component } from 'react'
export class Ref3 extends Component {
constructor(props) {
super(props)
this.state = {
count:0
}
}
//use prevState to help you update the old value to a new one
clickHandler=()=>{
this.setState((prevState=>({
count:prevState.count+1
})))
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Click To Count</button>
{this.state.count}
</div>
)
}
}
export default Ref3

React.js - How to execute function in parent with arguments from child

The question is probably rather unclear, but i did not how to formulate it, maybe that was the reason why i was not able to find solution to this puzzle i have. anyway, here is an example of what i want to accomplish:
<Calendar
tileContent={({ activeStartDate, date, view }) =>
this.renderGames(date, view)
}
/>
This is an example from npm package react-calendar, but i am sure you know what i mean. The param tileContent gets passed function that already has destructured object, and then i run my own function with data i get from that function.
I was thinking that this was done by executing function in child where i would pass an object (or single param, i just use object as an example).
I think what you're looking for are Render Props, not just executing function in parent with args (even though render props do this as well). It would appear your example is using Render Props specifically.
There are some good examples online of using render props in React, also referred to as "Wrapper Components", etc..
An example could be something like:
const { render } = ReactDOM;
class CounterWrapper extends React.Component {
state = {
count: 0
};
increment = () => {
const { count } = this.state;
return this.setState({ count: count + 1 });
};
decrement = () => {
const { count } = this.state;
return this.setState({ count: count - 1 });
};
render() {
const { count } = this.state;
return (
<React.Fragment>
{this.props.wrapperContent({
increment: this.increment,
decrement: this.decrement,
count
})}
</React.Fragment>
);
}
}
class App extends React.Component {
renderApp = (cnt, inc, dec) => {
return (
<div>
<h1>Render Props Counter Example</h1>
<div>
<p>{cnt}</p>
<button type="button" onClick={() => inc()}>
Increment
</button>
<button type="button" onClick={() => dec()}>
Decrement
</button>
</div>
</div>
)
};
render() {
return (
<CounterWrapper
wrapperContent={({ count, increment, decrement }) =>
this.renderApp(count, increment, decrement)
}
/>
);
}
}
render(<App />, document.body);
<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>
It sounds like you want to execute a function that's in the parent component, from a child component with arguments passed from the child.
Here is an example:
const ParentComponent = () => {
const handleClick = (args) => {
console.log(args)
}
return (
<div>
<ChildComponent onClick={handleClick} />
</div>
)
}
const ChildComponent = ({onClick}) => {
const val = 5;
return (
<div>
<button onClick={() => handleClick(val)} name="Click">Click Me</button>
</div>
)
}
This hsould render the child component which is just a button, with an event handler that is sent from the parent. When you click the button, you should get a console log of 5, which is coming from the parent. This is how you would propgate values from the child, up to the parent.

React doesn't render all components of array

I want to dynamically add Components, after clicking the "add" button.
For that, I created an array that consists of all the components, and add them on click.
My problem is, that it only renders one component, even though it consists of several ones.
My code looks like this:
class QuestionBlock extends React.Component {
constructor(props) {
super(props);
this.state = {answersArray: []};
}
addPossibleAnswer() {
this.state.answersArray.push(
<PossibleAnswers id={this.state.answersArray.length + 1}/>
)
this.forceUpdate();
}
componentWillMount() {
this.state.answersArray.push(
<PossibleAnswers id={this.state.answersArray.length + 1}/>
)
}
render() {
console.log(this.state.answersArray) // Grows after adding componenets, but they are not rendered.
return (
<div>
{this.state.answersArray}
<AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
</div>
);
}
}
If you see what I did wrong, I'd be really glad if you could help me out!
Instead of mutating state directly and adding JSX to it, you can instead keep raw data in your state and derive the JSX from that in the render method instead.
Example
class QuestionBlock extends React.Component {
state = { answers: 1 };
addPossibleAnswer = () => {
this.setState(({ answers }) => ({ answers: answers + 1 }));
};
render() {
return (
<div>
{Array.from({ length: this.state.answers }, (_, index) => (
<PossibleAnswers key={index} id={index} />
))}
<AddPossibleAnswer addPossibleAnswer={this.addPossibleAnswer} />
</div>
);
}
}
You don't interact with state like you do. Never mutate the state field. You need to use this.setState:
this.setState(prevState => ({answersArray: prevState.answersArray.concat([
<PossibleAnswers id={prevState.answersArray.length + 1}])}));
Having said that, it is also strange that you store components in state. Usually, you would store data and create the components based on the data in the render method.
You are directly pushing elements to the array without setState so the component won't re-render
Also avoid using tthis.forceUpdate() as much as you can in your application because this is not recommended much
You need to change your code like below. The recommended approach for dealing with arrays in react is using previous state and push to an array
addPossibleAnswer() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
componentWillMount() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
Also keep in mind that componentWillMount life cycle method is deprecated in react 16. So move the code to componentDidMount instead
Here is the corrected code
class QuestionBlock extends React.Component {
constructor(props) {
super(props);
this.state = {answersArray: []};
}
addPossibleAnswer() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
componentDidMount() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
render() {
const { answersArray } = this.state;
return (
<div>
{answersArray}
<AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
</div>
);
}
}

Toggling visibility of array of stateless react components

I am trying to simply map over some data returned from an api and create a stateless component for each object returned. I want to be able to click on any of the components to toggle visibility of the rest of its data.
I have tried numerous ways to do it and keep hitting a brick wall, i've also scoured stack overflow and cannot seem to find an answer.
I have gotten it working by making them individual class components, however it seems like a lot of unnecessary code for just a toggle functionality.
Thank you in advance for any help or insight, here is a quick breakdown of what I have currently.
For clarification this is a simple app for me to learn about using react and an external api, it is not using redux.
fetched users in state of class component
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: []
};
}
async componentDidMount() {
let fetchedData = await API_Call("people");
this.setState({ resource: fetchedData.results });
while (fetchedData.next) {
let req = await fetch(fetchedData.next);
fetchedData = await req.json();
this.setState({
resource: [...this.state.resource, ...fetchedData.results]
});
}
}
}
Then map over the results and render a component for each result
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person key={i} {...person} />
));
return <div>{mappedPeople}</div>;
}
Is there i can make each person component a stateless component with the ability to click on it and display the rest of the data? Here is what I have currently.
class Person extends Component {
constructor(props) {
super(props);
this.state = {
visibility: false
};
}
toggleVisible = () => {
this.setState(prevState => ({
visibility: !prevState.visibility
}));
};
render() {
return (
<div>
<h1 onClick={this.toggleVisible}>{this.props.name}</h1>
{this.state.visibility && (
<div>
<p>{this.props.height}</p>
</div>
)}
</div>
);
}
}
Again thanks in advance for any insight or help!
You could keep an object visible in your parent component that will have keys representing a person index and a value saying if the person is visible or not. This way you can toggle the person's index in this single object instead of having stateful child components.
Example
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: [],
visible: {}
};
}
// ...
toggleVisibility = index => {
this.setState(previousState => {
const visible = { ...previousState.visibile };
visible[index] = !visible[index];
return { visible };
});
};
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person
key={i}
{...person}
visible={this.state.visible[i]}
onClick={() => this.toggleVisibility(i)}
/>
));
return <div>{mappedPeople}</div>;
}
}
const Person = (props) => (
<div>
<h1 onClick={props.onClick}>{props.name}</h1>
{props.visible && (
<div>
<p>{props.height}</p>
</div>
)}
</div>
);
Similar idea with #Tholle but a different approach. Assuming there is an id in the person object we are changing visibles state and toggling ids.
class PersonList extends React.Component {
constructor(props) {
super(props)
this.state = {
resource: this.props.persons,
visibles: {},
}
}
toggleVisible = id => this.setState( prevState => ({
visibles: { ...prevState.visibles, [id]: !prevState.visibles[id] },
}))
render() {
const mappedPeople =
this.state.resource.map((person, i) =>
<Person
key={person.id}
visibles={this.state.visibles}
toggleVisible={this.toggleVisible}
{...person}
/>
)
return (
<div>
{mappedPeople}
</div>
)
}
}
const Person = (props) => {
const handleVisible = () =>
props.toggleVisible( props.id );
return (
<div>
<h1 onClick={handleVisible}>
{props.name}</h1>
{props.visibles[props.id] &&
<div>
<p>{props.height}</p>
</div>
}
</div>
);
}
const persons = [
{ id: 1, name: "foo", height: 10 },
{ id: 2, name: "bar", height: 20 },
{ id: 3, name: "baz", height: 30 },
]
const rootElement = document.getElementById("root");
ReactDOM.render(<PersonList persons={persons} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can make sure your "this.state.resource" array has a visibility flag on each object:
this.state.resource = [
{ ..., visibility: true },
{ ..., visibility: false}
...
];
Do this by modifying your fetch a little bit.
let fetchedData = await API_Call("people");
this.setState({
resource: fetchedData.results.map(p => ({...p, visiblity: true}))
});
Merge your Person component back into PersonList (like you are trying to do), and on your onclick, do this:
onClick={() => this.toggleVisible(i)}
Change toggleVisible() function to do the following.
toggleVisible = (idx) => {
const personList = this.state.resource;
personList[idx].visibility = !personList[idx].visibility;
this.setState({ resource: personList });
}
So now, when you are doing:
this.state.resource.map((person, i) => ...
... you have access to "person.visibility" and your onclick will toggle the particular index that is clicked.
I think that directly answers your question, however...
I would continue with breaking out Person into it's own component, it really is good practice!
Other than better organization, one of the main reason is to avoid lamdas in props (which i actually did above). Since you need to do an onClick per index, you either need to use data attributes, or actually use React.Component for each person item.
You can research this a bit here:
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
BTW you can still create "components" that aren't "React.Component"s like this:
import React from 'react';
const Person = ({ exProp1, exProp2, exProp3}) => {
return <div>{exProp1 + exProp2 + exProp3}</div>
}
Person.propTypes = {
...
}
export default Person;
As you can see, nothing is inheriting from React.Component, so you are getting the best of both worlds (create components without creating "Components"). I would lean towards this approach, vs putting everything inline. But if your application is not extremely large and you just want to get it done, going with the first approach isn't terribly bad.

Fibonacci Counter in React.js

I am a beginner in React. I am trying to do a counter for a fibonacci sequence. For those who don't know what is a fibonacci sequence, here is a guide. It works at the beginning and then it starts to go off sequence. I want to be able display the number in the fibonacci sequence and move up and down the sequence. Here is what my code so far:
import React, { Component } from 'react';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
previous1: 1,
previous2: 1,
count: 1
}
this.clickDecrease = this.clickDecrease.bind(this);
this.clickIncrease = this.clickIncrease.bind(this);
}
clickIncrease(e) {
this.setState({
previous1: this.state.count,
previous2: this.state.previous1,
count: this.state.previous1 + this.state.previous2
});
}
clickDecrease(e) {
this.setState({
previous1: this.state.previous2,
previous2: this.state.count - this.state.previous1,
count: this.state.previous1
});
}
render() {
return (
<div>
The current number is: {this.state.count}
<button onClick={this.clickDecrease} className="btn btn-danger">-</button>
<button onClick={this.clickIncrease} className="btn btn-primary">+</button>
</div>
);
}
}
I tried to store the last two numbers in a property of the state so I can add them up to get the next sequence. However, it does not seems to work. What should I do to make this better?
Check the working code, you don't need a separate count variable to store the series value, previous1 will always have that value.
How to update series values:
During '+' : At each step previous1 will be equals to (previous1 + previous2) and previous2 will take the previous1 value.
During '-': previous1 will become prevoious2 and previous2 will take the value of previous1 - previous2
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
previous1: 1,
previous2: 0,
}
this.clickDecrease = this.clickDecrease.bind(this);
this.clickIncrease = this.clickIncrease.bind(this);
}
clickIncrease(e) {
let a = this.state.previous1 + this.state.previous2;
this.setState({
previous1: a,
previous2: this.state.previous1,
});
}
clickDecrease(e) {
this.setState({
previous1: this.state.previous2,
previous2: this.state.previous1 - this.state.previous2,
});
}
render() {
return (
<div>
The current number is: {this.state.previous1}
<br/>
<button onClick={this.clickDecrease} className="btn btn-danger">-</button>
<button onClick={this.clickIncrease} className="btn btn-primary">+</button>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id ='app'/>
Note: Put the condition in decrease function, when the value of state variable will be 1 and 0, otherwise it will take the negative values.
If you use React hooks, things can be quite simple. Use a ref to hold the previous count, when the current count changed, add the previous count and the current count together. that's all.
Here I moved hold the previous count into the usePrevious hook.
const App = () => {
const [count, setCount] = useState(1);
const previousCount = usePrevious(count); // hold the previous count
const addCount = () => {
setCount(count + previousCount);
};
return (
<div>
<div>previousCount: {previousCount}</div>
<div>count: {count}</div>
<button onClick={addCount}>click me</button>
</div>
);
}
usePrevious.js:
const usePrevious = value => {
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
export default usePrevious;

Categories