I'm creating a list in react native and each element is clickable. When the element is clicked it navigates to another Scene and passes an object based on the element that was clicked and the value of 'i'.
But when clicking an element it always send the object that 'i' ended on. Which makes sense.
constructor(props) {
super(props);
this.state = {
items: this.props.items
};
}
makeList() {
var items = []
for (var i = 0; i <3; i++) {
items.push(
<TouchableOpacity
activeOpacity={0.6}
onPress={() => this.sendItem(this.state.items[i])}>
<View>this.state.items[i].name</View>
</TouchableOpacity>
)
}
return items;
}
render() {
return (
<View>
{this.makeList()}
</View>
);
}
sendItem(item) {
this.props.navigate(item);
}
So whenever any one of the items are clicked it always send 'Four'.
How can i fix this so that it sends the correct object?
Thank you!
var doesn't create a new variable for each iteration of the loop, so you're mutating the same i variable. When the loop ends, i === 3, and so that's what the onPress callback sees. You can use let instead.
for (let i = 0; i <3; i++) {
Or you can avoid loops.
const items = Array.from({length: 3}, (x, i) => {
return <TouchableOpacity ... />;
});
Related
I have this code and I want that after click li console.log display number of page. So I count my pages in pages const. I tryed do that on two way.
First:
const handlePageClick = (i) => {
console.log(i);
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i <= pages; i++){
list.push(<li key={i} onClick={handlePageClick(i)}>{i}</li>)
}
return list;
}
return (
<React.Fragment>
<div className="row">
<ul>
<Pagination pages={pages} />
</ul>
</div>
</React.Fragment>
);
It run handlePageClick method after run the app, not after click.
Second method I tryed:
const linkRef = useRef(null);
const handlePageClick = (i) => {
console.log(linkRef.current.innerText);
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i <= pages; i++){
list.push(<li key={i} ref={linkRef} onClick={handlePageClick}>{i}</li>)
}
return list;
}
It display the last result all the times. How can I solve my problem?
You have to pass a function to your onClick handler, so you can call handlePageClick in that function, and pass your current value of i like this:
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i <= pages; i++){
list.push(<li key={i} ref={linkRef} onClick={() => handlePageClick(i)}>{i}</li>)
}
return list;
}
Notice how you're now passing () => handlePageClick(i) is now what you're passing to onClick. This is a function that gets executed when the click event happens, and it passes your current value of i
In your first attempt you need to use onClick={() => handlePageClick(i)}. This is because currently it is invoked on the app render because you are invoking the method so you create an infinite loop where the app is rendered -> the onClick event is invoked which causes a another render. Using an arrow function means you pass a function which is why you invoke it.
have a simple React component that displayes a character and should call a handler when clicked, and supply a number. The component is called many times, thus displayed as a list. The funny thing is that when the handler is called, the supplied index is always the same, the last value of i+1. As if the reference of i was used, and not the value.
I know there is a javascript map function, but shouldn't this approach work too?
const charComp = (props) => {
return (
<div onClick={props.clicked}>
<p>{props.theChar}</p>
</div>
);
deleteHandler = (index) => {
alert(index);
}
render() {
var charList = []; // will later be included in the output
var txt = "some text";
for (var i=0; i< txt.length; i++)
{
var comp =
<CharComponent
theChar = {txt[i]}
clicked = {() => this.deleteHandler(i)}/>;
charList.push(comp);
}
Because by the time you click on a letter, i is already 9 and it will remain 9 since the information is not held anywhere.
If you want to keep track of the index you should pass it to the child component CharComponent and then pass it back to the father component when clicked.
const CharComponent = (props) => {
const clickHandler = () => {
props.clicked(props.index);
}
return (
<div onClick={clickHandler}>
<p>{props.theChar}</p>
</div>
);
};
var comp = (
<CharComponent theChar={txt[i]} index={i} clicked={(index) => deleteHandler(index)} />
);
A little codesandbox for ya
I made a sorting algorithm visualiser which displays vertical bars of different heights and sort them. I have used a button here called "Generate new Array" which will call a function to generate new array everytime and I have also used this function in componentDidMount() function. How do I change the style property whenever I click that button?
I tried taking document.getElementByClassName('array-bars') into an array and change its style property using loop but its not happeneing. I am adding the necessary code below.
{ //array is const storing array of numbers which is also only state of this program.
array.map((value, idx) => (
<div
className="array-bar"
key={idx}
style={{ height: value, backgroundColor: 'turquoise' }}></div>))
}
componentDidMount(){
this.resetArray();
}
// this is called when I click generate new array
resetArray(){
const array = [];
for (let i = 0; i < 300; i++) {
array.push(randomIntFromInterval(15, 650));
}
const arrayBars = document.getElementByClassName('array-bar');
for (let i = 0; i < arrayBars.length; i++)
arrayBars[i].style.backgroundColor = 'green'; //this is failing
this.setState({ array });
}
Edited:
This is function where I am changing style property using the method written in above code. Its working here.
Also, Can you tell how can I change the color in this mergeSort() in last?
I tried using this.setState() at last but that's changing the color in the beginning only.
mergeSort(){
for(let i=0;i<animations.length;i++){
const arrayBars= document.getElementsByClassName('array-bar');
const colorChange=i%3!==2;
if(colorChange){
const [barOne,barTwo] =animations[i];
const barOneStyle=arrayBars[barOne].style;
const barTwoStyle=arrayBars[barTwo].style;
const color=i%3===0?'red':'turquoise';
setTimeout(()=>{
barOneStyle.backgroundColor=color;
barTwoStyle.backgroudColor=color;
},i*2);
}
else{
setTimeout(()=>{
const[barOne,newHeight]=animations[i];
const barOneStyle=arrayBars[barOne].style;
barOneStyle.height=newHeight+'px';
},i*2)
}
}
}
In React, you should rely on state changes to "make things happen". As suggested by other in the question comments, set an initial state containing the initial background color and update the value as needed.
UPDATE: If you want changes to happen after button click, just set its onclick attribute to point to a function that does what you want. Here I added a button and pointed its onclick attribute to resetArrays.
class YourComponent extends React.Component {
constructor(props){
super(props);
this.state = {
barBg: 'turquoise'
}
}
render(){
return <div>
<button onClick={this.resetArray.bind(this)}>Generate new array</button>
{ //array is const storing array of numbers which is also only state of this program.
array.map((value, idx) => (
<div
className="array-bar"
key={idx}
style={{ height: value, backgroundColor: this.state.barBg }}></div>))
}
</div>
}
componentDidMount(){
this.resetArray();
}
// this is called when I click generate new array
resetArray(){
const array = [];
for (let i = 0; i < 300; i++) {
array.push(randomIntFromInterval(15, 650));
}
this.setState({ array, barBg: 'green' });
}
}
I'm not sure what I could do to make this work...So I'm calling API data (which is just a bunch of nested json objects) to create a menu. So far I have it working so that it renders the first layer of objects (first img), and when you click on each element, the next level shows up below it (second img). This works recursively, so I assume it to work for the next level down when I click on an element in "level 2", but this is not the case.
Can anyone see what I could change in my code to make this work?
class Menu extends React.Component {
state = {
categories: [],
list: ""
};
//handle displaying list of values when clicking on button
//search for list of values within object
handleSearch = (obj, next, event) => {
// console.log(event.target.name);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === "object") {
if (next === key) {
//create DOM CHILDREN
createData(Object.keys(obj[key]), key, this.test, event);
}
this.handleSearch(obj[key], next);
}
});
};
componentDidMount() {
axios.get("https://www.ifixit.com/api/2.0/categories").then(response => {
this.setState({ categories: response.data });
});
}
render() {
return (
<React.Fragment>
{/* display list of things */}
<div className="columns is-multiline">
{Object.keys(this.state.categories).map(key => (
<div className="column is-4">
<div
onClick={event =>
this.handleSearch(this.state.categories, key, event)
}
>
{key}
</div>
<div name={key + "1"} />
</div>
))}
</div>
</React.Fragment>
);
}
}
and here is the code for createData, which creates the next level of data and is supposed to make it so these elements can be clicked to show the next level
var index = 1;
export function createData(data, key, search, e) {
e.stopPropagation();
let parent = document.getElementsByName(key + "1");
//create elements and append them
for (let i = 0; i < data.length; i++) {
let wrapper = document.createElement("ul");
wrapper.innerHTML = data[i];
wrapper.name = data[i] + index + 1;
wrapper.addEventListener("click", search(data[i]));
parent[0].append(wrapper);
}
}
here's a screenshot of "categories" in the console (objects within objects within objects)
For simplicity we will render this nested object recursively which is structured as your categories object:
const categories = {
level1: {
"level1-1": {
"level1-1-1": {
"level1-1-1-1": {}
}
},
"level1-2": {
"level1-2-1": {}
}
},
level2: {}
};
Recursion end condition will be an object without keys.
For every layer, render the keys and make a recursion step.
const makeMenuLayer = layer => {
const layerKeys = Object.entries(layer).map(([key, value]) => (
<>
{key}
{makeMenuLayer(value)}
</>
));
return <div>{layerKeys}</div>;
};
Will result:
level1
level1-1
level1-1-1
level1-1-1-1
level1-2
level1-2-1
level2
Checkout the sandbox example.
You should try to avoid manipulating the DOM by creating/appending elements with vanilla JS while using React if possible, one of its main strengths is the virtual DOM which manages rendering for you and avoids conflicts.
Not sure how deep the objects you're rendering go, but due to the complexity of the data this would be a good case for using another component to modularize things and avoid the messiness you're dealing with now.
Assuming all the child objects are structured the same, you could create a component that renders itself recursively based on the object keys. This answer should help.
I'm trying to build a connect 4 game, which has a Board component comprised of 7 Column components all contain 6 Space components. Every Column has a Drop button above it, which will be used to drop the piece into the column. In order to alternate between "red" and "black" players, I have created a state "redOrBlue" which returns a boolean.
In a nutshell, when the Drop button is clicked, I want to toggle the value of "redOrBlue" to keep track of whose turn it is. I've set the onClick function to setState({redOrBlue: !this.state.redOrBlue)}, however, calling this function will cause react to render an extra column right below the column in which the button was clicked. I understand that setState automatically re-renders the DOM, but how can I keep from it rendering duplicates of a component? Thanks for your help!
class Column extends Component{
constructor(){
super();
this.state = {
myArray: [],
buttonArray: [],
buttonNumber: null,
newArray: [],
redOrBlue: true
}
}
makeRow(){
var component = <Space className="space"/>
for(var i = 0; i < 6; i++){
this.state.myArray.push(component);
}
}
handleClick(){
var num = this.props.colNumber;
var array = this.props.boardArray[num];
var boolean = false;
var color;
if(this.state.redOrBlue === true){
color = "red"
}else{
color = "black"
}
for(var i = 5; i > -1; i--){
if(boolean === false){
if(array[i] === null){
array[i] = color;
boolean = true;
}
}
}
this.setState({redOrBlue: !this.state.redOrBlue})
console.log(array)
}
render(){
{this.makeRow()}
return(
<div className="column">
<DropButton onClick={() => this.handleClick()} id={'button-' + this.props.colNumber} buttonNumber={this.props.colNumber} className={this.props.className}/>
{this.state.myArray.map(function(component, key){
return component
})}
</div>
)
}
}
There are many things that you need to change in your code:
*First of all never store the ui items in state variable, state variable should contain only data and values.
*Never do any changes in state variable by this.state.a = '' or this.state.a.push({}), always treat the state values as immutable and use only setState to change the value.
*Always call function inside render that will create the ui part directly if you want to create something dynamically.
Call makeRow method from render and it will return the ui directly without storing it in state variable, like this:
makeRow(){
var component = [];
for(var i = 0; i < 6; i++){
component.push(<Space key={i} className="space"/>);
}
return component;
}
render(){
return(
<div className="column">
<DropButton onClick={() => this.handleClick()} id={'button-' + this.props.colNumber}
buttonNumber={this.props.colNumber} className={this.props.className}/>
{this.makerow()}
</div>
)
}
Remove {this.makeRow()} from your render function. All you're doing is adding another row to the state, in a rather non-kosher method, every time the component renders. Try something like this:
constructor(){
super();
this.state = {
myArray: [],
buttonArray: [],
buttonNumber: null,
newArray: [],
redOrBlue: true
}
this.makeRow();
}
makeRow(){
var tempArray = [];
var component = <Space className="space"/>
for(var i = 0; i < 6; i++){
tempArray.push(component);
}
this.setState({ myArray: tempArray }};
}