How to use map function for hooks useState properties - reactjs

I tried to use map function for looping my list data in react-hooks useState but I stuck with an error that "TypeError: Cannot read property 'map' of undefined"
//1.Initial declaration
const App = props=> {
const [state, changeState]= useState ({
name:"",
eventTitle:"",
details:"",
objdata:{},
list:[],
toggleIndex:"",
editName: "",
editEventTitle: "",
editDetails: "",
editObj: {}
});
//2.logic comes here
//3.tried using map
{(state.list.map((data,id)=>{
console.log ('loop data',data)
}))}

As we suspected you are not setting your state in the right way. I tried to explain in my comment, with hooks when you set your state it does not merge the updated properties with the current one. So, you should think about that. Right now you are setting your state like that:
const handleName = name => {
changeState({
name: name.target.value
});
};
Here, you are setting the name property and lose other parts of your state. Hence, when you set your state, you lose list as well as other parts of your state. This is how you should do it:
const handleName = name => {
const { target } = name;
changeState(state => ({
...state,
name: target.value,
}));
};
You take the old state, keep the other properties by spreading it, then update the relevant part. I would use here event instead of name. It is not "name", it is "event" after all actually :)
const handleName = event => {
const { target } = event;
changeState(state => ({
...state,
name: target.value,
}));
};
Also, you have a few other problems and unnecessary parts in your code. For example, you are struggling too much to handle the submit and add an object to your list. You don't need an extra objdata in your state to push it to the list. If you want to construct an extra object, you can do it in the function itself.
Here is a very simple way to do it:
const submitHandle = () => {
const { name, eventTitle, details } = state;
const obj = { name, eventTitle, details };
changeState(state => ({
...state,
list: [ ...state.list, obj ],
}))
};
Again, we are using spread operator to keep both the other parts of the state and while updating the list, to keep other objects. Do not set your state as you do in your submitHandle function. Try to think it simple :)
Also, you don't need to bind your functions when it is not necessary. You can find a working copy of the code below. I just removed unnecessary parts and fix the issues.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = props => {
const [state, changeState] = useState({
name: "",
eventTitle: "",
details: "",
list: [],
toggleIndex: "",
editName: "",
editEventTitle: "",
editDetails: "",
editObj: {}
});
const handleName = event => {
const { target } = event;
changeState(state => ({
...state,
name: target.value
}));
};
const handleEventTitle = event => {
const { target } = event;
changeState(state => ({
...state,
eventTitle: target.value
}));
};
const handleDetails = event => {
const { target } = event;
changeState(state => ({
...state,
details: target.value
}));
};
const submitHandle = () => {
const { name, eventTitle, details } = state;
const obj = { name, eventTitle, details };
changeState(state => ({
...state,
list: [...state.list, obj]
}));
};
const resetHandle = () =>
changeState(state => ({
...state,
name: "",
eventTitle: "",
details: ""
}));
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container">
<h1 className="display-5 text-center">Let's set your reminders</h1>
</div>
</div>
<div className="bg-dark container-fluid">
<div className="row">
<div className="col-sm-12 col-md-4 col-lg-4 " />
<div className="col-sm-12 col-md-4 col-lg-4 ">
<div className="card login-card ">
<div className=" card-header ">
<h3 className="text-center"> TO-DO LIST FORM</h3>
</div>
<div className="card-body">
<form className="form-elements">
<input
value={state.name}
className="form-control form-inputs form-elements"
type="text"
onChange={handleName}
placeholder="user name"
/>
<input
value={state.eventTitle}
className="form-control form-inputs form-elements"
type="text"
onChange={handleEventTitle}
placeholder="Event Title"
/>
<input
value={state.details}
className="form-control form-inputs form-elements"
type="text"
onChange={handleDetails}
placeholder="Details "
/>
</form>
</div>
<div className="card-footer ">
<button
type="submit"
onClick={submitHandle}
className="btn-primary offset-lg-1 offset-md-0 btn-sm "
>
Create
</button>
<button
type="reset"
onClick={resetHandle}
className="btn-primary offset-lg-5 offset-md-0 btn-sm"
>
cancel
</button>
</div>
</div>
</div>
<div className="col-sm-12 col-md-4 col-lg-4 " />
</div>
<div className="container-fluid bg-dark">
<div className="row ">
{state.list.map(data => (
<div style={{ border: "1px black solid" }}>
<p>{data.name}</p>
<p>{data.eventTitle}</p>
<p>{data.details}</p>
</div>
))}
</div>
</div>
</div>
<div
className="footer footer-copyright"
style={{ background: "#e9ecef" }}
>
<div className="container">
<h6 className=" text-center">Just make it work ;)</h6>
</div>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));

if your list is empty you will get this error just check if list is empty or not to solve the problem:
{if(state.list){
state.list.map((data,id)=>{
console.log ('loop data',data)
})
}}

Related

Incorrect state when onClick fired

I'm using AlephJS for the first time, and I think I'm running into a closure issue with my code, but for the life of me I can't figure it out. As you can see I have a modal and a page, onClick I'm dispatching a function, but it's using the inital state, instead of the values updated in the modal. I could probably use useRef(), but, I shouldn't have to? Any info or pointers in the right direction would be much appreciated. Code:
import React, {
useEffect,
useState
} from 'react';
import "./main.css";
import { useGames } from "~/lib/gamesContext.tsx";
// import M from "materialize-css";
export default () => {
const { dispatch } = useGames();
const [modalOpen, setModalOpen] = useState(false);
const [newGame, setNewGame] = useState({
players: [],
type: ""
});
const handleContentLoaded = () => {
const selects = document.querySelectorAll('select');
//#ts-ignore
M.FormSelect.init(selects, {});
const chips = document.querySelectorAll('.chips');
//#ts-ignore
M.Chips.init(chips, {
placeholder: 'Enter a Player',
secondaryPlaceholder: '+Player',
onChipAdd: (element: Element, chip: Element) => updatePlayers(element, chip),
onChipDelete: (element: Element, chip: Element) => updatePlayers(element, chip)
});
};
const updatePlayers = (element: Element, chip: Element) => {
//#ts-ignore
const chipData = element[0].M_Chips.chipsData.map(item => item.tag);
setNewGame(newGame => ({
...newGame,
players: chipData
}));
}
const resetState = () => setNewGame({
players: [],
type: ""
});
useEffect(() => {
handleContentLoaded();
}, []);
console.log("New Game", newGame);
return (
<>
<div className={modalOpen ? "custom-modal" : "custom-modal closed"}>
<div className="card modal-start-container">
<div className="card-content">
<span className="card-title">New Game</span>
<div className="input-field col s12">
<select value={newGame.type} onChange={(event) => {
setNewGame(newGame => ({
...newGame,
type: event.target.value
}))
}}>
<option value="" disabled>Choose a game</option>
<option value="rummy">Rummy</option>
<option value="upAndDown">Up and Down</option>
</select>
<label>Game Type</label>
</div>
<div className="chips chips-placeholder"></div>
</div>
<div className="card-action">
<a
onClick={() => {
console.log("on click: ", newGame);
dispatch({
type: "addGame",
payload: {
...newGame,
index: `${newGame.type}${Date.now()}`
}
});
setModalOpen(false);
resetState();
}}
className="waves-effect waves-green btn-flat">Start</a>
</div>
</div>
</div>
<div className={`container ${modalOpen ? "blur" : ""}`}>
<blockquote>
<h4>"Weird how the scorekeeper always wins..."</h4>
<p>- every sore loser everywhere</p>
</blockquote>
<div className="flex-col">
<p className="flow-text">
Today it's easier to find a tablet or phone, than pen and paper. So
we've created an app that will let you keep score of all your
favorite card games, from your favorite digital device.
</p>
<a className="waves-effect waves-light btn" onClick={() => setModalOpen(true)}>
New Game
</a>
</div>
</div>
</>
);
}
Link to code in the TypeScript playground

React array state is not Iterable

I am trying to figure out how to pass an item thru the state on the item: [] inside the list state. Whenever I tried this code, an error shows up as lists is not iterable whenever I insert or add item to the array
Is there a way to insert data to the array property of the state? And adding more string arrays in that property?
const [lists, setLists] = useState({
item: [],
});
const addList = () => {
const listItem = document.getElementById("listItem");
if (listItem.value !== "") {
setLists([
...lists,
{
item: listItem.value,
},
]); // >>> [INSERT TO THE ARRAY PROPERTY]
listItem.value = "";
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
id="listItem"
name="item"
onKeyPress={(e) => (e.key === "Enter" ? addList() : null)}
/>
<button
type="button"
onClick={() => {
addList();
}}
>
Add
</button>
<ul>
LIST
{lists.item.map((val, index) => {
return (
<li key={index}>
<p>{val}</p>
<button type="button" onClick={() => removeList(index)}>
Remove
</button>
</li>
);
})}
</ul>
<button type="submit">submit</button>
</form>
</div>
);
You seem to be having some confusion regarding your data types. lists is an array of objects of the shape {item: ...}.
The useState call should be useState([]).
You'll need lists.map(({item}, index) => (or lists.map(val and val.item) to get at the ....
You can use e.g. console.log(lists), or a debugger, to see what's really happening.)
You shouldn't use document.getElementById() with React, ever. Instead, make the input controlled (or have a ref to it and read the value if you want uncontrolled, but you likely don't).
The setLists call should be the functional form: setLists(lists => [...lists, {item: listItem.value}]).
All in all, something like
function Component() {
const [newItemText, setNewItemText] = React.useState("");
const [todoList, setTodoList] = React.useState([]);
const addList = (event) => {
event.preventDefault();
if (newItemText !== "") {
setTodoList(todoList => [
...todoList,
{
item: newItemText,
},
]);
setNewItemText("");
}
};
return (
<div>
<form onSubmit={addList}>
<input
type="text"
name="item"
value={newItemText}
onChange={e => setNewItemText(e.target.value)}
/>
<button
type="submit"
>
Add
</button>
</form>
<ul>
LIST
{todoList.map(({item}, index) => {
return (
<li key={index}>
<p>{item}</p>
<button type="button">
Remove
</button>
</li>
);
})}
</ul>
</div>
);
}
ReactDOM.render(<Component />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root">
const [lists, setLists] = useState({
item: [],
});
In the code above you set initial value as an Object not an Array. Try to change code like below.
const [lists, setLists] = useState([]);

having issues fetching google map

googlemapapiI'm having issues fetching google map, it says the page can't load correctly, I also have some errors on my console. I don't understand what I'm doing wrong, I should be able to make a query and have the places showing in the suggestions, but I'm doing something wrong. here is my component, I have also attached a photo. All help will be welcome [
import React, { Component } from "react";
import { Map, Marker, GoogleApiWrapper } from "google-maps-react";
const apiKey = process.env.REACT_APP_GOOGLE_API_KEY;
const center = {
lat: 51.5074,
lng: 0.1278,
};
let service = null;
export class MapContainer extends Component {
constructor(props) {
super(props);
this.state = {
input: "",
suggestions: [],
places: [],
};
}
savePlace = (place) => {
this.setState({ places: [...this.state.places, place] });
};
handleChange = (e) => {
this.setState({ input: e.target.value });
};
handleKeyPress = (event) => {
if (event.key === "Enter") {
this.search();
}
};
search = () => {
const {input} = this.state;
service.textSearch({query: input}, (suggestions) => {
this.setState({suggestions});
})
};
initPlaces(mapProps, map) {
const { google } = mapProps;
service = new google.maps.places.PlacesService(map);
}
render() {
const { suggestions, places } = this.state;
return (
<div className="container">
<div className="row">
<div className="col">
<div className="form-inline d-flex justify-content-between mb-4">
<input
type="text"
value={this.state.input}
onChange={this.handleChange}
className="form-control flex-grow-1"
placeholder="Search for places on Google Maps"
onKeyPress={this.handleKeyPress}
/>
<button onClick={this.search} className="btn btn-primary ml-2">
Search
</button>
</div>
<h3>Suggestions</h3>
<ul className="list-group">
{suggestions.map((place, i) => (
<li
key={i}
className="list-group-item d-flex justify-content-between align-items-center"
>
<div>
<div>
<strong>{place.name}</strong>
</div>
<span className="text-muted">
{place.formatted_address}
</span>
</div>
<button
className="btn btn-outline-primary"
onClick={() => this.savePlace(place)}
>
Show
</button>
</li>
))}
</ul>
</div>
<div className="col">
<Map google={this.props.google} zoom={14} initialCenter={center} onReady={this.initPlaces}></Map>
</div>
</div>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey,
})(MapContainer);
]2
I checked your code and if you directly put your API key in your
const apiKey = "PUT_YOUR_API_KEY_HERE"; , it will properly show your map.
It seems that you are putting your variables in the .env file (refer here on how to add custom environment variables). Make sure that you put your .env file outside the src folder and set this inside your .env file :
REACT_APP_GOOGLE_API_KEY=API_KEY_VALUE_HERE. This works for me.
You can find the sample code in this link.
Make sure to change the value of the REACT_APP_GOOGLE_API_KEY in the .env file to your API key.
Hope this helps!

Replace String in content

Take a look on my Chat.js contents:
import React, { Component } from "react";
class Chat extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
message: ""
};
}
submitMessage(event) {
event.preventDefault();
this.setState(state => ({
messages: [<li>{this.state.message}</li>, ...state.messages]
}));
this.setState({
message: ""
});
}
render() {
return (
<>
<div class="container py-3">
<h2 className="text-center mb-4">Simple Chat</h2>
<form
onSubmit={e => {
this.submitMessage(e);
}}
>
<input
type="text"
className="form-control"
placeholder={"Enter your message..."}
value={this.state.message}
onChange={e => this.setState({ message: e.target.value })}
/>
<button type="submit" className="btn btn-success mt-2">
Send Message
</button>
</form>
<div className="container border mt-2">
<ul className="group-list mt-3 pt-2">{this.state.messages}</ul>
</div>
</div>
</>
);
}
}
export default Parent;
Also, this is my example:
https://codesandbox.io/s/objective-water-1e8uq
i need to replace user link when anyone type username with #
for example, this is my message content:
Are you good #Daniel ?
I need to convert above message to following content:
Are you good #Daniel ?
I use react-router-dom, so i need to replace link with this code:
Eg:
<Router>
<Link to={ '#'} onClick={() => {this.example()}}>
#Daniel
</Link>
</Router>;
It should be done with a string replace:
// This is to have as tag "Are you good #Daniel ?" (with # in the username)
const replacementReg = /(#[a-zA-Z0-9]+)/g;
// This is to have as tag "Are you good Daniel ?" (without # in the username)
const replacementReg = /#([a-zA-Z0-9]+)/g;
function decorateWithLink(text) {
text.replace(replacementReg, replaced => ` ${replaced} `);
}
and you should use in your component:
this.setState(state => ({
messages: [
<li>{decorateWithLink(this.state.message)}</li>,
...state.messages
],
message: '' // with this you don't need to call again the this.setState
}));
I have updated your submitMessage function here is the code:
submitMessage(event) {
event.preventDefault();
const { message } = this.state;
let msg = message.replace(/#([a-zA-Z0-9]+)/g, value => ` ${value} `);
this.setState(state => ({
messages: [<li dangerouslySetInnerHTML={{__html: msg}} />, ...state.messages]
}));
this.setState({
message: ""
});
}
here is the working code: https://codesandbox.io/s/stupefied-platform-zmg94

React app not showing in Codepen no matter what?

I have a react app that I made in VS Studio, putting it into codepen, it doesnt seem to load a thing, any suggestions?
I have tried making sure React is linked and checked all of my syntax, no errors on local host but no display in codepen.
I have looked through the code multiple times and I feel its such a silly mistake
https://codepen.io/donnieberry97/pen/EzmOvW
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
editing: false,
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
if (this.state.userInput === "") { (alert("Please enter a To-do")); return; };
const { list, userInput } = this.state;
this.setState({
list: [...list, {
text: userInput, key: Date.now(), done: false
}],
userInput: ''
})
}
handleChecked(e, index) {
console.log(e.target.checked);
const list = [...this.state.list];
list[index] = { ...list[index] };
list[index].done = e.target.checked;
this.setState({
list
})
}
handleEditing(e) {
this.setState({
editing: true
})
}
handleRemoved(index) {
const list = [...this.state.list];
list.splice(index, 1);
this.setState({
list
})
}
render() {
var viewStyle = {};
var editStyle = {};
if (this.state.editing) {
viewStyle.display = "none"
}
else {
editStyle.display = "none"
}
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<div class="submitButton">
<button onClick={() => { this.addToList(this.state.userInput) }}>Add todo</button>
</div>
{this.state.list.map((list, index) => (
<div className="form">
<ul>
{/* <div style={viewStyle} onDoubleClick={this.handleEditing.bind(t his)}> */}
<li key={list.key}>
<div class="liFlexCheck">
<input type="checkbox" onChange={(e) => this.handleChecked(e, index)} />
</div>
<div class="liFlexText">
<div class="liFlexTextContainer">
<span style={{ textDecoration: list.done ? 'line-through' : 'inherit' }}>
{list.text}
</span>
</div>
</div>
<button onClick={(index) => this.handleRemoved(index)}>Remove</button>
<input
type="text"
style={editStyle}
value={list.text}
/>
</li>
{/* </div> */}
</ul>
</div>
))}
</div>
);
}
}
Remove the import statements, working example.
You shouldn't use import when you got External Scripts.
Also, you got many errors in your code that should be handled, like:
<div class="submitButton">, use className.
Each child in a list should have a unique key prop.
Form field with value prop but without onChange handler.
Check out the logs:
In codpen, you don't need to import the react instead just write code,
here is codepen working one : codepen
from codesandbox, you can learn with all imports also because it doesn't uses any external scripts,
your code will work fine if you add an import to it
that is import ReactDOM from 'react-dom';
codesandbox will show all these suggestions,
here is codesandbox working example: codesandbox

Resources