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)}
/>
);
})
}
Related
I'm implementing a project where
I have a array of 44 object data
When I type a it returns 37 data immediately by onChange()
After type ad it return 20
The Problem is when I return back to a by backspace. It stay on 20.
How can I get back 37 data again.
Code of Root.jsx
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
}
getBadge = (e) => {
console.log(e)
const searched = this.state.data.filter(
item => {
if (e === '') {
return item
} else if (item.title.toLowerCase().includes(e.toLowerCase())) {
console.log(item)
return item
}
}
)
this.setState({ data:searched })
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search getBadge={this.getBadge} />
</>
<div className='container'>
<IconCard data={data} />
</div>
</>
)
}
}
export default Root
state data be like
state={
data:data
}
data
{
"title": "Academia",
"hex": "41454A"
},
{
"title": "Academia",
"hex": "41454A"
}
Code of Search.jsx
import React, { Component } from 'react';
class Search extends Component {
handleChange = (e) => {
this.props.getBadge(e.target.value)
}
render() {
// console.log(this.state.search)
return (
<div className='container pb-3'>
<div className="row">
<div className="col-md-3 align-self-center ">
<input type="text" className="form-control" placeholder="Search by brand..." onChange={this.handleChange} />
</div>
</div>
</div>
)
}
}
export default Search;
I understood your problem. You are mutating the original data whenever the search text is changing. Actually, you should not do that.
Instead,
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
searchText: '',
}
getBadge = (search) => {
console.log(search)
return this.state.data.filter(
item => {
if (item.title.toLowerCase().includes(search.toLowerCase())) {
console.log(item)
return true;
}
return false;
}
)
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search
value={this.state.searchText}
onChange={(value) => this.setState({searchText: value})} />
</>
<div className='container'>
<IconCard data={this.getBatchData(this.state.searchText)} />
</div>
</>
)
}
}
export default Root
Set searchText state in the component
Change the props of the <Search /> component
Update the state when the search updates
Update the getBatchData() as per above code.
Everytime you update the search text, the data will remains same, but the filter will return the results according to search text
In your function getBadge :
const searched = this.state.data.filter(...)
this.setState({ data:searched })
You are replacing the state with the object you found. So if the data object had 44 elements, after a search it will only have the filtered elements. All the other elements are gone.
You should consider filtering from a constant object instead of state.data
I a learning react and stuck at this place. I am creating an app In which user will see a list of product with different id and name. I have created another component in which the detail of the product will open . I am collection the id and value of the particular product in my addList component by onClick function. And now i want to send those value in DetailList component so that i can show the detail of that particular product.
A roadmap like
Add list -> (user click on a product) -> id and name of the product passes to the DetailList component -> Detail list component open by fetching the product detail.
Here is my code of Add list component
export default class Addlist extends Component {
constructor(props) {
super(props)
this.state = {
posts : []
}
}
passToDetailList(id) {
console.log( id)
}
async componentDidMount() {
axios.get('http://localhost:80/get_add_list.php')
.then(response => {
console.log(response);
this.setState({posts: response.data})
})
.catch(error => {
console.log(error);
})
}
render() {
const { posts } = this.state;
// JSON.parse(posts)
return (
<Fragment>
<div className="container" id="listOfAdd">
<ul className="addUl">
{
posts.map(post => {
return (
<li key={post.id}>
<div className="row">
<div className="col-md-4">
<img src={trialImage} alt=""></img>
</div> {/* min col end */}
<div className="col-md-8">
<b><h2>{post.add_title}</h2></b>
{/* This button is clicked by user to view detail of that particular product */}
<button onClick={() => this.passToDetailList(post.id)}>VIEW</button>
</div> {/* min col end */}
</div> {/* row end */}
</li>
);
})}
</ul>
</div>{/* container end */}
</Fragment>
)
}
}
You should pass the data through the routes -
<Route path="/details/:id" component={DetailList} /> // router config
passToDetailList(id) {
this.props.history.push('/details/'+id)
}
and then in the DetailList Component, you can access the value through -
console.log(this.props.match.params.id) - //here is the passed product Id
You need to elevate the state for id to a common parent between AddList and DetailList and then create a function in parent component to set the id and pass the id and setId function to your AddList Component through props , then just use setId function to set the id state in passToDetailList function.
finally you can use the id in your DetailList Component to fetch its details
so Here is how your AddList Component would look like:
export default class Addlist extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
passToDetailList(id) {
this.props.setId(id);
}
// The rest of your code
}
and here is how your DetailList Component will look like:
export default class DetailList extends Component {
componentDidMount(){
// Use the id to fetch its details
console.log(this.props.id)
}
}
and finally here is your CommonParent Component:
export default class CommonParent extends Component {
constructor(props) {
super(props);
this.state = {
id: ''
};
this.setId = this.setId.bind(this);
}
setId(id){
this.setState({
id
})
}
render(){
return(
<>
<AddList setId={this.setId} />
<DetailList id={this.state.id} />
</>
)
}
}
if your Components are very far from each other in component tree you can use react context or redux for handling id state
I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};
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
I have a search bar that is in its own component (so that it is in the navbar) and it uses Session.set to create a variable I use in another component as the search term:
updateSearch(e){
Session.set('searchTerm', e.target.value);
console.log(Session.get('searchTerm'));
}
render(){
return(
<div>
<form>
<div className="form-group">
<input type="text" className="form-control" placeholder="Search Tickets"
// value={this.state.search}
onChange={this.updateSearch.bind(this)}/>
</div>
</form>
</div>
)
}
}
This successfully creates a search term as it is entered. The problem is that when I try to use the 'searchTerm' variable in the listing component to list my collections.
import { Tickets } from '../../../imports/collections/tickets';
import TicketSearch from './TicketSearch';
export default class TicketList extends React.Component {
constructor (props) {
super(props);
this.state = {
tickets: [],
searchTerm: ''
};
}
componentDidMount() {
this.ticketTracker = Tracker.autorun(() => {
Meteor.subscribe('tickets');
let searchTerm = Session.get('searchTerm');
if (searchTerm) {
let tickets = Tickets.find({$in: { talent: searchTerm}}).fetch()
this.setState({ tickets });
}
else {
let tickets = Tickets.find({}).fetch();
this.setState({ tickets });
}
});
}
componentWillUnmount() {
console.log('componentWillUnmount TicketList');
this.ticketTracker.stop();
}
renderTicketList() {
if (!this.state.tickets.length) {
return (
<div>
<p>No Tickets Found</p>
</div>
)
}
return this.state.tickets.map((ticket) => {
return (
<div>
{ticket.talent}
{ticket.city}
</div>
)
render() {
return(
<div>
{this.renderTicketList()}
</div>
);
}
};
The ticket list component should be showing all tickets until something is entered into the search bar (that part is working). After the search bar is used, ideally the searchTerm will filter any tickets that match the 'talent' or 'city' fields of the collection.
Assuming there is no typo or logic bug in your code, my first recommendation is to use createContainer
instead of tracker.autorun. Move your code from tracker.autorun to createContainer function and pass searchTerm as a prop to TicketList. Also move setState code to componentWillReceiveProps method. This is from my personal experience with similar issue and also see https://github.com/meteor/react-packages/issues/99.