How to render an object with keys? - javascript

This is example code from a user named FrankerZ:
class ExampleComponent extends React.Component {
onBlur = async () => {
const results = await axios.get('myhttpendpoint');
this.setState({
results
});
}
render() {
return (
<div>
<form>
<span className="name"> Search Term: </span>
<input id="search-term" value={this.state.value} onBlur={this.onBlur} />
</form>
<div id="results">
{this.state.results}
</div>
</div>)
}
}
But essentially, my question is what if my axios.get returned an object with keys like
[{name: test1, data: datadatadata}, {name: test2, data: datatatatatata}]
How would I render each object in it's own span or own div?
I tried using a map such as
this.setState(results.map((item, index) => (<li key = {index}>{item.name}</li>)));
but it doesn't seem to work. I did this because it seems that React can't render object with keys and it told me to use an array instead which is what I tried.

You should do the map in the render method or any other method and call it in render, but not in set state.
Something like this
class ExampleComponent extends React.Component {
onBlur = async () => {
const results = await axios.get('myhttpendpoint');
this.setState({
results
});
}
render() {
return (
<div>
<form>
<span className="name"> Search Term: </span>
<input id="search-term" value={this.state.value} onBlur={this.onBlur} />
</form>
<div id="results">
{this.state.results.map(item => (<li key={item.name}>{item.name}</li>))}
</div>
</div>)
}
}

Markup should not go inside state.
Your render() should be like this.
<div id="results">
<ul>
{
this.state.results.map((item, index) => <li key = {index}>{item.name}</li>)
}
</ul>
</div>
make sure you initialize the state like this in constructor.
this.state = {
results : []
};

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
frank: [] // Basically, you will have a place in the state where you will save your data, which is empty.
}
componentDidMount(){
// This lifecycle method is used to fire requests.
// Fire your request.
// get the response.
// save the data only in the state, don't save ELEMENTS such like li.
const _result = [
{ id: 1, name: 'Frank1' },
{ id: 2, name: 'Frank2' }
];
this.setState({
frank: _result
});
}
render() {
const { frank } = this.state;
return (
<div>
<form>
<span className="name"> Search Term: </span>
<input id="search-term" value={this.state.value} onBlur={this.onBlur} />
</form>
<div id="results">
{/*HERE DO MAPPING*/}
{
frank.map((item, index) => <li key={index}>{item.name}</li>)
}
</div>
</div>)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Follow the comments up there.
live demo: https://codesandbox.io/s/kxv6myl8wo

Related

reactjs - adding friends to InnerCircle list

I have a list of friendsboxes with each friendsbox a button that should add the friends assign to the InnerCircle list. In the friendsDetail component the addToInnerCircle should add the friend's details to the InnerCircle` list.
My first guess would be to add a InnerCircle array as part of my user model and then make this function addToInnerCircle add a inner circle member id on click of the button "add to inner circle". Afterwards I would do a ComponentDidMount in Friends.js and pull the data (+ store in state of Friends.js under InnerCircle) for every element of the InnerCircle Array for the logged in user and via Populate get to all the data for that specific person.
Does that make sense or is there a better approach in doing this?
Friends.js
import React from 'react'
import DefaultLayout from "../layout/Default"
import './Friends.css'
import Axios from 'axios'
import Frienddetail from '../components/Frienddetail'
import InnerCircleDetail from '../components/InnerCircleDetail'
import { getUser } from '../utils/auth'
class Friendsfollowers extends React.Component {
constructor() {
super()
this.state = {
friends: [],
searchFriends: [],
innerCircle: [],
searchInnerCircle: []
}
this.searchFriends=this.searchFriends.bind(this)
}
componentDidMount(){
Axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/friends`,
withCredentials: true
})
.then(response =>{
console.log(response)
let friendslist = response.data // eslint-disable-next-line
let friendslistupdate = friendslist.filter(friend => {
if(friend.username){
if(friend.username !== getUser().username){
return true
}
}
})
this.setState({
friends:friendslistupdate,
searchFriends: friendslistupdate
})
})
.catch(error =>{
console.log("Charles made an error when retrieving all friends: ",error)
})
}
render() {
return (
<DefaultLayout>
<div className="friendsoverviewcontainer">
<h1>Our community</h1>
<form className="friends">
<div className="titlepart">
<label className="friendlabel" htmlFor="friend">Search for Users :</label><br></br>
<input className="friendform" type="text" name="friend" value={this.state.friend} placeholder="Type a username here!" onChange={this.searchFriends}></input>
</div>
</form>
<div className="friendsboxes" >
{
this.state.searchFriends.map(friend =>
<div key={friend._id}>
<Frienddetail
key={friend._id}
id={friend._id}
username={friend.username}
location={friend.location}
/>
</div>
)
}
</div>
</div>
<div className="innercirclecontainer">
<h1>Your inner circle</h1>
<div className="innercircleboxes">
{
this.state.searchInnerCircle.map(inner =>
<div key={inner._id}>
<InnerCircleDetail
key={inner._id}
id={inner._id}
username={inner.username}
location={inner.location}
/>
</div>
)
}
</div>
</div>
Frienddetail.js
import React from 'react'
import './Frienddetail.css'
class InnerCircleDetail extends React.Component {
constructor() {
super()
this.state = {
}
}
render() {
return (
<div className="friendbox">
<img className="imagedaredevilspicdetail" src="/images/profileimage.png" alt="picturesetting" />
<p className="friend">{this.props.username}</p>
<p className="friend">{this.props.location}</p>
</div>
)
}
}
export default InnerCircleDetail
So you wouldn't need to do anything with componentDidMount since updating state with added ids in innerCircle wouldn't trigger a remount, but rather componentDidUpdate. To render the innerCircle you would just render a filtered version of friends where id must match an id within innerCircle.
Check out this working version:
https://codesandbox.io/s/wonderful-http-gin2j?file=/src/ICDetail.js
{friends
.filter(friend => innerCircle.some(id => id === friend.id))
.map((friend, index) => {
return (
<ICDetail
key={friend.id}
friend={friend}
removeFromIC={() => this.removeFromIC(index)}
/>
);
})
}

How to create an object from another class, and push it into the state array?

I'm currently working on a React application, where I two classes - let's call them class App and class Container. Basically, class App has a state array, and I want to have many Container objects in this array.
class Container extends React.Component{
render(){
return(
<img src= {this.props.url} />
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
url: ""
data: []
}
}
handleSubmit(event){
event.preventDefault();
//I WANT TO BE ABLE TO MAKE A NEW CONTAINER, AND PASS THE URL AS PROPS.
// THEN, I WANT TO ADD THAT CONTAINER TO THE ARRAY.
this.setState({
data: url = this.state.url, id = 'a'
});
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label htmlFor="url">url:</label>
<input
type = "text"
name = "url"
value = {this.state.url}
onChange = {this.handleChange}
/>
</form>
)
}
}
In the function handleSubmit() above, I want to add a new container containing the props URL to the array. How would I do this?
don't mutate the state
you just need url in the state, not the whole container
use setState to modify the state
consider using spread operator (...) for concatenation
I don't see handleChange in your code
class Container extends React.Component {
render() {
return <img src={this.props.url} />;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
url: "",
containers: []
};
}
handleChange = e => {
this.setState({
url: e.target.value
});
};
handleSubmit = event => {
event.preventDefault();
if (this.state.url) {
this.setState({
containers: [...this.state.containers, this.state.url],
url: ""
});
}
};
render() {
const { url, containers } = this.state;
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor="url">url:</label>
<input
type="text"
name="url"
value={url}
onChange={this.handleChange}
/>
<button>submit</button>
</form>
<h2>Containers:</h2>
<div>
{!containers.length && <i>no urls added</i>}
{containers.map((_url, i) => (
<Container key={i} url={_url} />
))}
</div>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Working Example:
https://stackblitz.com/edit/react-g72uej

how to add new input field after click plus icon in React Js

I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.

React Todo Checkbox Styles all listed items at once

I'm trying to create a todo-list with React. I am able to display the list in the display area and also able to remove the items. But when I click on one checkbox, all the checkboxes are selected and the class is applied to all the list items. I'm not sure what is it that I am doing wrong.
I tried to use the same logic as I did with the deleted item(that's using the filter), but it doesn't work. I looked other cases here but they are mostly about how to do it with jQuery.
Here is the working example of my problem.
This is the List class
class List extends Component {
state={
check: false,
strike: 'none'
}
onCheck(item){
this.setState({check: !this.state.check})
if (this.state.strike === 'none'){
this.setState({strike: 'line-through'})
} else {
this.setState({strike: 'none'})
}
}
render() {
const strike = {
textDecoration: this.state.strike,
}
return (
<ul className='list-style'>
{ this.props.items.map((item, index) =>
<li key={index}>
<div className="outer-div">
<div className="item-checkbox">
<input type="checkbox" checked={this.state.check}
onChange={() => this.onCheck(item)} />
</div>
<div className="item-text">
<span style= {strike}> {item} </span>
</div>
<div className="item-remove-div">
<button className="item-remove" onClick={() => this.props.onDeleteList(index)}>
Remove
</button>
</div>
</div>
<br />
</li>
)}
</ul>
)}
}
export default List;
And this is the Main Class:
class Main extends Component {
state = {
items: [],
term : "",
}
onChange(event){
this.setState({ term: event });
}
onDelete= (item) =>{
// this.setState ({
// items: this.state.items.filter((i) => i.index !== item.index)
// })
this.state.items.splice(item, 1);
this.setState({items: this.state.items});
}
onSubmit= (event) => {
event.preventDefault();
if (this.state.term.length > 0){
this.setState({
term: '',
items: [...this.state.items, this.state.term]
});
}
}
render() {
return (
<div className="center">
<h1 className="header" > TODO-LIST </h1>
<div className='mainCenter'>
<form className="App" onSubmit={this.onSubmit}>
<input placeholder="add task" value={this.state.term} onChange={(e) => this.onChange(e.target.value)}
className="inputField"/>
<button>Add to the List</button>
</form>
<List items={this.state.items} onDeleteList={this.onDelete}/>
<div className="footer-outer">
<span className="footer"> Number of completed items in an array: {this.state.items.length} </span>
</div>
</div>
</div>
);
}
}
I edited your SlackBlitz. Now you can properly add new todos, check individuals tasks (toggle checked on todo click) and see correct checked counter in the footer.
Check todo-list-react demo.
import React, { Component } from 'react';
import TodoList from './List';
import './style.css';
class Main extends Component {
constructor() {
super();
this.state = {
items: [],
term: ''
};
}
handleChange = event => {
this.setState({ term: event.target.value });
}
handleItemClick = ({ value, checked }) => {
this.setState({
items: this.state.items.map(item => item.value === value ? { value, checked: !checked } : item)
});
}
onSubmit = event => {
event.preventDefault();
if (this.state.term.length > 0) {
this.setState({
term: '',
items: [...this.state.items, { value: this.state.term, checked: false }]
});
}
}
handleDelete = index => {
console.info('todo: remove todo at index', index);
// deletion logic... keep in mind that using index as key properties on jsx could breaks the correct functioning of this component.
}
render() {
return (
<div className="center">
<h1 className="header" > TODO-LIST </h1>
<div className='mainCenter'>
<form className="App" onSubmit={this.onSubmit}>
<input placeholder="add task" value={this.state.term} onChange={this.handleChange}
className="inputField"/>
<button>Add to the List</button>
</form>
<TodoList
onTodoClick={this.handleItemClick}
onDelete={this.handleDelete}
todos={this.state.items}
/>
<div className="footer-outer">
<span className="footer">
Number of completed items in an array:
{this.state.items.filter(item => item.checked).length}
</span>
</div>
</div>
</div>
);
}
}
export default Main
import React, { Component } from 'react';
import './style.css';
class List extends Component {
render() {
const { todos, onTodoClick, onDelete } = this.props;
return (
<ul className='list-style'>
{
todos.map((item, index) =>
<li key={index}>
<div className="outer-div">
<div className="item-checkbox">
<input type="checkbox" checked={item.checked}
onChange={() => onTodoClick(item)} />
</div>
<div className="item-text">
<span style={checkboxStyle(item.checked)}>{item.value}</span>
</div>
<div className="item-remove-div">
<button className="item-remove"
onClick={() => onDelete(index)}>
Remove
</button>
</div>
</div>
<br />
</li>
)}
</ul>
)}
}
function checkboxStyle(checked) {
return {
textDecoration: checked? 'line-through' : 'none',
};
}
export default List;
In addition to this answer, I recommend you to consider to add an unique key property to each jsx-element differen from the array index. Current implementation has no problem, but once you start deleting todo items probably display wrong data.
Read List and Keys from React docs and this article on Medium which covers possible error when using indixes as keys.
The reason that all your list items are being 'striked' is because you have only one state reserved for all the items in the list. You need to have the checked or strike state for each item in the list. However, as I view your comments, I realize that you already know that.
You have several other inconsistencies in the code:
onDelete= (item) =>{
this.state.items.splice(item, 1);
this.setState({items: this.state.items});
}
Making direct changes to the state like that might cause unwanted errors and unusual behavior. A better way to do it is to:
onDelete = (item) => {
const items = this.state.items.slice();
items.splice(item, 1);
this.setState({
items: items,
});
}
For more info refer to this article:
https://medium.com/pro-react/a-brief-talk-about-immutability-and-react-s-helpers-70919ab8ae7c

How to loop and render different components from array in ReactJs?

What implementation is needed to render different components from render method. As you can see below the idea is that Survey component receives an array which contains different components names (could be Input, CheckList, Dropdown, File). The array passes as property to Survey Component is generated properly depending of what button is clicked, but at the time to render different components is not working. I'm using JsComplete to test it.
const Dropdown = () =>{
return(
<div>
<select>
<option value="initial" selected>Select...</option>
<option value="Option ">Option 1</option>
<option value="Option ">Option 2</option>
</select>
</div>
)
}
const Checklist = () =>{
return(
<div>
<h4>Question name</h4>
<label>
Option 1:
<input
name="pl"
type="checkbox" />
</label>
<label>
Option 2:
<input
name="tz"
type="checkbox" />
</label>
</div>
)
}
const Input = () =>{
return(
<div>
<label>
Question name:
<input
name="input"
type="text" />
</label>
</div>
)
}
const File = () =>{
return(
<div>
<label>
Upload:
<input
name="file"
type="file" />
</label>
</div>
)
}
class Survey extends React.Component {
constructor(props){
super(props);
}
render(){
var ChildName ;
for (var i = 0; i < this.props.components.length; i++) {
log("Log:" + this.props.components[i]);
ChildName = this.props.components[i];
return <ChildName />;
}
return (
false
)
}
}
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit("Input")} name="Input">Input</button>
<button onClick={()=>this.handleSubmit("Checklist")} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit("Dropdown")} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit("File")} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
components: prevState.components.concat(element)
}));
};
render() {
return (
<div>
<Form onSubmit={this.addNewElement} />
<Survey components={this.state.components} />
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
Try this. Dont pass string in handleSubmit method. Instead pass component itself like this:
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit(Input)} name="Input">Input</button>
<button onClick={()=>this.handleSubmit(Checklist)} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit(Dropdown)} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit(File)} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
Also in you survey component return the elements like this
class Survey extends React.Component {
constructor(props) {
super(props);
}
render() {
if (this.props.components.length === 0) {
return null;
}
const renderCommpos = this.props.components.map((Elem, index) => {
return <Elem key={index} />
});
return (
<div>
{renderCommpos}
</div>
);
}
}
Also notice the Elem in map function. When it comes to react component jsx needs the first letter capital. So doesn't matter what variable you keep at place of Elem, you should always keep the first letter capital.
The render method for your survey component should be like this :
render(){
const { components } = this.props;
return (
<div>
{
components.map((c, index) => {
return (
<div key={`one-of-components-${index}`}>
{c}
</div>
);
})
}
</div>
);
}
Now it will return all the components in the props.
Try this out.
const Survey = ({ components }) => {
const Components = components.map(
( component, index ) => {
return (
<div key={ index }>
{ component }
</div>
);
}
);
return (
<div>
{ Components }
</div>
);
};
In your for loop you're returning from the function on the first component. Add them to an array and then return the array. On another note, I used a functional stateless component here. I don't see the need for the class overhead.

Categories