export class Person extends Component{
constructor(props){
super(props);
this.state = {status:""};
}
componentDidMount(){
/* get status from database */
if (this.props.status === "online"){
document.getElementById("dot").style.background = "green";
this.setState({status:"online"});
}
else if(this.props.status === "offline"){
document.getElementById("dot").style.background = "red";
this.setState({status:"offline"});
}
else if(this.props.status === "away"){
document.getElementById("dot").style.background = "yellow";
this.setState({status:"away"});
}
}
render(){
return(
<ListGroup.Item action variant="success" id="personLi" >
<Row>
<Col>
<Image id="avatar" src={Avatar} roundedCircle />
</Col>
<Col id="profileName">
{this.props.name}
</Col>
<Col>
<span id="dot"></span>
</Col>
</Row>
</ListGroup.Item>
)
}
}
Here is my Person class. I am trying to create a friendbar that has a list of friend names which will get rendered into components that will have a status bar that will change color based on whether they are online (green) and offline (red). However, when I try to create Person components in my friend bar only the first component has a status bar color of green. Currently, I want to just my status to start off as online which is passed into props.
It is not good idea to mixup things. Why you use DOM API together with React?
why do not you want to use the virtual DOM?
And it is an anti-pattern to copy props directly to the component's state.
export class Person extends Component{
constructor(props){
super(props);
this.state = {status:""};
}
componentDidMount(){
fetchState().then((status) => this.setState({status}));
}
getStatusColor() {
switch(this.state.status) {
case "offline": return "red";
case "online": return "green";
case "away": return "yellow";
}
}
render() {
return (
<ListGroup.Item action variant="success" id="personLi" >
<Row>
<Col>
<Image id="avatar" src={Avatar} roundedCircle />
</Col>
<Col id="profileName">
{this.props.name}
</Col>
<Col>
<span style={{backgroundColor: this.getStatusColor()}}></span>
</Col>
</Row>
</ListGroup.Item>
)
}
document.getElementById will get the first instance of that ID on the page and change the style. You could handle this in a more React-y way:
export class Person extends Component{
render(){
let color = 'green';
if(this.props.status === 'offline'){
color = 'red';
} else if (this.props.status === 'away') {
color = 'yellow';
}
return(
<ListGroup.Item action variant="success" id="personLi" >
<Row>
<Col>
<Image id="avatar" src={Avatar} roundedCircle />
</Col>
<Col id="profileName">
{this.props.name}
</Col>
<Col>
<span style={{ backgroundColor: color }} />
</Col>
</Row>
</ListGroup.Item>
);
}
}
Related
I have two different react components placed one after the other in my app named SearchBar and InfiniteScroller;
function App() {
const [searchTerm, setSearchTerm] = useState("");
return (
<div className="App">
<SNavbar></SNavbar>
<MainLogo></MainLogo>
<SearchBar search={setSearchTerm}></SearchBar>
<hr/>
<InfiniteScroller term={searchTerm}/>
<Footer/>
</div>
);
}
The search bar component has its own state where it updates a search term as its input is being edited and it calls the setSearch function of its parent when the button is clicked (the function is passed as a prop in the parent)
function SearchBar(props)
{
const [search,setSearch] = useState("");
return(
<Container className="Search-Bar">
<Row>
<Col>
<InputGroup >
<FormControl
placeholder="What are we making today?"
onChange={event => setSearch(event.target.value)}
/>
<Button onClick={() => props.search(search)}>
Go!
</Button>
</InputGroup>
</Col>
</Row>
</Container>)
}
The search term that is updated by the SearchBar component is passed onto the InfiniteScroller component as a property and is set as the searchTerm field in its state object.
class InfiniteScroller extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
items:[],
page:1,
hasMore:true,
searchTerm:props.term
};
}
render(){
return(
<InfiniteScroll
dataLength={this.state.items.length}
next={this.fetchData}
hasMore={this.state.hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
>
<Row>
{this.state.items.map((i, index) => (
<Col key={index} lg="2" md="4" sm="6" xs="12">
<ImageCell className="ImageCell" link = {this.state.items[index].link}> - #{index}</ImageCell>
</Col>
))}
</Row>
</InfiniteScroll>
)
}
}
However when the setSearchTerm function of App.js is triggered by pressing the button on the SearchBar component, the InfiniteScroller does not seem to get updated. As the SearchTerm field of its state still comes up as "undefined" and the component itself does not re-render to represent the change in property.
I want the InfiniteScroller to completely re-render itself and make some API calls to populate itself with content, How can I achieve this?
So far I've tried adding in HTML tags that have the SearchTerm property in them to check if react skips re-rendering components that don't "use" any properties but that has not worked.
The props' change does not make the UI re-rendering but the states' change does.
It has 2 potential ways to fix have a proper UI re-rendering.
For the first one, you can add key attribute to your component that will help you do a trick for re-rendering whenever key gets changed
<InfiniteScroller term={searchTerm} key={searchTerm}/>
The second way, you can update your local states of that component by componentDidUpdate (useEffect in function-based components)
class InfiniteScroller extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
items:[],
page:1,
hasMore:true,
searchTerm:props.term
};
}
//update states according to props change
componentDidUpdate(prevProps) {
if(this.props.searchTerm !== prevProps.searchTerm) {
setState({ searchTerm: this.props.searchTerm })
}
}
render(){
return(
<InfiniteScroll
dataLength={this.state.items.length}
next={this.fetchData}
hasMore={this.state.hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
>
<Row>
{this.state.items.map((i, index) => (
<Col key={index} lg="2" md="4" sm="6" xs="12">
<ImageCell className="ImageCell" link = {this.state.items[index].link}> - #{index}</ImageCell>
</Col>
))}
</Row>
</InfiniteScroll>
)
}
}
I have multiple elements in the state, and when input slider is dragged i want to set the value into state elements, i've tried to write one function that will setState for the dragged elements in state but it isn't working
Here's what i've tried. When slider changes in the browser it shows -- Cannot read property 'name' of undefined.
Can anyone help to solve this problem. I just want to set slider value into state with one function.
import React, {Component} from "react";
import './table.css';
import { Table, Slider, InputNumber, Row, Col } from 'antd';
export default class TableRender extends Component{
state = {
averageCheck: 0,
newSales: 0,
customerChurn:0,
reSales:0,
totalSales:0,
advertisingBudget: 0,
advertisingAgency: 0,
kpi:0,
salesManager:0,
};
handleChange=(event)=>{
let nam = event.target.name;
let val = event.target.value;
this.setState({[nam]: val});
console.log(nam)
};
render(){
const { averageCheck, newSales, customerChurn } = this.state;
return (
<div className="slider1">
<h2>Tovar</h2>
<p>Tovar o'rtacha qiymati</p>
<Row>
<Col span={12}>
<Slider
name="averageCheck"
min={10000}
max={30000000}
step={5000}
onChange={this.handleChange}
value={typeof averageCheck === 'number' ? averageCheck : 0}
/>
</Col>
<Col span={2}>
<InputNumber
min={10000}
max={30000000}
step={5000}
style={{ margin: '0 16px' }}
value={averageCheck}
onChange={this.handleChange}
/>
</Col>
</Row>
<p>Yangi Sotuvlar</p>
<Row>
<Col span={12}>
<Slider
name="newSales"
min={1000000}
max={30000000}
step={5000}
onChange={this.handleChange}
value={typeof newSales === 'number' ? newSales : 0}
/>
</Col>
<Col span={2}>
<InputNumber
min={1000000}
max={30000000}
step={5000}
style={{ margin: '0 16px' }}
value={newSales}
onChange={this.handleChange}
/>
</Col>
</Row>
<p>Xaridor Kamayishi, %</p>
<Row>
<Col span={12}>
<Slider
name="customerChurn"
min={1}
max={100}
step={1}
marks={marks}
style={{ borderColor: 'green' }}
onChange={this.handleChange}
value={typeof customerChurn === 'number' ? customerChurn : 0}
/>
</Col>
<Col span={2}>
<InputNumber
min={1}
max={100}
step={1}
style={{ margin: '0 16px' }}
value={customerChurn}
onChange={this.handleChange}
/>
</Col>
</Row>
</div>
)
}
}
The antd Slider onChange callback return a value not an event object that's why its give you an error of accessing undefined. Try to modify the onChange callback like this:
<Slider
min={10000}
max={30000000}
step={5000}
onChange={(value) => this.handleChange("averageCheck", value)}
value={typeof averageCheck === "number" ? averageCheck : 0}
/>;
and in your handleChange function:
handleChange = (name, value) => {
let nam = name;
let val = value;
this.setState({ [nam]: val });
console.log(nam);
};
It can be also applied on your InputNumber so you are still using one function
I'm trying to access "props" from a component for which I'm passing an object. I'm a bit lost with JS here ; basically what I'm trying to do is to build a Master/Detail view (so show/hide 2 different components based on user clicks on a table).
How can I access "props" from the object rowEvent once a user clicks on a table row ?
const rowEvents = {
onClick: (e, row, rowIndex) => {
console.log(row.itemId);
//this.currentItemId= row.itemId; //////////// THIS DOESNT WORK...
}
};
const TableWithSearch = (props) => {
const { SearchBar } = Search;
const { ExportCSVButton } = CSVExport;
return (
<Card>
<CardBody>
<h4 className="header-title">Search and Export</h4>
<p className="text-muted font-14 mb-4">A Table</p>
<ToolkitProvider
bootstrap4
keyField="itemId"
data={props.data}
columns={columns}
search
exportCSV={{ onlyExportFiltered: true, exportAll: false }}>
{props => (
<React.Fragment>
<Row>
<Col>
<SearchBar {...props.searchProps} />
</Col>
<Col className="text-right">
<ExportCSVButton {...props.csvProps} className="btn btn-primary">
Export CSV
</ExportCSVButton>
</Col>
</Row>
<BootstrapTable
{...props.baseProps}
bordered={false}
rowEvents={ rowEvents }
defaultSorted={defaultSorted}
pagination={paginationFactory({ sizePerPage: 5 })}
wrapperClasses="table-responsive"
/>
</React.Fragment>
)}
</ToolkitProvider>
</CardBody>
</Card>
);
};
And the component looks like this :
render() {
let show;
if (this.props.currentItemId === null){
show = (<TableWithSearch data={this.props.data} />)
}
else {
show = (<DisplayItem />)
}
return (
<React.Fragment>
<Row>
<Col>
{ show }
</Col>
</Row>
</React.Fragment>
)
}
}
Your issue is a bit complex because you seem to be needing to update the prop currentItemId from parent's parent.
You can solve your issue by doing the following:
Move the declaration of rowEvents objects in side TableWithSearch functional component.
In TableWithSearch component, receive a callback say updateCurrentItemId from parent which updates the currentItemId in the parent
In parent component, the currentItemId is being passed from parent(again). So maintain a state for it.
TableWithSearch Component
const TableWithSearch = (props) => {
const { SearchBar } = Search;
const { ExportCSVButton } = CSVExport;
const {updateCurrentItemId} = props; //<--------- receive the prop callback from parent
const rowEvents = {
onClick: (e, row, rowIndex) => {
console.log(row.itemId);
updateCurrentItemId(row.itemId) // <--------- use a callback which updates the currentItemId in the parent
//this.currentItemId= row.itemId; //////////// THIS DOESNT WORK...
},
};
return (
<Card>
<CardBody>
<h4 className="header-title">Search and Export</h4>
<p className="text-muted font-14 mb-4">A Table</p>
<ToolkitProvider
bootstrap4
keyField="itemId"
data={props.data}
columns={columns}
search
exportCSV={{ onlyExportFiltered: true, exportAll: false }}
>
{(props) => (
<React.Fragment>
<Row>
<Col>
<SearchBar {...props.searchProps} />
</Col>
<Col className="text-right">
<ExportCSVButton
{...props.csvProps}
className="btn btn-primary"
>
Export CSV
</ExportCSVButton>
</Col>
</Row>
<BootstrapTable
{...props.baseProps}
bordered={false}
rowEvents={rowEvents}
defaultSorted={defaultSorted}
pagination={paginationFactory({ sizePerPage: 5 })}
wrapperClasses="table-responsive"
/>
</React.Fragment>
)}
</ToolkitProvider>
</CardBody>
</Card>
);
};
Parent Component
class ParentComp extends React.Component {
state = {
curItemId: this.props.currentItemId
}
updateCurrentItemId = (udpatedCurId) => {
this.setState({
curItemId: udpatedCurId
})
}
render() {
let show;
// if (this.props.currentItemId === null){
if (this.state.curItemId === null){
show = (<TableWithSearch data={this.props.data} updateCurrentItemId={this.updateCurrentItemId}/>)
}
else {
show = (<DisplayItem />)
}
return (
<React.Fragment>
<Row>
<Col>
{ show }
</Col>
</Row>
</React.Fragment>
)
}
}
}
this.props should give you access for class components
In addition you should create a bind to the click function so it can correctly resolve this, in the constuctor of the rowEvent
I have a following example simple page:
App.js:
export default class App extends React.Component {
render() {
return <Router>
<Switch>
<Route exact path='/' component={ArticlesPage}/>
<Route path='/search' component={SearchPage}/>
</Switch>
</Router>
};
};
ArticlesPage.js:
export default class ArticlesPage extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Grid>
<Row>
<Col lg={12}>
<SearchBox/>
</Col>
</Row>
<Row>
<Col lg={12}>
articles
</Col>
</Row>
</Grid>;
}
};
SearchPage.js:
export default class SearchPage extends React.Component {
constructor(props) {
super(props);
const {q} = queryString.parse(location.search);
this.state = {
query: q
};
}
render() {
return <Grid>
<Row>
<Col lg={12}>
<SearchBox/>
</Col>
</Row>
<Row>
<Col lg={12}>
search {this.state.query}
</Col>
</Row>
</Grid>;
}
};
SearchBox.js:
export default class SearchBox extends React.Component {
constructor(props) {
super(props);
this.state = {
q: ''
};
}
onFormSubmit = (e) => {
e.preventDefault();
const {router} = this.context;
router.history.push('/search?q=' + this.state.q);
};
handleChange = (e) => {
this.setState({q: e.target.value});
};
render() {
return <form onSubmit={this.onFormSubmit}>
<Col lg={10} lgOffset={1}>
<FormGroup>
<input type="text" name="q" id="q" ref={i => this.searchInput = i} onChange={this.handleChange} />
</FormGroup>
</Col>
</form>;
}
};
And now, when I'm on the index page and type something in the input next send form, React render SearchPage.js and return correctly text search *and what I typed*, try again type something else in the input and send form, and React still show my previous text (not rerender).
What can be wrong with this simple page?
You have two different state variables, query on <SearchPage /> and q on <SearchBox />. What you are changing is q, but the variable you are rendering as text is query.
You need to lift state up and pass query as prop to <SearchPage />.
Here's why the text on SearchPage doesn't update: the constructor runs once and updates the variable in state, but when the app re-renders, React, wanting to be efficient, sees that it would re-render a new SearchPage in the same spot as the previous one, so instead of replacing it, it keeps the state of the old one. Because of this, SearchPage's state still keeps the old q variable.
Here's how you can fix it: make your SearchPage accept the search query as a prop, and render that.
class SearchPage extends React.Component {
render() {
return (
<Grid>
<Row>
<Col lg={12}>
<SearchBox />
</Col>
</Row>
<Row>
<Col lg={12}>search {this.props.query}</Col>
</Row>
</Grid>
)
}
}
In the parent, where the route for it is being rendered, use a render function, take the props of it, parse the actual query from props.location.search, and pass it directly to SearchPage.
<Route
path="/search"
render={props => <SearchPage query={getSearchQuery(props.location.search)} />}
/>
// utility function to keep things clean
function getSearchQuery(locationSearch) {
return queryString.parse(locationSearch.slice(1)).q
}
Here's a working demo.
I am trying to show my results from a JSON file only when the search button is clicked. What is the correct way to do it?
Right now as the user types a product the results are show. I have a simple filter, that is filtering the results, but I would like to make that only appear when the button is clicked. I only want to show results when the search button is clicked.
class App extends Component {
constructor(props){
super(props);
this.state = {
value: '',
list: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.refresh();
}
handleChange(event){
this.setState({ ...this.state, value: event.target.value })
}
refresh(){
axios.get(`${URL}`)
.then(resp => this.setState({...this.state, value: '', list: resp.data}));
}
handleSearch(product){
this.refresh();
}
render(){
return(
<div className="outer-wrapper">
<Header />
<main>
<Container>
<Row>
<Col xs={12} md={12} lg={12} className="pl-0 pr-0">
<SearchBar
handleChange={this.handleChange}
handleToggle={this.handleToggle}
handleSearch={this.handleSearch}
value={this.state.value}
/>
<SearchResultBar
value={this.state.value}
/>
<Filter />
</Col>
</Row>
<ProductList
value={this.state.value}
list={this.state.list}
/>
</Container>
</main>
</div>
)
}
}
export default App;
class Search extends Component {
constructor(props){
super(props);
}
render(){
return(
<div className="search-input">
<InputGroup>
<Input placeholder='Enter Search'
onChange={this.props.handleChange}
value={this.props.value}
/>
<InputGroupAddon className='input-group-append'
onClick={this.props.handleSearch}>
<span className='input-group-text'>
<i className="fa fa-search fa-lg fa-flip-horizontal"></i>
</span>
</InputGroupAddon>
</InputGroup>
</div>
)
}
}
export default Search;
class ProductList extends Component {
constructor(props){
super(props);
this.state = {
}
}
render(){
let filteredSearch = this.props.list.filter(
(product) => {
return product.title.indexOf(this.props.value) !== -1
}
)
return(
<Container>
<Row>
{
filteredSearch.map(item => {
return <Product {...item} key={item._id} />
})
}
</Row>
</Container>
);
}
}
export default ProductList;
As it stands, my list of products is being displayed in the app as soon as it loads. This seems something trivial, but I have been scratching my head in trying to solve it.
You're calling this.refresh() inside the constructor. So it gets run on mount.
Just remove it from the constructor and you should be fine.