How to map array of objects in React js - javascript

I am trying to map the array which I get from API call to the state. Actually it maps elements of array in the correct count. However, I get only 1 st element of array n times(n length of array). Where I did mistake???
export default class NewCalendarView extends Component {
componentDidMount() {
API.getLectures().then((res)=>{
console.log(res)
let cal=res.map((lec)=>{
let lecture={
title: lec.subjectName,
startDate : moment(lec.dateHour).toDate(),
endDate: moment(lec.dateHour).toDate()
}
console.log("lec "+ JSON.stringify(lecture));
return lecture;
})
this.setState({events:cal,loading:null,serverErr:null})
}).catch((err)=>{
this.setState({serverErr:true,loading:null})
})
}
constructor(props) {
super(props);
this.state = {
events: []
}
}
render() {
return (
<div style={{
flex: 1
}}>
<Calendar
localizer={localizer}
events={this.state.events}
startAccessor='startDate'
endAccessor='endDate'
views={['month', 'week', 'day']}
culture='en'
/>
</div>
);
}
}
json from API call
res: [{"subject":"SoftwareEngineering II","date":"2020-11-16","hour":"15:26","modality":"In person","room":"12A","capacity":150,"bookedStudents":100,"teacherName":"Franco yjtyjty","lectureId":1,"booked":false},{"subject":"SoftwareEngineering II","date":"2020-11-14","hour":"17:26","modality":"In person","room":"12A","capacity":50,"bookedStudents":100,"teacherName":"Franco yjtyjty","lectureId":2,"booked":false},{"subject":"SoftwareEngineering II","date":"2020-11-13","hour":"17:26","modality":"In person","room":"12A","capacity":50,"bookedStudents":100,"teacherName":"Franco yjtyjty","lectureId":3,"booked":false},{"subject":"SoftwareEngineering II","date":"2020-11-17","hour":"17:26","modality":"In person","room":"12A","capacity":50,"bookedStudents":100,"teacherName":"Franco yjtyjty","lectureId":4,"booked":false}]

Looks like the keys that you are trying to access do not exist in the res object. Try replacing the keys as defined below and that might help.
It can be confusing to match the parameters in the cloud and app at times especially with camelCase and kebab-case conventions!
let lecture= {
title: lec.subject,
startDate : moment(lec.date).toDate(),
endDate: moment(lec.date).toDate()
}

I would suggest removing this piece of code -
let cal=res.map((lec)=>{
let lecture={
title: lec.subjectName,
startDate : moment(lec.dateHour).toDate(),
endDate: moment(lec.dateHour).toDate()
}
console.log("lec "+ JSON.stringify(lecture));
return lecture;
})
just do this instead -
this.setState({events:JSON.stringy(res),loading:null,serverErr:null})
also I noticed that there is no unique key , this is why its always the first object repeated n times
anyways I noticed Talha Azhar has already answered while I was typing my answer his answer will definitely help , also you can try doing what I suggest above it will also reduce your code .

You're mapping properties that don't exist in your response. You should store the lectureId to use it as key.
I'd also suggest you use a more idiomatic approach, using function components and hooks.
You can rewrite your component like this:
function NewCalendarView() {
const [serverErr, setServerErr] = useState();
const [loading, setLoading] = useState();
const [events, setEvents] = useState();
useEffect(() => {
(async () => {
try {
const lectures = await API.getLectures();
const cal = lectures.map(({ lectureId, subject, date }) => ({
id: lectureId,
title: subject,
startDate: moment(date).toDate(),
endDate: moment(date).toDate()
}));
setEvents(cal);
setServerErr(null);
} catch (e) {
setEvents();
setServerErr(true);
}
setLoading(null);
})();
}, []);
return (
<div
style={{
flex: 1
}}
>
<Calendar
localizer={localizer}
events={events}
startAccessor="startDate"
endAccessor="endDate"
views={["month", "week", "day"]}
culture="en"
/>
</div>
);
}
I put a functional version in this sandbox: https://codesandbox.io/s/react-playground-forked-7sm6h?file=/index.js:2582-3523

Related

How to sync a JS class to a component's state in React?

I am completing a technical challenge and came across a scenario I never had to deal with before.
I am asked to code up a shopping cart that has a UI that represents basic checkout data like order total, current items in cart, etc.
One of the requirements states I need to implement a Checkout class that can be instantiated:
const checkout = new Checkout();
And I should be able to obtain basic info from it like:
const total = checkout.total();
And add items to the cart through it:
checkout.add(product.id);
What makes this a tricky thing to solve is that I can't think of a clean "DRY" way of implementing it into the UI. This is mainly because any updates in the checkout class will not trigger any re-renders since it's not part of the state. I would usually use state variables for this.
I tried binding state variables to parameters in the checkout class like:
const [total, setTotal] = useState();
useEffect(()=>{
setTotal(checkout.total)
}, [checkout.total])
But checkout.total is only the reference to the method, so it never changes so I do not get the binding I want.
Trying out other stuff I managed to put together a "solution" but I question whether it is a good pattern.
I basically pass a callback to the checkout class which is called whenever the cart is updated. The callback is a state variable's setter, so:
const [cart, setCart] = useState<string[]>(checkout.cart);
checkout.callback = setCart;
Then inside the add method:
add(productId) {
// Some code...
this.callback([...this.cart]);
}
What this grants is that the cart state variable is updated whenever the checkout class has changes in its parameters. So it fires a rerender on the Cart component and all of its children that have props being passed down. Therefore I get a synced UI.
The thing is I kind of don't need the cart variable other than for forcing re-renders. I can get the cart info directly from the checkout class which is what I do. But for it to be reflected in the UI I need some state variable to be updated. It could even be a counter, I only went for cart instead of a counter to make it more coherent I guess.
Am I overcomplicating things here? Is there a pattern I am missing that is used for this scenarios? How does one usually interact with an instantiated class and ensures the UI is somehow updated from changes to the class?
EDIT (adding missing info):
The Checkout class needs to implement the following interface:
interface Checkout {
// ...
// Some non relevant properties methods
// ...
add(id: number): this;
}
So it is explicitly asked that the add method returns this (in order to allow function chaining).
mixing of patterns
Using OOP instances with methods that mutate internal state will prevent observation of a state change -
const a = new Checkout()
const b = a // b is *same* state
console.log(a.count) // 0
a.add(item)
console.log(a.count) // 1
console.log(a == b) // true
console.log(a.count == b.count) // true
React is a functional-oriented pattern and uses complimentary ideas like immutability. Immutable object methods will create new data instead of mutating existing state -
const a = new Checkout()
const b = a.add(item) // b is *new* state
console.log(a.count) // 0
console.log(b.count) // 1
console.log(a == b) // false
console.log(a.count == b.count) // false
In this way, a == b is false which effectively sends the signal to redraw this component. So we need a immutable Checkout class, where methods return new state instead of mutating existing state -
// Checkout.js
class Checkout {
constructor(items = []) {
this.items = items
}
add(item) {
return new Checkout([...this.items, item]) // new state, no mutation
}
get count() {
return this.items.length // computed state, no mutation
}
get total() {
return this.items.reduce((t, i) => t + i.price, 0) // computed, no mutation
}
}
export default Checkout
demo app
Let's make a quick app. You can click the 🍐 and 🥨 buttons to add items to the cart. The app will show the correct count and total as well as the individual items -
App component preview
Now "syncing" the class to the component is just using ordinary React pattern. Use your class and methods directly in your componenets -
import Checkout from "./Checkout.js"
import Cart from "./Cart.js"
function App({ products = [] }) {
const [checkout, setCheckout] = React.useState(new Checkout)
const addItem = item => event =>
setCheckout(checkout.add(item))
return <div>
{products.map(p =>
<button key={p.name} onClick={addItem(p)}>{p.name}</button>
)}
<b>{checkout.count} items for {money(checkout.total)}</b>
<Cart checkout={checkout} />
</div>
}
const data =
[{name: "🍐", price: 5}, {name: "🥨", price: 3}]
const money = f =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(f)
A simple Cart component uses JSON.stringify to quickly visualize each item -
// Cart.js
function Cart({ checkout }) {
return <pre>{JSON.stringify(checkout, null, 2)}</pre>
}
export default Cart
Run the demo below to verify the result in your browser -
class Checkout {
constructor(items = []) {
this.items = items
}
add(item) {
return new Checkout([...this.items, item])
}
get count() {
return this.items.length
}
get total() {
return this.items.reduce((t, i) => t + i.price, 0)
}
}
function App({ products = [] }) {
const [checkout, setCheckout] = React.useState(new Checkout)
const addItem = item => event =>
setCheckout(checkout.add(item))
return <div>
{products.map(p =>
<button key={p.name} onClick={addItem(p)}>{p.name}</button>
)}
<b>{checkout.count} items for {money(checkout.total)}</b>
<Cart checkout={checkout} />
</div>
}
const money = f =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(f)
function Cart({ checkout }) {
return <pre>{JSON.stringify(checkout, null, 2)}</pre>
}
const data = [{name: "🍐", price: 5}, {name: "🥨", price: 3}]
ReactDOM.render(<App products={data} />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Hmm, looks like you need to share the state. The first solution that came to my mind is just to use the Class component. You can use force rerender while you need and write more custom logic without useEffect hacks.
The second solution is more clear IMO. It uses an Observer pattern. You need to add a subscription to your Checkout class. So basically.
useEffect(() => {
const subscription = (newState) => setState(newState)
const instance = new Checkout()
instance.subcribe(subscription)
return instance.unsubcribe(subscription)
}, [setState])
Since setState is immutable, this hook will be run only once.
Your idea is correct, you need somehow to start re-render to sync state of checkout object and state of a component.
E.g. you may do it by context and force update (in case if you do not want to duplicate data in object and state):
const CheckoutContext = React.createContext();
const checkout = new Checkout();
const CheckoutProvider = ({ children }) => {
// init force update, just to start re-render
const [ignored, forceUpdate] = React.useReducer((x) => x + 1, 0);
const add = (a) => {
checkout.add(a);
forceUpdate();
};
const total = checkout.total();
const value = { add, total };
return (
<CheckoutContext.Provider value={value}>
{children}
</CheckoutContext.Provider>
);
};
const Child = () => {
const v = React.useContext(CheckoutContext);
console.log(v.total);
return <button onClick={() => v.add(100)}>Click</button>;
};
export default function App() {
return (
<div className="App">
<CheckoutProvider>
<Child />
</CheckoutProvider>
</div>
);
}
You can make a Cart class that allows for observers to be notified when something important happens. To make it available for the react components, provide an instance of it with a context, and use a stateful hook to notify components by setting the state through the observer function.
Here we go:
First, we need a Cart class that notifies observers when something happens
export class Cart {
constructor() {
this.products = [];
this.subscribers = new Set();
}
subscribe = (notifyMe) => {
this.subscribers.add(notifyMe);
};
unSubscribe = (notifyMe) => {
this.subscribers.delete(notifyMe);
};
addToCart = (product) => {
this.products = [...this.products, product];
this.notify();
};
removeFromCart = (product) => {
this.products = this.products.filter(product);
this.notify();
};
notify = () => {
this.subscribers.forEach((n) => n(this.products));
};
}
We will expose this through the react tree with a context, so lets make one
const CartContext = React.createContext();
export const CartProvider = ({ children, cart }) => {
return <CartContext.Provider value={cart}>{children}</CartContext.Provider>;
};
Now for the trick! A hook that will update its state using the carts observer pattern, thereby notifying the component that uses it.
export const useCart = () => {
const cart = React.useContext(CartContext);
const [content, r] = React.useState();
React.useEffect(() => {
const notify = (productsInCart) => r(productsInCart);
cart.subscribe(notify);
cart.notify();
return () => cart.unSubscribe(notify);
}, [cart, r]);
return {
addToCart: cart.addToCart,
removeFromCart: cart.removeFromCart,
content
};
};
Note that it can be worth to update after subscribing.
Now we have our library set up, we can make some components. So here's where we instantiate the Cart class. We make a new Cart, and let the provider provide that instance
const cart = new Cart();
export default function App() {
return (
<div className="App">
<CartProvider cart={cart}>
<CartCounter />
<h1>Welcome to the shop</h1>
<h2>start putting stuff in the cart!</h2>
<Catalog />
<button
onClick={() => {
// this will still notify components
cart.addToCart({ foo: "bar" });
}}
>
add product by directly manipulating class instance
</button>
</CartProvider>
</div>
);
}
Here are the other components
const Catalog = () => {
const getProducts = async () =>
await fetch(
"https://random-data-api.com/api/commerce/random_commerce?size=6"
).then((r) => r.json());
const [products, setProducts] = React.useState();
React.useEffect(() => {
getProducts().then(setProducts);
}, []);
if (!products) {
return null;
}
return (
<ul
style={{
listStyle: "none",
display: "grid",
gridTemplateColumns: "50% 50%"
}}
>
{products.map((product) => (
<Item key={product.uid} product={product} />
))}
</ul>
);
};
const Item = ({ product }) => {
const { addToCart } = useCart();
const addProductToCart = () => addToCart(product);
return (
<li>
<article
style={{
maxWidth: 200,
border: "1px solid black",
margin: 10,
padding: 10
}}
>
<h4>{product.product_name}</h4>
<div>
<div>$ {product.price}</div>
<button onClick={addProductToCart}>add to cart</button>
</div>
</article>
</li>
);
};
const CartCounter = () => {
const { content } = useCart();
return <div>items in cart: {content?.length || 0}</div>;
};
This can be a pretty handy pattern, and can be taken pretty far (e.g. React Query works like this).
CodeSandbox link
I read with interest most of the answers, and I found them pretty explicative and comprehensive, especially the extensive example of the observer pattern.
I used the same approach to handle a similar need, without having to implement all the pattern and it makes use of the 'EventEmitter' class.
This way you can subscribe your React UI to several different type of events, olle's example would become something like this:
export class Cart extends EventEmitter {
constructor() {
super();
this.products = [];
}
addToCart = (product) => {
this.products = [...this.products, product];
this.emit("CART_UPDATE", this.products)
};
removeFromCart = (product) => {
this.products = this.products.filter(product);
this.emit("CART_UPDATE", this.products)
};
}
And in React you'd just need a custom hook or just an effect placed on top where you can place your event listeners:
export default function App() {
const cartRef = useRef(new Cart())
const [items, setItems] = useState([])
useEffect(()=>{
const cart = cartRef.current
cart.on("CART_UPDATE", setItems)
return () => cart.removeListener("CART_UPDATE", setItems)
}, []) //
return (
<div className="App">
<div>{items.map(it => item.id)}</div>
<button
onClick={() => {
// this will still notify components
cart.addToCart({ id: "bar" });
}}
>
add product by directly manipulating class instance
</button>
</div>
);
}
I think it is perfectly reasonable to send a callback to the object and then call that callback when it is needed. If you don't want to add any unnecessary data, then don't:
add(productId) {
// Some code...
this.callback();
}
checkout.callback = () => {
setTotal(checkout.total);
}

React | Adding and deleting object in React Hooks (useState)

How to push element inside useState array AND deleting said object in a dynamic matter using React hooks (useState)?
I'm most likely not googling this issue correctly, but after a lot of research I haven't figured out the issue here, so bare with me on this one.
The situation:
I have a wrapper JSX component which holds my React hook (useState). In this WrapperComponent I have the array state which holds the objects I loop over and generate the child components in the JSX code. I pass down my onChangeUpHandler which gets called every time I want to delete a child component from the array.
Wrapper component:
export const WrapperComponent = ({ component }) => {
// ID for component
const { odmParameter } = component;
const [wrappedComponentsArray, setWrappedComponentsArray] = useState([]);
const deleteChildComponent = (uuid) => {
// Logs to array "before" itsself
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5"},
{"uuid":"0ed3cc3-7cd-c647-25db-36ed78b5cbd8"]
*/
setWrappedComponentsArray(prevState => prevState.filter(item => item !== uuid));
// After
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5",{"uuid":"0ed3cc3-
7cd-c647-25db-36ed78b5cbd8"]
*/
};
const onChangeUpHandler = (event) => {
const { value } = event;
const { uuid } = event;
switch (value) {
case 'delete':
// This method gets hit
deleteChildComponent(uuid);
break;
default:
break;
}
};
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
onChangeOut: onChangeUpHandler,
};
setWrappedComponentsArray(wrappedComponentsArray => [...wrappedComponentsArray, objToAdd]);
// Have also tried this solution with no success
// setWrappedComponentsArray(wrappedComponentsArray.concat(objToAdd));
};
return (
<>
<div className='page-content'>
{/*Loop over useState array*/}
{
wrappedComponentsArray.length > 0 &&
<div>
{wrappedComponentsArray.map((props) => {
return <div className={'page-item'}>
<ChildComponent {...props} />
</div>;
})
}
</div>
}
{/*Add component btn*/}
{wrappedComponentsArray.length > 0 &&
<div className='page-button-container'>
<ButtonContainer
variant={'secondary'}
label={'Add new component'}
onClick={() => addOnClick()}
/>
</div>
}
</div>
</>
);
};
Child component:
export const ChildComponent = ({ uuid, onChangeOut }) => {
return (
<>
<div className={'row-box-item-wrapper'}>
<div className='row-box-item-input-container row-box-item-header'>
<Button
props={
type: 'delete',
info: 'Deletes the child component',
value: 'Delete',
uuid: uuid,
callback: onChangeOut
}
/>
</div>
<div>
{/* Displays generated uuid in the UI */}
{uuid}
</div>
</div>
</>
)
}
As you can see in my UI my adding logic works as expected (code not showing that the first element in the UI are not showing the delete button):
Here is my problem though:
Say I hit the add button on my WrapperComponent three times and adds three objects in my wrappedComponentsArray gets rendered in the UI via my mapping in the JSX in the WrapperComponent.
Then I hit the delete button on the third component and hit the deleteChildComponent() funtion in my parent component, where I console.log my wrappedComponentsArray from my useState.
The problem then occurs because I get this log:
(2) [{…}, {…}]
even though I know the array has three elements in it, and does not contain the third (and therefore get an undefined, when I try to filter it out, via the UUID key.
How do I solve this issue? Hope my code and explanation makes sense, and sorry if this question has already been posted, which I suspect it has.
You provided bad filter inside deleteChildComponent, rewrite to this:
setWrappedComponentsArray(prevState => prevState.filter(item => item.uuid !== uuid));
You did item !== uuid, instead of item.uuid !== uuid
Please try this, i hope this works
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item !== uuid));
};
After update
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item.uuid !== uuid)); // item replaced to item.uuid
};
Huge shoutout to #Jay Vaghasiya for the help.
Thanks to his expertise we managed to find the solution.
First of, I wasn't passing the uuid reference properly. The correct was, when making the objects, and pushing them to the array, we passed the uuid like this:
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
parentOdmParameter: odmParameter,
onChangeOut: function(el) { onChangeUpHandler(el, this.uuid)}
};
setWrappedComponentsArray([...wrappedComponentsArray, objToAdd]);
};
When calling to delete function the function that worked for us, was the following:
const deleteChildComponent = (uuid) => {
setWrappedComponentsArray(item => item.filter(__item => __item.uuid !== uuid)); // item replaced to item.uuid
};

react-widgets DropDownList dynamic load on demand

I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this 😇
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.

React pass date as props : "undefined is not iterable"

I am a noob in React.
I am trying to get a variable that comes from another component.
this is in a js file for the MapLeaflet component
const date = props => {
return (
props.date);
};
And the first component (the one where the variable is date is created) is :
import React, { Component } from 'react';
import DateTimePicker from 'react-datetime-picker';
import MapLeaflets from './MapLeaflet';
class Picker extends Component {
state = {
date: new Date(),
}
onChange = date => this.setState({ date },
function(){
console.log("this works: " + this.state.date);
//const DateContext = React.createContext(this.state.date);
const DateContext =this.state.date
})
render() {
return (
<div>
<DateTimePicker
onChange={this.onChange}
value={this.state.date}
/>
{ console.log(this.state.date) }
<MapLeaflets date = {this.state.date}
/>
)}
</div>
);
}
}
export default Picker;
Here is my log error :
TypeError: undefined is not iterable (cannot read property
Symbol(Symbol.iterator))
I have extensively searched stackoverflow. This appears to be a pretty simple problem, yet it does not work here. I can read use child/parents, or context, but i did not manage to make it work. I did not try redux, but i guess this would be overkill to just pass a props.
Any observation or suggestion would be valuated.
EDIT
Thanks for your answer. Actually the error log comes from this line in the first component :
<MapLeaflets date = {this.state.date}
/>
Does anybody knows why it does not work ?
I am editing also to include a part of the mapleaflet component, just so you understand what i want to do with this date.
refreshStationsList() {
const { updateFavStationsList, readStoredFav } = this.props;
// console.log('refresh');
const date = (props) => {
return (
<div>{props.date}</div>
)
}
const request = `https:url`+date;
this.setState({ isLoading: true });
const favStationsId = readStoredFav();
axios.get(request)
.then(result => {
const stationsList = result.data.map(
station => {
const isFavorite = favStationsId.includes(station.number);
return { ...station, isFavorite: isFavorite }
}
);
this.setState({
stationsList: stationsList,
isLoading: false
})
updateFavStationsList(stationsList);
})
.catch(error => this.setState({
apiDataError: error,
isLoading: false
}));
}
render() { ....etc
Your MapLeaflets component should look like this
const MapLeaflets = (props) => {
return (
<div>{props.date}</div>
)
}
export default MapLeaflets;
You are trying to loop on undefined, first make sure that you are getting data from your api. and always try to do type check before proceeding.
You can use typeof to do type check

How to solve Cannot update during an existing state issue - react?

I have a function I calling it in render() method
and it's setState a Flag from the state.
So I got this error
Cannot update during an existing state transition (such as within
render).
I read about this error, and what I understand it's because I setState in render method and this is the wrong way.
So I'm forced to do it if u have any idea to handle this tell me.
The main idea about this function
I have an array of an object "Name as Tools" so in every object I have "id, name, count, price"
so that will render a three input in UI like this
and I have a boolean flag in-state "isEmpty" that checks every input in array before sending this data to the database.
Code
State = {
toolsUsed: [
{
id: 0,
name: '',
price: '',
count: '',
},
],
// Checker
isEmpty: false,
}
renderToolsUsed = () => {
const {toolsUsed} = this.state;
const tools = toolsUsed.map((item, i) => {
const {count, price, name, id} = item;
this.setState({
isEmpty: ['name', 'price', 'count'].every(key => item[key].length > 0),
});
return (
<View key={i} style={styles.tools}>
.... Inputs here ...
</View>
);
});
return tools;
};
JSX
render() {
return (
<View>
{this.renderToolsUsed()}
</View>
);
}
You can't update the state like this. It is like infinite loop. setState will trigger render, then render will trigger another setState, then you keep repeat the circle.
I don't know why you need isEmpty when you already have toolsUsed which you can use it to check if all input are empty.
Lets say if you insist to have isEmpty, then you can set it inside input change event.
The code is not tesed. I wrote the code directly from browser. But you can get the idea before the code.
renderToolsUsed = () => {
const { toolsUsed } = this.state;
const tools = toolsUsed.map((item, i) => {
return (
<View key={i} style={styles.tools}>
<TextInput value={item.name} onChangeText={(text) => {
this.setState({
toolsUsed: [
...toolsUsed.slice(0, i - 1),
{...item, name: text },
...toolsUSed.slice(i)
]
}, this.updateEmptyState)
}>
// other input here
</View>
);
});
// ...
};
updateEmptyState = () => {
this.setState({
isEmpty: this.state.toolsUsed.every(x => x.name === '' && x.price === '' && x.count === '')
})
}
The state is not designed to store all the data you have in the app
For what you need isEmpty inside the state?
To do this, use a global variable
Or check it out when you want it out of the render

Categories

Resources