I'm new to React, and I just can't figure this out...
I'm trying to pass my state to a prop in the parent component, and then render that state in a child component. Like so:
Parent Component
class App extends Component {
constructor(props) {
super(props);
this.state = {
text: 'hello'
}
}
render() {
return (
<div className="App">
<MessageList textProp={this.state.text}/>
</div>
);
}
}
Child Component
const MessageList = textProp => {
return (
<div className='MessageList'>
{textProp}
</div>
)
}
React won't render properly, claiming it is an object that is trying to be rendered.
I can access the property by using {textProp.textProp}, why is it rendering an object which contains a property of the same name?
I'm sure this is very simple but I could do with an explanation!
Thanks in advance.
Components' props are an object, just as their state is an object. Therefore you want something more like:
const MessageList = props => {
return (
<div className='MessageList'>
{props.textProp}
</div>
)
}
or, using destructuring:
const MessageList = ({ textProp }) => {
return (
<div className='MessageList'>
{textProp}
</div>
)
}
When you declare stateless component you need to specify the arguments you want it to receive it can be (props) or ({ specific }) in your case:
const MessageList = ({ textProp }) => (
<div className='MessageList'>
{textProp}
</div>
);
Note how i do => () that will return the markup, you don't have do to { return () } just a tip to run things better :)
The textProp in MessageList is a collection of props form the parent. In your case the collection will have only textProp. To make this work you need to do the following.
const MessageList = ( {textProp} ) => {
return (
<div className='MessageList'>
{textProp}
</div>
)
}
or
const MessageList = (textProp) => {
return (
<div className='MessageList'>
{textProp.textProp}
</div>
)
}
Related
I have a page in my React App that renders some locally stored JSON Data into a table. I've been trying for a long time to implement a search bar that will filter through the PostCode column in the table and return the row. So far I've been able to make a search bar that does what I want it to but I haven't been able to integrate it into the page with the table.
My apologies if this is an easy question or I'm way off the mark. I'm a coding novice but have to use React for a project and struggle to get to grips with it.
The code for my table looks like this:
import SearchBar from './SearchBar'
import React,{useState,useEffect} from 'react';
import './App.css';
import { Data } from './NewData.js'
export const JsonReader = () => {
return (
<>
<ArchiveHeader />
<div className="data-container">Welcome to RF</div>
{Data.map((data, key) => {
return (
<div key={key}>
<JsonTable
key = {key}
Username = {data.Username}
Address = {data.Address}
PostCode = {data.PostCode}
Details ={data.Details}
Date ={data.Date}
Score ={data.Score}
/>
</div>
);
})}
</>
);
};
const ArchiveHeader = () => {
return (
<header className="ArchiveHeader">
<h2>Rent Flag</h2>
</header>
);
};
const JsonTable= ({ Username, Address, PostCode, Details, Date, Score }) => {
if (!Username) return <div />;
return (
<table data={Data}>
<tbody>
<tr>
<td>
<h5>{Username}</h5>
</td>
<td>
<h5>{Address}</h5>
</td>
<td>
<h4>{PostCode}</h4>
</td>
<td>
<p>{Details}</p>
</td>
<td>
<p>{Date}</p>
</td>
<td>
<p>{Score}</p>
</td>
</tr>
</tbody>
</table>
);
};
export default JsonReader;
and the code for my searchbar looks like this:
import Papa from "papaparse";
import React, { useState, useEffect } from 'react';
import { Data } from './NewData.js'
import JsonReader from './JsonReader'
export default function SearchBar () {
const [searchTerm,setSearchTerm] = useState('')
return (
<div className="SearchBar">
<input type="text" placeholder="search..." onChange={e=>setSearchTerm(e.target.value)} />
{Data.filter((val)=>{
if(searchTerm == ""){
return val
}
else if(val.PostCode.toLowerCase().includes(searchTerm.toLowerCase())){
return val;
}
}).map((val,key)=>{
return <div>{val.PostCode} </div>
})}
</div>
);
}
You can use useMemo hook for this functionality.
const searchResults = useMemo(() => {
if (!searchTerm) {
return items;
}
return Data?.filter((item) =>
item?.toLowerCase()?.includes(searchTerm?.toLowerCase().trim())
);
}, [Data, searchTerm]);
You can use searchResults variable and render that in your table.
In the same JsonReader function you should:
Create the useState constants [searchTerm, setSearchTerm].
Set a new FData array with filtered data and that is the one you should do the mapping to.
Place the input field on the same page.
And, instead of using the Data.map you will use the FData.map.
This is how your JsonReader function should look like (and the SearchBar function could be discarded):
export const JsonReader = () => {
const [searchTerm, setSearchTerm] = useState("");
const FData = Data.filter((val) => {
if (searchTerm == "") {
return val;
} else if (val.PostCode.toLowerCase().includes(searchTerm.toLowerCase())) {
return val;
}
});
return (
<>
<ArchiveHeader />
<div className="data-container">Welcome to RF</div>
<input type="text" placeholder="search..." onChange={(e) => setSearchTerm(e.target.value)} />
{FData.map((data, key) => {
return (
<div key={key}>
<JsonTable
key = {key}
Username = {data.Username}
Address = {data.Address}
PostCode = {data.PostCode}
Details ={data.Details}
Date ={data.Date}
Score ={data.Score}
/>
</div>
);
})}
</>
);
};
I feel like I am very close to fixing it but when I change select option the selected values disappear. How can I avoid this when using Objects in MUI select?
Code Sandbokx: https://codesandbox.io/s/jovial-pascal-ngpgyh?file=/src/App.js:0-909
import { useState } from "react";
import "./styles.css";
import { TextField, MenuItem } from "#material-ui/core";
export default function App() {
const mainOptions = [
{ id: 1, text: "hello" },
{ id: 2, text: "This is just a test" }
];
function handleFilterChange(e) {
console.log(e.target);
setChosenFilterId(e.target.value);
}
const [chosenFilterId, setChosenFilterId] = useState(0);
return (
<>
<TextField
select
label="Category"
value={mainOptions[chosenFilterId].text}
onChange={(e) => handleFilterChange(e)}
>
{mainOptions.map((option, index) => {
return (
//When i try to change value={option} to value={index} I also get another error
<MenuItem value={option} key={index}>
{option.text}
</MenuItem>
);
})}
</TextField>
</>
);
TextField's value and MenuItem's value couldn't be an Object, and both should be the same kay.
So,
<TextField
select
label="Category"
value={chosenFilterId} // <= here
onChange={(e) => handleFilterChange(e)}
>
{mainOptions.map((option, index) => {
return (
<MenuItem value={option.id} key={index}> {/* <= here */ }
{option.text}
</MenuItem>
);
})}
</TextField>
Demo: https://codesandbox.io/s/immutable-star-ppzgzl?file=/src/App.js
I have two classes:
Row (Child Class)
mySpreadsheet (Parent Class)
I am trying to do something like this:
Row:
class Row extends React.Component {
constructor(props, ref) {
super(props);
this.state = { selectedFile: null}
this.handleUpload = this.handleUpload.bind(this);
}
//This handleUpload is being called by the parent class of this Row class through ref.
handleUpload(ev) {
ev.preventDefault();
const data = new FormData();
data.append('file', this.uploadInput.files[0]);
data.append('filename', this.fileName.value);
data.append('comment',this.comment.value);
data.append('id', this.fileName.id);
fetch('http://localhost:8000/upload', {
method: 'POST',
body: data,
}).then((response) => {
response.json().then((body) => {
this.setState({ selectedFile: `http://localhost:8000/${body.file}` });
});
});
}
rowCreator() {
let row = []
for (var i = 0; i < 10; i++) {
row.push(
<td>
<div>
<input type="file" name={`file${this.props.id*10 + i}`} id={this.props.id*10 + 1} ref={(ref) => { this.uploadInput = ref; }}/>
<input type="text" name={`fileName ${this.props.id*10 + i}`} ref={(ref) => { this.fileName = ref; }} placeholder="Name the file with extension"/>
<input type="text" ref={(ref) => { this.comment = ref; }} placeholder="Comment"/>
</div>
</td>
)
}
return row
}
render() {
return (
<tr>
<td class="align-middle ">
<div class="cell">
<input type="text" placeholder={this.props.id + 1} />
</div>
</td>
{this.rowCreator()}
</tr>
)
}
}
and in mySpreadsheet I am creating each row in a table using Row class as follows:
<tbody id="tbody">
{this.state.data.map(id => (
<Row id={id} ref={this.rowRef} />
))}
</tbody>
I am using the handleUpload() function from the Row (child) by using rowRef:
this.rowRef = React.createRef();
upload(ev) {
this.rowRef.current.handleUpload(ev);
}
<button onClick={this.upload}>
Upload Files
</button>
But I am getting error (500) while doing a POST request through my website. Is it because of the ref I am using in Row such as uploadInput, for appending data in handleUpload? Is there any way to make a unique ref for all the cells in my table? Or can I use something else like id or name which I have made uniquely for all different cells using this.props.id*10 + i for each iteration, i while making columns for one row?
You can create refs for mapped elements and put it in an array based on their indexes or ids.
// constructor state part
constructor() {
this.refs = [];
}
// render part
{this.state.data.map(id => (
return <Row id={id} ref={itemRef => this.refs[id] = itemRef} />
))}
And in my opinion, you should hold the values instead of the components itself in an array.
Id must be unique and if the map parameter is an object, you should use a unique property instead of the key which comes with map. If you use keys, React will not keep track of your component while you' re updating your state.
I've been struggling to make this react virtualized table example work & seriously starting to doubt my sanity. I've created a react app and I'm just trying to render the example table inside App.js with this:
class App extends Component {
render() {
var data = [1,2,3,4,5,6,7,8,9,10];
return (
<TableExample
list={data}
/>
);
}
}
React keeps saying list isn't defined - it seems obvious I'm not getting the data into the component the right way. I haven't been able to understand the example code, what props need to be passed in and what they should be named. Sorry for the stupid question but I've been stuck forever not knowing where else to find an answer. The table example code is below:
/** #flow */
import Immutable from 'immutable';
import PropTypes from 'prop-types';
import * as React from 'react';
import {
ContentBox,
ContentBoxHeader,
ContentBoxParagraph,
} from '../demo/ContentBox';
import {LabeledInput, InputRow} from '../demo/LabeledInput';
import AutoSizer from '../AutoSizer';
import Column from './Column';
import Table from './Table';
import SortDirection from './SortDirection';
import SortIndicator from './SortIndicator';
import styles from './Table.example.css';
export default class TableExample extends React.PureComponent {
static contextTypes = {
list: PropTypes.instanceOf(Immutable.List).isRequired,
};
constructor(props, context) {
super(props, context);
const sortBy = 'index';
const sortDirection = SortDirection.ASC;
const sortedList = this._sortList({sortBy, sortDirection});
this.state = {
disableHeader: false,
headerHeight: 30,
height: 270,
hideIndexRow: false,
overscanRowCount: 10,
rowHeight: 40,
rowCount: 1000,
scrollToIndex: undefined,
sortBy,
sortDirection,
sortedList,
useDynamicRowHeight: false,
};
this._getRowHeight = this._getRowHeight.bind(this);
this._headerRenderer = this._headerRenderer.bind(this);
this._noRowsRenderer = this._noRowsRenderer.bind(this);
this._onRowCountChange = this._onRowCountChange.bind(this);
this._onScrollToRowChange = this._onScrollToRowChange.bind(this);
this._rowClassName = this._rowClassName.bind(this);
this._sort = this._sort.bind(this);
}
render() {
const {
disableHeader,
headerHeight,
height,
hideIndexRow,
overscanRowCount,
rowHeight,
rowCount,
scrollToIndex,
sortBy,
sortDirection,
sortedList,
useDynamicRowHeight,
} = this.state;
const rowGetter = ({index}) => this._getDatum(sortedList, index);
return (
<ContentBox>
<ContentBoxHeader
text="Table"
sourceLink="https://github.com/bvaughn/react-virtualized/blob/master/source/Table/Table.example.js"
docsLink="https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md"
/>
<ContentBoxParagraph>
The table layout below is created with flexboxes. This allows it to
have a fixed header and scrollable body content. It also makes use of{' '}
<code>Grid</code> for windowing table content so that large lists are
rendered efficiently. Adjust its configurable properties below to see
how it reacts.
</ContentBoxParagraph>
<ContentBoxParagraph>
<label className={styles.checkboxLabel}>
<input
aria-label="Use dynamic row heights?"
checked={useDynamicRowHeight}
className={styles.checkbox}
type="checkbox"
onChange={event =>
this._updateUseDynamicRowHeight(event.target.checked)
}
/>
Use dynamic row heights?
</label>
<label className={styles.checkboxLabel}>
<input
aria-label="Hide index?"
checked={hideIndexRow}
className={styles.checkbox}
type="checkbox"
onChange={event =>
this.setState({hideIndexRow: event.target.checked})
}
/>
Hide index?
</label>
<label className={styles.checkboxLabel}>
<input
aria-label="Hide header?"
checked={disableHeader}
className={styles.checkbox}
type="checkbox"
onChange={event =>
this.setState({disableHeader: event.target.checked})
}
/>
Hide header?
</label>
</ContentBoxParagraph>
<InputRow>
<LabeledInput
label="Num rows"
name="rowCount"
onChange={this._onRowCountChange}
value={rowCount}
/>
<LabeledInput
label="Scroll to"
name="onScrollToRow"
placeholder="Index..."
onChange={this._onScrollToRowChange}
value={scrollToIndex || ''}
/>
<LabeledInput
label="List height"
name="height"
onChange={event =>
this.setState({height: parseInt(event.target.value, 10) || 1})
}
value={height}
/>
<LabeledInput
disabled={useDynamicRowHeight}
label="Row height"
name="rowHeight"
onChange={event =>
this.setState({
rowHeight: parseInt(event.target.value, 10) || 1,
})
}
value={rowHeight}
/>
<LabeledInput
label="Header height"
name="headerHeight"
onChange={event =>
this.setState({
headerHeight: parseInt(event.target.value, 10) || 1,
})
}
value={headerHeight}
/>
<LabeledInput
label="Overscan"
name="overscanRowCount"
onChange={event =>
this.setState({
overscanRowCount: parseInt(event.target.value, 10) || 0,
})
}
value={overscanRowCount}
/>
</InputRow>
<div>
<AutoSizer disableHeight>
{({width}) => (
<Table
ref="Table"
disableHeader={disableHeader}
headerClassName={styles.headerColumn}
headerHeight={headerHeight}
height={height}
noRowsRenderer={this._noRowsRenderer}
overscanRowCount={overscanRowCount}
rowClassName={this._rowClassName}
rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight}
rowGetter={rowGetter}
rowCount={rowCount}
scrollToIndex={scrollToIndex}
sort={this._sort}
sortBy={sortBy}
sortDirection={sortDirection}
width={width}>
{!hideIndexRow && (
<Column
label="Index"
cellDataGetter={({rowData}) => rowData.index}
dataKey="index"
disableSort={!this._isSortEnabled()}
width={60}
/>
)}
<Column
dataKey="name"
disableSort={!this._isSortEnabled()}
headerRenderer={this._headerRenderer}
width={90}
/>
<Column
width={210}
disableSort
label="The description label is really long so that it will be truncated"
dataKey="random"
className={styles.exampleColumn}
cellRenderer={({cellData}) => cellData}
flexGrow={1}
/>
</Table>
)}
</AutoSizer>
</div>
</ContentBox>
);
}
_getDatum(list, index) {
return list.get(index % list.size);
}
_getRowHeight({index}) {
const {list} = this.context;
return this._getDatum(list, index).size;
}
_headerRenderer({dataKey, sortBy, sortDirection}) {
return (
<div>
Full Name
{sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />}
</div>
);
}
_isSortEnabled() {
const {list} = this.context;
const {rowCount} = this.state;
return rowCount <= list.size;
}
_noRowsRenderer() {
return <div className={styles.noRows}>No rows</div>;
}
_onRowCountChange(event) {
const rowCount = parseInt(event.target.value, 10) || 0;
this.setState({rowCount});
}
_onScrollToRowChange(event) {
const {rowCount} = this.state;
let scrollToIndex = Math.min(
rowCount - 1,
parseInt(event.target.value, 10),
);
if (isNaN(scrollToIndex)) {
scrollToIndex = undefined;
}
this.setState({scrollToIndex});
}
_rowClassName({index}) {
if (index < 0) {
return styles.headerRow;
} else {
return index % 2 === 0 ? styles.evenRow : styles.oddRow;
}
}
_sort({sortBy, sortDirection}) {
const sortedList = this._sortList({sortBy, sortDirection});
this.setState({sortBy, sortDirection, sortedList});
}
_sortList({sortBy, sortDirection}) {
const {list} = this.context;
return list
.sortBy(item => item[sortBy])
.update(
list => (sortDirection === SortDirection.DESC ? list.reverse() : list),
);
}
_updateUseDynamicRowHeight(value) {
this.setState({
useDynamicRowHeight: value,
});
}
}
Looking at some previous questions, it seems that the example is using some components that are not included inside dist package. Thats probably why you are getting undefined error.
Here is the most basic example of Tables in react virtulized:
import React from 'react';
import ReactDOM from 'react-dom';
import { Column, Table } from 'react-virtualized';
import 'react-virtualized/styles.css'; // only needs to be imported once
// Table data as an array of objects
const list = [
{ name: 'Brian Vaughn', description: 'Software engineer' }
// And so on...
];
// Render your table
ReactDOM.render(
<Table
width={300}
height={300}
headerHeight={20}
rowHeight={30}
rowCount={list.length}
rowGetter={({ index }) => list[index]}
>
<Column
label='Name'
dataKey='name'
width={100}
/>
<Column
width={200}
label='Description'
dataKey='description'
/>
</Table>,
document.getElementById('example')
);
When learning new library, its always best to start with the most simple example then expand on it. Here is the link to the full table docs.
The issue with the example is that in order for contextTypes to work, the parent component needs to define corresponding contextTypes and a getChildContext function.
In the parent component:
class App extends React.Component {
static childContextTypes = {
list: PropTypes.instanceOf(Immutable.List).isRequired
};
getChildContext() {
return {
list
};
}
render() {
return <TableExample />;
}
}
Mentioned in this issue; see lines 48-53 and 68-76 of the react-virtualized demo Application.js.
I am building a 1-1 chat using Angular4 and Firebase and I am pretty new to Angular.
In order to initiate a conversation, I am trying to display all available users form '/users' subcollection. So, I need to get user/{user.uid}/username.
This is my chat.component.ts:
import { Component, OnInit } from '#angular/core';
import {AngularFireDatabase, FirebaseListObservable} from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import { UserSessionService } from '../_services/user-session.service';
#Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {
items: FirebaseListObservable<any[]>;
other_users: FirebaseListObservable<any[]>;
user_id: any;
from: any;
msgVal: string = '';
constructor(public afAuth: AngularFireAuth, public af: AngularFireDatabase, public logged_user: UserSessionService ){ }
ngOnInit() {
this.from= this.logged_user.getFirebaseUid();
this.user_id= this.logged_user.getFirebaseUid();
this.items = this.af.list('/personalMessages', {
query: { limitToLast: 5 }
});
this.other_users= this.af.list('/users');
}
findChat(){
this.other_users= this.other_users;
this.user_id = this.user_id;
}
chatSend(theirMessage: string) {
this.items.push({ text: theirMessage, from: this.logged_user.getFirebaseUid(), isRead: false, timestamp: + new Date() });
this.msgVal = '';
this.user_id = this.user_id;
this.other_users= this.other_users;
}
}
And this is my chat.component.html:
<div class="users-chat-container" *ngFor="let other_user of other_users| async">
<div id="circle" style="background-color:pink;">
</div>
<br/> {{other_user.username}}
</div>
<div class="chat-container" *ngFor="let item of items | async">
<div id="circle" style="background-image:url( http://www.ics.forth.gr/mobile/female.png);">
</div>
<br/> {{item.from}}
<p>{{item.text}}</p>
</div>
<input type="text" id="message" placeholder="Type a message..." (keyup.enter)="chatSend($event.target.value)" [(ngModel)]="msgVal" />
How can I iterate over the array of objects I get from '/users' collection? Thank you! :)
you need use ForkJoin. ForkJoin will take users list as input and fire parallel request for all users list
try some thing like this
this.af.list('/users')
.mergeMap((users) => {
if (users.length > 0) {
return Observable.forkJoin(
users.map((user) => this.af.database
.object(`user/${user.$uid}/username`)
.first()
),
(...values) => { // here you can assign username
users.forEach((user, index) => { user.username = values[index]; });
return users;
}
);
}
return Observable.of([]);
});
more info about forkJoin
https://www.learnrxjs.io/operators/combination/forkjoin.html
You need an array for *ngFor. With object.keys you can create an array of the objects. In the example below I have done this with players from a group, coming as objects from firebase.
private getPlayersPerGroup(group: Group) {
this.playersInGroup = [];
if (group["players"]) {
Object.keys(group["players"]).forEach((key) => {
this.groupService.getPlayerById(key).then((player) => {
this.playersInGroup.push(player);
});
});
}
}