Array not dynamically updating due to mis-use of states - javascript

When a radio button is clicked it changes a value from 0 to 1 or vice versa.
This is done through 2 components Test.js and Graph_Test.js.
Test.js is where the radio buttons are created and the array is filled. E.g when radio button 1 is pressed, I want it to change the value in array[1] from 0 to 1 or 1 to 0.
So any time a radio button is pressed it will dynmaically change the array.
This array is then used to build a graph in Graph_Test.js, depending on which indexes have 1 values beside it will make corresponding lines on the graph.
For example if array = [0,1,0,0] , a line will be drawn for region 1.
So as this array dynamically changes so will the lines on the graph.
I am testing my code. In Graph_test I've it outputting array[0] and when a radio button[0] is touched this value should update. However this is not happening.
In test.js, i've used states this is where my problem is as the inital array is being dleivered as a prop but it is not dynamically updating.
Graph_test.js has two props which is sent through array and testing, testing will be used to build the graph later on . But is not currently needed.
Ive tried many attempts, and still not getting anywhere, any help would be greatly appreciated/needed.
Code:
Test.js:
// When radio buttons checked, change corresponding value in array if 0 change to 1 and if 1 change to 0
// This will be used in the graph component, and will enable the functionality of selcting one region or multiple region.
// As the graph will be plotted based on which regions are noted 1 in the array
import $ from "jquery";
import Graph_Test from "./Graph_Test.js";
import React, { useState } from "react";
const Test = props => {
const total_regions = (JSON.parse(JSON.stringify(props.test)).length); // gets the number of regions
const [array, setArray] = useState(Array(total_regions.length).fill(0));
//when a radio button is clicked change its corresponding in the array
//when a radio button is clicked change its corresponding in the array
const handleClick = (item, idx) => {
if (array[idx] == 1) {
array[idx] = 0;
} else {
array[idx] = 1;
}
setArray(array);
};
return ( // displays radio buttons depending on the number of objects in json
<div>
<div>
<Graph_Test testing={[]} arrays={array}/>
</div>
<div>
{props.test.map((item, idx) => {
return (
<label key={idx}>
<input className="region" type="radio" value={idx} onClick={() => handleClick(item, idx)}/>
<span>{idx}</span>
</label>
);
})}
</div>
</div>
);
};
export default Test;
Graph_Test.js
import React from 'react';
import $ from "jquery";
//<h1>{props.testing.map}</h1>
const Graph_Test = props => {
return(
<div>
<div>
{props.arrays && props.arrays.length > 0 && <p>{props.arrays[0]}</p> }
</div>
</div >
);
};export default Graph_Test;
App.js
import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import ReactPlayer from 'react-player'
import LeftPane from "./components/LeftPane.js";
import Video from "./components/Video.js";
//import Footer from "./components/Footer.js";
import Test from "./components/Test.js";
import Graph_Test from "./components/Graph_Test.js";
//import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { apiResponse: [] };
this.state = {
clicked: "no"
};
}
// Comunicate with API
callAPI() {
fetch("http://localhost:9000/IntensityAPI") //React app talks to API at this url
.then(res => res.json())
.then(res => this.setState({ apiResponse: res }));
}
handleClick = () => {
this.setState({ ...this.state, isClicked: "yes" });
console.log("clicked");
};
componentWillMount() {
this.callAPI();
}
render() {
return (
<div className="App">
<div class="row fixed-top fixed-bottom no-gutters" >
<div class="col-3 fixed-top fixed-bottom">
<LeftPane></LeftPane>
</div>
<div class="offset-md-3 fixed-top fixed-bottom" >
<Video></Video>
</div>
<div class=" col-3 fixed-bottom">
<Test test = {this.state.apiResponse} handler={this.handleClick}/>
<Graph_Test testing = {this.state.apiResponse} arrays={[]} {...this.state}/>
</div>
</div>
</div>
);
}
}
export default App;
// <Footer test = {this.state.apiResponse}/>

AFAIK React does not perform deep comparison with the state, so since you're reusing the already existing array it has the same memory reference as the previous one.
Try something like this:
const handleClick = (item, idx) => {
const newArray = [...array]
if (newArray[idx] == 1) {
newArray[idx] = 0;
} else {
newArray[idx] = 1;
}
setArray(newArray);
};

Related

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.

React JS which button is clicked how many times?

I have 2 buttons and 1 h1 I need to count how many times the button is clicked and display the button name with no. of count it clicked in h1 with the name of the button. Right now I am getting the name of button correctly but not able to display the click count for the button. please help
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
export default class App extends Component {
state={
button:"",
countClick:0
}
buttonClick=(e)=>{
e.preventDefault();
this.setState({button:e.target.name, countClick:this.state.countClick+1});
}
render() {
return (
<Fragment>
<h1>
{this.state.button} clicked {this.state.countClick} times
</h1>
<button name="Button 1" type="button" onClick={this.buttonClick}>Button 1</button>
<button name="Button 2" type="button" onClick={this.buttonClick}>Button 2</button>
</Fragment>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'));
Thank You
Some notice points:
build an Array to store each button's click amount status.
optionally use {id, value} structure for the array items, since we need to display the clicked button with its click amount.
use map() to prevent from repeating yourself about <button />.
pass the identity index as a param inside the nested handler function.
Full code:
import React from "react";
import "./styles.css";
const amount = 2;
export default function App() {
const [clickList, setClickList] = React.useState(
[...Array(amount).keys()].map(x => ({ id: `Button ${x + 1}`, value: 0 }))
);
const [activeButton, setActiveButton] = React.useState("");
const buttonClick = idx => e => {
const result = [...clickList];
result[idx].value += 1;
setClickList(result);
setActiveButton(e.target.name);
};
return (
<>
<h1>
{`${activeButton} clicked ${clickList.find(x => x.id === activeButton)?.value ?? 0} times`}
</h1>
{clickList.map((x, idx) => (
<button name={x.id} type="button" onClick={buttonClick(idx)}>
{x.id}
</button>
))}
</>
);
}
setState can take an object or a callback with previous state as parameter.
Change your setState like this:
this.setState(prevState => ({button:e.target.name, countClick: prevState + 1}));

Displaying Multiple API Responses in React

I am learning React and I have a solution that requests information through an API, and when there is a response it sets the state, however when rendering my results it only shows the last response on screen,
Even though there are 4, see image below.
App.js
import React from 'react';
import Tiles from './components/Tiles'
import Form from './components/Form'
import WaterData from './components/WaterData'
class App extends React.Component{
state = {
station_name: undefined,
water_value: undefined,
dateTime: undefined
}
getData = async (e) => {
e.preventDefault();
const name = e.target.elements.name.value;
const api_call = await fetch(`https://waterlevel.ie/geojson/latest/`)
.then(response1 => {
response1.json().then(data =>{
Array.from(data.features).forEach(element => {
if(element.properties['station.name'] === name){
this.setState({
station_name: element.properties['station.name'],
water_value: element.properties['value'],
dateTime: element.properties['datetime'],
});
}
})
});
});
}
render(){
return(
<div>
<Tiles />
<Form loadData={this.getData}/>
<WaterData
station_name={this.state.station_name}
water_value={this.state.water_value}
dateTime={this.state.dateTime}
/>
</div>
)
}
}
export default App;
WaterData.js
import React from 'react';
const Weather = (props) => {
console.log(props)
return(
<li>
<p>Location {props.station_name}</p>
<p>Value {props.water_value}</p>
<p>Date Time: {props.dateTime}</p>
</li>
)
}
export default Weather;
Can someone explain to me why the 4 responses do not display?
This happens because you are replacing the values in your state for each part of your data.
You can filter out the element you want in your array using filter.
And then put the whole array into your state only once :
const api_call = await fetch(`https://waterlevel.ie/geojson/latest/`)
.then(response1 => {
response1.json().then(data => {
const features = Array.from(data.features)
.filter(el => el.properties['station.name'] === name);
this.setState({ features });
})
});
But now, to render all of them, you will need to map your state values :
render(){
return(
<div>
<Tiles />
<Form loadData={this.getData}/>
{this.state.features.map(feat => <WaterData
key={/* Find something unique*/}
station_name={feat.properties['station.name']}
water_value={feat.properties['value']}
dateTime={feat.properties['datetime']}
/>)}
</div>
)
}
There's no need to store all the value separately in your state if they are related to each other, it would be fine for your child component though.
To be sure that the state value is always an array, give it an empty array at the start of your class :
state = {
features: []
}

Cannot update the app state from custom component using React and Redux

I have the following Codesandbox.io:
https://codesandbox.io/s/qxkq5vvm1q
which is a basic ReactJS / Redux application.
The key components here are:
a Select which gets its values something like through this way: Redux (state manager) -> PanelMaterialSize (container) -> Select
one Updater component which takes care of update the values available on the Select through Redux
Alert button, which when clicked should alert the value stored on the store
What should happen is:
when the user changes an option on the Select, that value should be stored on the store. This is actually happening properly - OK
if the Select gets its values changed (for example because the Updater component), then it should automatically change the value stored on the store with the value it is showing (something similar as if the user changes the value on it). Unfortunately this is not happening - The Goal
Here are some of the codes:
./src/controls/Select/Select.js
import React, { Component } from "react";
import "./Select.scss";
class Select extends Component {
constructor(props) {
super(props);
let { name, data, className, ...controlProps } = this.props;
this.name = name;
this.data = data;
this.controlProps = controlProps;
this.state = {
[name]: data,
className
};
}
render() {
let data = this.state[this.name];
return (
<div className="control-select" {...this.controlProps}>
<div className="custom-dropdown custom-dropdown--grey">
<select className="custom-dropdown__select custom-dropdown__select--grey">
{this.props.data.length > 0 &&
this.props.data.map((elem, index) => {
return (
<option value={elem.value} key={index}>
{elem.text}
</option>
);
})}
</select>
</div>
</div>
);
}
}
export default Select;
src/controls/PanelMaterialSize/PanelMaterialSize.js
import React, { Component } from "react";
import { connect } from "react-redux";
import "./PanelMaterialSize.scss";
import Select from "../Select/Select";
import { setThemeList, setSelectedTheme } from "../../store/AppConfig/actions";
class PanelMaterialSize extends Component {
constructor(props) {
super(props);
this.state = {
selection: "",
options: []
};
}
handleChange = e => {
let target = e.target;
let value = target.value;
this.props.setSelectedTheme(value);
};
render() {
return (
<div className="partial-designer-panel-material-size">
<div>
<div className="label-input">
<div className="label">THEME</div>
<div className="input">
<Select
name="selection"
value={this.state.selection}
data={this.props.themeList}
style={{ width: "100%" }}
onChange={this.handleChange}
/>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = appState => {
return {
themeList: appState.appConfig.themeList,
selectedTheme: appState.appConfig.selectedTheme,
};
};
const mapDispatchToProps = dispatch => {
return {
setThemeList: themeList => dispatch(setThemeList(themeList)),
setSelectedTheme: selectedTheme => dispatch(setSelectedTheme(selectedTheme)),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(PanelMaterialSize);
Any idea on how to make the point 2 work?
If possible, please, provide back your solution on a forked Codesandbox.io.
Thanks!
Updater component is producing new list of themes every 3seconds
It must also dispatch setSelectedTheme action to update selected theme in application state

How can I stop this wierd output of my lists in react

I want to make each element in a list clickable separately. I have an array of divs which I will loop into an array soon but for simplicity, I just hardcoded them into it(I am going to add more elements once I figure this out). When I click on the list item div, I want it to turn that Item into the text: "clicked".
I want to keep the files separate because this app will get big and I'm planning to add much more.
App.js
import React, { Component } from 'react';
import './App.css';
import Comp from './Comp';
class App extends Component {
state = {
list: [
"gameplay",
"visuals"
]
}
changetext = event =>{
this.setState({list: event.target.textContent = "clicked"});
}
render() {
return (
<div>
<Comp list = {this.state.list}
changetext = {this.changetext}/>
</div>
);
}
}
export default App;
Comp.js
The problem here is that when I click on a list item, The event.target.textContent is inputting {props.list[0]} and {props.list[1]} into the event object and turn both elements into c and l respectively.. both are the first and second elements in the string array "clicked".
The strange thing is, when I click the c or the l the second time, they act as I wanted them to and separately turn into clicking. So the question is, How can I achieve this without the initial hiccup? Let me know if you need set up information.
import React from 'react';
const Comp = props => {
let listarr = [];
listarr[0] = <div key = {0} onClick = {props.changetext}{props.list[0]}
listarr[1] = <div key = {1} onClick = {props.changetext}>{props.list[1]}
</div>
return(
<div>{listarr}</div>
);
}
export default Comp;
You have a couple of syntax errors. If you want to change the text to "clicked" you can do it like this:
const Comp = props => {
let listarr = [];
listarr.push(<div key={0} onClick={props.changetext}>{props.list[0]}</div>);
listarr.push(<div key={1} onClick={props.changetext}>{props.list[1]}</div>);
return (
<div>{listarr}</div>
);
}
class App extends Component {
state = {
list: [
"gameplay",
"visuals"
]
}
changetext = event => {
const { textContent } = event.target;
// Always use the callback syntax for setState when you need to refer to the previous state
this.setState(prevState => ({
list: prevState.list.map(el => textContent === el ? "clicked" : el)
}));
}
render() {
return (
<div>
<Comp list={this.state.list}
changetext={this.changetext} />
</div>
);
}
}
Just change the method you are passing as a property to be:
this.changetext.bind(this)
so it will look like this:
<div>
<Comp list = {this.state.list}
changetext = {this.changetext.bind(this)}/>
</div>
Or your other option could be to do this in the constructor:
constructor() {
super();
this.changetext.bind(this);
}
...
render() {
<div>
<Comp list = {this.state.list}
changetext = {this.changetext}/>
</div>
}

Categories

Resources