How to rerender .map() list - javascript

I'm trying to rerender a list when I click a button that sends the first element of the array to the last position, however, when I click the button the component doesn't rerender, even thou the console.log shows that the array has changed:
codesandbox
import React, { useState } from "react";
const DailySchedule = () => {
const [exerciseList, setExerciseList] = useState([
"exercise 1",
"exercise 2",
"exercise 3"
]);
return (
<div>
<section>
<h2>Warm-up</h2>
<ul>
{exerciseList.map((exrcs, idx) => {
return (
<li>
{exrcs}{" "}
{idx === 0 && (
<button
onClick={() => {
exerciseList.push(exerciseList.shift());
setExerciseList(exerciseList);
console.log(exerciseList);
}}
>
Done
</button>
)}
</li>
);
})}
</ul>
</section>
</div>
);
};
export default DailySchedule;

Its because you're modifying the exerciseList array directly, which you shouldn't do, as the state update will see that the list is the same and not trigger a re-render. Instead make a copy of the array, and then use setExerciseList:
const newList = [...exerciseList]
newList.push(newList.shift())
setExerciseList(newList)

This issue is because array reference is not changed.
onClick={() => {
const list = [...exerciseList]
list.push(list.shift());
setExerciseList(list);
}}

This is because the array reference in state is not changed. Update the setState call like this,
<button
onClick={() => {
exerciseList.push(exerciseList.shift());
setExerciseList([...exerciseList]);
console.log(exerciseList);
}}
>
Done
</button>

You have to change the array reference to reflect that in the state.
setExerciseList([...exerciseList]);
Working code - https://codesandbox.io/s/react-playground-forked-ohl1u

As others have pointed out, you're mutating your state directly by using .push() and .shift(). This is a "no-no" in the react-world. Instead, you can treat your array as immutable by not changing your original state, but rather by producing a new array so that your state will update correctly. One approach to do this is to destructure the first item from your array and obtain the rest of your array in an array called rest, then set your new state using the destructured variables using the spread syntax (...):
onClick={() => setExerciseList(([first, ...rest]) => [...rest, first])}

Related

React, updating array to a new array doesn't re-render the dom with the new array

so i'm looping over an array, and every element has a click event :
{optionsCategory.map((c,i) => (
<div className="content" key={i} onClick={(e) => handleCategoryClick(e,c)}>
{c.name}
</div>
))}
upon clicking if the element has a subCategory i want to assing the subCategory array to the optionsCategory
const handleCategoryClick = async (e: React.MouseEvent<HTMLHeadingElement>, c: Category) => {
if(c.subCategories.length > 0) {
setOptionsService([...c.subCategories])
console.log([optionsCategory])
}
else{
setIsCategory(true);
const data = await onActsByServiceIdAndCategoryId(serviceId,c.id);
setActs([...data])
console.log(acts);
}}
in the console the optionsCategory is updated but not in the Dom
You need to put your optionsCategories inside a useState hook, then react will update the dom, for example: https://jsfiddle.net/7u9cqtxd/
In this case, every two seconds i'll push a new value to optionsCategory and react will update the dom.
const [optionsCategory, setCategory] = useState([30, 20, 10]);
setTimeout(() => setCategory([ ...optionsCategory, 50]), 2000);
return (
<div>
{optionsCategory.map((category) =>
<li key={category}>
{category}
</li>
)}
</div>
);
My bad, after looking at it i used setOptionsService instead of setOptionCategory

how to update FieldArray elements with redux store variable

I am using redux-form with a FieldArray.By default 1 element will be there in array and it is populated from JSON. I can add upto 3
elements in FieldArray component.
In below code, 'elementList'
property is coming from JSON and also I have store variables named
as'elements' and 'elementList'. I initialize these store variable with elementList
from JSON at first and then keep updating store variables when 'Add
Element' is clicked on. I can see store variables are updating
properly but on screen Field array elements are not updating.It may be because name property 'elementList' in FieldArray may refer to
JSON element.
Is it possible, if I can refer to store variables 'elementList' or 'elements' in name property
of 'FieldArray'. Please advice.
Main page
<div>
<FieldArray name="elementList" component={Elements}
props={this.props}/>
<button type="button" className="btn btn-primary"
onClick={event => this.addElement(elementDTO)}>Add Element
</button>
<br/>
</div>
addElement(elementDTO){
if(this.props.elements && this.props.elements!=undefined && this.props.elements.length >= 3){
return;
}
this.props.addReqElement(this.props.elements);
}
Field Array page
const elements= ({props, meta: {error, submitFailed}}) => {
const {fields} = props;
return (
{fields.map((element, index) => (
<div>
//Field definitions
</div>
))}
Thank you
Update:
Adding method from Redux Action and Reducer
export function addReqElement(childList) {
let state = store.getState()
let newChild=
state.requestReducer.DTOobj.requestDoc; //this is an empty element coming from backend with few properties and adding rest of the //proerties as below to create a new child
newChild.prop1 = null
newChild.prop2 = null
childList.push(newChild)
return (dispatch) => {
dispatch(setDoc(childList));
}
}
export function setDoc(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Update 2: I tried to remove push and used spread operator , but it did not work. I have inner array also, that is working as I am using different strategy. I take pull parent array ,it's index and update parent array with the new inner array. It works but parent array I am not getting how should I make it work. I tried to set the main array to the form props and render full page by dispatching an action but it did not work. Any suggestions plz?
From the main array page:
render() {
const {fields, parentArrayFromStore} = this.props;
return (
<div className="col-sm-12">
{fields.map((doc, index) => (
<div key={index}>
<div className="col-sm-12">
<FieldArray name={`${doc}.innerArrayList`} component={CustomInnerArrayComponent}/>
</div>
<div className="row">
<div className="col-sm-4">
<button type="button" className="btn btn-primary"
onClick={event => this.addInnerArray(index, parentArrayFromStore ,fields.get(index).innerArrayList)}>Add Printer
</button>
</div>
</div>
</div>
))}
</div>)
}
In Action class
export function addInnerArray(index, parentArrayFromStore, innerArrayList) {
let newInnerItem= {};
newInnerItem.prop1 = null
newInnerItem.prop2 = null
innerArrayList.push(newInnerItem)
parentArrayFromStore[index].innerArrayList = innerArrayList;
return (dispatch) => {
dispatch(setParentArray(parentArrayFromStore));
}
}
export function setParentArray(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Hi the issue is with the push statement in your function when updating states in the constructor or reducer use concat or spread operator[...]>
I have made a sample over here
please check
onAddItem() {
let list = [...this.state.items, {text:`Check 1`}];
this.setState({items:list});
}
in your case you can do the following
let arr = [...childList, newChild]
then dispatch the arr
dispatch(setDoc(arr));

React returns older state value onClick

I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.

How to pass props to subcomponent in Reactjs

I am attempting to pass props from a parent component function to a child in React without any luck. I am utilizing the React Data Table Component's filtering functionality. The example documentation in Storybook is great, however I want to change the search box into a list of tags, that when clicked, will filter the data table just the same.
The data I'm trying to parse into individual "tags" is structured like this:
[{"id":"09090","first_name":"Cynthia","last_name":"McDonald","email":"email1#gmail.com","profile_url":"https:myprofile.com/1","types":["professor","science"]},
{"id":"03030","first_name":"Ryan","last_name":"Burke","email":"email2#gmail.com","profile_url":"https://myprofile.com/2","types":["student","science"]},
{"id":"05050","first_name":"Stewart","last_name":"Hook","email":"email3#gmail.com","profile_url":"https://myprofile.com/3","types":["professor","math"]}]
I am trying to create a unique tag list of the "types" attribute that acts as a filter onClick instead of onChange, just like the original search textbox example. So based on the sample data, we would end up with tags for professor, science, student, and math.
If you look at the code in Storybook, more specifically, line 45:
const subHeaderComponentMemo = React.useMemo(() =>
<Filter onFilter={value => setFilterText(value)} />, []);
My data is loaded via an API call and I am trying to pass that data to the subHeaderComponentMemo with props, i.e.,
const subHeaderComponentMemo = React.useMemo(() =>
<Filter data={people} onFilter={value => setFilterText(value)} />, []);
I am then trying to receive and loop through that data on line 20 and am replacing most of that code so that it will render the unique tags from the types attribute of the data.
Storybook code:
const Filter = ({ onFilter }) => (
<TextField id="search" type="search" role="search" placeholder="Search Title" onChange={e => onFilter(e.target.value)} />
);
My failed code
const Filter = ({props, onFilter }) => {
// Get a unique array of types
const types = [...new Set(props.types.map(type => type))];
return types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
));
};
This is of course resulting in an epic fail. The module isn't even displaying.
I'm new to React, so any insight/help would be greatly appreciated.
Updated code based on feedback
const Filter = ({ data, onFilter }) => {
console.dir(data);
if (data.length === 0) {
return <div>No data</div>;
}
const types = [...new Set(data.types.map(type => type))];
return (
<>
{types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
))}
;
</>
);
};
One of the errors I got when I made the changes was "cannot read property of 'map' undefined", which I equated to the fact that this component may be rendering before the data gets there and it is empty. So I added an if with a return in case this happens.
I also rewrote the return the statement in Filter based on the feedback because it did look like it would loop and try to render a new component for each iteration.
However, the issue still stands with the data not being present. The table is now loading with the data, but this component is just returning the "No data" div as if nothing ever happened. I'm not sure if this is due to the useMemo hook or what. Besides that, no errors.
Thank you for your time and feedback.
Another update
UseMemo needed the prop to be a dependency in its array... I'm finally seeing data when I console.log it.
const subHeaderComponentMemo = useMemo(
() => <Filter data={people} onFilter={value => setFilterText(value)} />,
[people]
);
However, I'm getting the 'map' undefined error again, as if my data is not in the structure I expect it to be in. I have not changed it and this is the code I'm using to try to access it (as shared before):
const types = [...new Set(data.types.map(type => type))];
Thanks again everyone... getting closer!
you should rewrite you component signature to match the one you are trying to use
const Filter = ({props, onFilter }) => // you are expecting a props name props
<Filter data={people} onFilter={value => setFilterText(value)} />, []); // you are passing data and setFilterText
so you should change it to
const Filter = ({data, onFilter }) =>

Map different arrays into same component

Using react, I need to pass by props data to a component, the only problem is that this data comes from two different arrays.
How can I pass it by creating only one component?
If I do this, mapping both arrays, I get two components and it has to be only one:
const Field2 = (props) => {
return (
<div className={"field2"}>
{props.thumbnails.map(a =>
<Field2Content label={a.label}
/>
)}
{props.texturasEscolhidas.map(b =>
<Field2Content name={b.name}
/>
)}
</div>
)
};
export default Field2;
If I do:
{props.thumbnails.map(a =>
<Field2Content label={a.label}
name={'hello'}
/>
)}
I get this:
The 'hello' is what I need to get from the texturasEscolhidas array.
"Color of leg" and "Colour" are created when the component is renderer, the Hello should only appear when a button is clicked, and it's dynamic, changing by which of button is pressed.
To use just one component, assuming the both arrays have the same length, you can get the label and the name by iterating one array and accessing the element of the other array by index (the second parameter in the callback of the map):
const Field2 = (props) => {
return (
<div className={"field2"}>
{props.thumbnails.map((a, index) =>
<Field2Content
label={a.label}
name={(props.texturasEscolhidas[index] || {}).name}
/>
)}
</div>
)
};

Categories

Resources