i have this react App component
<BreadCrumb>
<Chip/>
<Chip/>
<Chip/>
...
<BreadCrumb/>
<CardContainer>
<Card/>
<Card/>
<Card/>
...
<CardContainer/>
<Button/>
<Stores/>
I need to set as initial page only the component, it takes stores from a JSON and map them in a selection
class Stores extends React.Component {
constructor(props) {
super(props);
this.state = {
stores: [],
};
}
componentDidMount(){
axios
.get(URLS.STORES)
.then(r => r.data)
.then(stores => {
this.setState({
stores
});
console.log(this.state.stores)
});
}
render(){
const options = this.state.stores.map(store => (
<option key={store.storeCode} value={store.storeCode}>
{store.storeName}
</option>
));
return(
<div>
<select>{options}</select>
</div>
);
}
}
i need to render only this component as initial page and after selecting a store to render the entire App, without the stores component, how can i use React router to achieve this?
Related
I was looking up how to dynamically populate a dropdown() component I have set up and I am having a hard time getting it to work. The data for the categories is getting pulled (fetched) from a Rails Api I have setup on the backend. I am also getting a 'TypeError: this.props.state is undefined' message as well if that helps with factors to my issue.
So far in my RecipeInput form component this is what I have so far with the dropdown rendered:
//I took out some of the event handlers to try to keep this explanation straight to the point
import React, { Component } from 'react'
import Catagories from './Catagories.js'
class RecipeInput extends Component{
constructor(props){
super(props)
this.state = {
catagories: [],
name:'',
ingredients: '',
chef_name: '',
origin: '',
}
}
componentDidMount(){
let initialCats = [];
const BASE_URL = `http://localhost:10524`
const CATAGOREIS_URL =`${BASE_URL}/catagories`
fetch(CATAGOREIS_URL)
.then(resp => resp.json())
.then(data => {
initialCats = data.results.map((catagory) => {
return catagory
})
this.setState({
catagories: initialCats,
})
});
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<Catagories catagories={this.state.catagories}/>
<input value='submit' type='submit'/>
</form>
</div>
)
}
}
export default RecipeInput
And here is my actual Catagories component:
import React, { Component } from 'react'
class Catagories extends Component{
constructor(){
super()
}
render(){
let catagories = this.props.state.catagories
let optionItems = catagories.map((catagory,index) =>
<option key={index}>{catagory.name}</option>
)
return (
<div>
<select>
{optionItems}
</select>
</div>
)
}
}
export default Catagories
Who is able to point out my dropdown is not populating based on the code provided?
Use this.props.catagories instead this.props.state.catagories
I have one component in which I have one button and I am calling one node js service on that. I am getting a response back from that service and I want to pass that response on next component to display a data there. Below is my component which is doing a node js call.
import { FormGroup } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import axios from "axios";
export default class Abc extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
axios.get(url)
.then(res => {
this.setState({
data: res.data// I need this variable to pass to next component Pqr where I can use it for display purpose.
})
this.props.history.push("/Pqr",{ response:res.data});
})
};
render() {
return (
<form >
<button className="btn btn-info btn-sm" onClick={this.handleClick} style={{ whitespace: 'nowrap' }} >
Launch
</button>
</form>
)
}
}
My Pqr component code is as below.
import React from "react";
export default class ModelLaunch extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const state = this.props.location.state
return (
<h1>This page will display model Inputs : {state} </h1>
)
}
}
I have solved above problem with other way. Instead of calling a node js service on Abc component I am just redirecting it to new coponent and in new component's componentDidMount() method I am calling a node js service and storind a data in props. In this way I have my data on new copmonent. Below is my updated code in Abc component now.
import { FormGroup } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import axios from "axios";
export default class Abc extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => {
e.preventDefault();
this.props.history.push("/Pqr");
})
};
render() {
return (
<form >
<button className="btn btn-info btn-sm" onClick={this.handleClick} style={{ whitespace: 'nowrap' }} >
Launch
</button>
</form>
)
}
And in pqr coponent's code as below
import React from "react";
import axios from "axios";
export default class Pqr extends React.Component{
constructor(props)
{
super(props);
this.state = {
data :[]
}
}
componentDidMount(){
axios.get(url).then((res) => {
console.log("res****",res.data)
this.setState({
data:res.data
})
}).catch((err) =>{
console.log("err", err)
})
}
render()
{
return(
<h1>This page will display data</h1>
)
}
}
I see you're changing a route (using react-router?).
Remember that this.setState is async and specific for your component, when you call this.props.history.push('/Pqr'), maybe the this.state.data is not updated yet.
To share this data through different routes in the same react project, I actually know that you can:
Store it on window.localStorage and then get on the next route here have a tutorial
Use react contexts to share data between components (if you're not reloading the page)
Send data through routes with react-router, as explained here
If its not the case, and you just want to pass the property down or above the hierarchy tree, in addition to the comments above, maybe it can help:
As you probably know, react projects are composed of components that are put all together to work in a specific way. In the example below, there are two components (father and child)
import React from 'react';
// We can say that this component is "below" Father
function Child(props) {
return (
<button>
Hey, I'm a button!
</button>
);
}
// We can say that this component is "above" Child
function Father(props) {
return (
<div>
<Child />
</div>
);
}
I couldn't find in the provided code/question, one child component, maybe you forgot to write it?
If the response is "yes", I'll create a fictional component called... FictionalComponent (I'm a Genius!), and pass the data on state as a property named... data.
In order to pass this property, if its the case, you just need to update your render method to look like this:
render() {
return (
<form >
<button
className="btn btn-info btn-sm"
onClick={this.handleClick}
style={{ whitespace: 'nowrap' }}
>
Launch
<FictionalComponent data={this.state.data} />
</button>
</form>
)
}
This way, when this.state.data changes, the FictionalComponent will be re-rendered with the new data value.
But, maybe you want the reverse operation and you need to pass the this.state.data to the component above your Abc, listed there when the button is pressed.
To achieve it you need to have a "Father" component to your Abc, the "Father" component must provide an onDataChanged callback in order to capture the event. This callback will receive the data and handle it.
In this case, I'll create another component to be the component above your Abc. I'll name it... AboveAbcComponent, perfect!
...
class AboveAbcComponent extends React.Component {
constructor(props) {
this.state = {
dataFromChild: null
};
this.onDataChanged = this.onDataChanged.bind(this);
}
onDataChanged(dataReceived) {
console.log("Yey! It works!");
this.setState({ dataFromChild: dataReceived });
}
render() {// Just the passed props changes here
...
<Abc
onDataChanged={this.onDataChanged}
/>
...
}
}
export default class Abc extends React.Component {
constructor(props) { ... } // No changes here
handleClick = (e) => {
e.preventDefault();
axios.get(url)
.then(res => {
this.setState({
data: res.data
});
this.props.onDataChanged(res.data);
this.props.history.push("/Pqr"); // I really didn't understand Why this push is here... but ok
})
};
render() { ... } // No changes here
}
Hope it helps, have fun!
I have 2 components in my react application. On first time page load, the first component is supposed to make a query and display data(buttons) accordingly. The state of second component till now is empty. When the user clicks on any of the button, another request should be made to the sever and state of the second component should be changed and should be reflected on the web page.
These are my files..
Apps.js
import React, { Component } from 'react';
import './App.css';
import OrgList from "./orgList"
import OrgDetails from "./orgDetails"
class App extends Component {
render() {
return [
<OrgList/>,
<OrgDetails/>
];
}
}
export default App;
orgList.js
import React, { Component } from 'react'
import OrgDetails from "./orgDetails"
var posts =[]
class OrgList extends Component {
constructor(props){
super(props);
this.state={
mainpost: [],
devices:[],
}
}
componentDidMount(){
fetch(someURL)
.then(res => res.json())
.then(function (data){
for (let i = 0; i < 3; i++){
posts.push(data.orgs[i].name)
}
}).then(mainpost => this.setState({mainpost:posts}));
}
render() {
var token =new OrgDetails();
const postItems =this.state.mainpost.map((post) => (
console.log(post),
<button
data-tech={post}
key={post}
className="org-btn"
onClick={() => token.dispatchBtnAction(post)}
>
<h3>{post}</h3>
</button>
)
)
return (
<div>
<h3> Organisations!!!! </h3>
<h5>{postItems}</h5>
</div>
)
}
}
export default OrgList;
orgDetails.js
import React, { Component } from 'react'
var list =[]
const orgname = org =>
`someURL/${org}`
class OrgDetails extends Component {
state={
devices:[],
}
constructor(props){
super(props);
this.state={
devices: [],
}
this.dispatchBtnAction=this.dispatchBtnAction.bind(this)
}
dispatchBtnAction=(str) => {
list =[]
fetch(orgname(str))
.then(res => res.json())
.then(function (data){
for (let i = 0; i < 3; i++){
//console.log("123")
list.push(data.devices[i].location)
console.log(list)
}
}).then(devices => this.setState({
devices : list,
}));
}
render() {
const devices=this.state.devices.map((dev,i)=>(
<div key={dev}>
<li>{dev}</li>
</div>
))
return (
<div>
<p>{devices}</p>
</div>
)
}
}
export default OrgDetails;
But I am getting this warning...
Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the OrgDetails component.
Because of this, the state is not getting changed and the component is not rerendering.
How to eliminate this warning and if any better method is there please do suggest.
As these 2 component are not parent-child components, perhaps you should implement all the logic in the App and than pass state-handlers as props to each component.
Then your components will look something like this:
class App extends Component {
state = { clicks: 0 }
incrementState = () {
const prev = this.state.clicks;
this.setState({ clicks: prev + 1 })
}
render() {
return [
<DisplayComponent counter={this.state.clicks} />,
<ControlComponent onIncrement={this.incrementState} />
];
}
}
Component that displays state
class DisplayComponent extends Component{
render() {
return (<h3>this.props.counter</h3>);
}
}
Component that handles state
class ControlComponent extends Component {
render() {
return (<button onClick={this.props.onIncrement}>click me</button>)
}
}
Well the whole issue is this line var token =new OrgDetails(); This just creates the object. But doesn't mount it in the DOM. It also doesn't reference to the component <OrgDetails/> created in App. So when you try to use token.dispatchBtnAction(post), you are trying to setState on a component that is not mounted in the DOM, hence the error.
This is a really questionable way of making communication in between two components. You are better off using a Parent-Child relationship in between component. Also you can have a look at making Presentational Component and Container components differentiation to make the workflow easy. Have a read at the this link.
Goal
I'm trying to manage mouseenter and mouseleave events from a parent component, for a collection of child components that keep getting re-rendered.
I'm building a reusable component for the collection of listings, that does several things like pagination, and a few other things when a listing is hovered.
So to make this reusable, I have to maintain the state of the hovered listing from the parent CollectionComponent, and mutate each individual listing component based on the state of the parent.
Code
Here are the components I'm using (I stripped them all down to their most basic forms):
Listings Component:
import React from 'react'
import $ from 'jquery'
import CollectionComponent from './CollectionComponent'
import Listing from './Listing'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {
listings: this.props.listings,
}
}
render() {
return (<section className="listing-results">
{this.state.listings.map( listing =>
<CollectionComponent results={this.state.listings} IndividualResult={Listing} perPage={this.props.perPage} options={options}/>
)}
</section>)
}
}
Collection Component:
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
componentDidMount() {
this.$listings = $('.result-card')
$(this.$listings).mouseenter(this.toggleInfoIn).mouseleave(this.toggleInfoOut)
}
toggleInfoIn = e => {
var { target } = e
var infoId = $(target).data('id')
this.setState({hoveredId: infoId})
}
toggleInfoOut = e => {
this.setState({hoveredId: null})
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} result={result} options={options}/>
)}
</div>
)
}
}
Individual Listing Component:
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props)
}
render() {
const { listing, hoveredId } = this.props
return (
<div className="result-card" data-id={listing.id}>
<div className={hoveredId === listing.id ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
I know I can probably structure the CollectionComponent a little cleaner with a higher order component, but I'll leave that for refactoring later once I get it working properly with this basic setup.
Problem
My problem is that every time I hover and change the state of the parent component, it re-renders the child components, because their props are dependent on the parent's state. Once this happens, the reference to my jQuery collection of listings is no longer valid. So the mouse events are attached to old DOM elements that no longer exist.
How can I structure this differently, so that either:
the child elements' props update without re-rendering, or
the jQuery collection reference doesn't change
I'd really like to avoid getting a new the jQuery collection every time the component updates.
The behavior of hover should be confined to the individual listing component and not the Collections component.
As the Collections component maintains the state of currently hovered item, it is good idea to pass an handler as part of props and then render the list again based on the change in state set by the Collections component.
Use react based event handlers where ever necessary which makes it for a controlled component. It is not a good idea to put state in the DOM where react can take care of it for you.
Listings
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}
onMouseEnter() {
this.props.onMouseEnter({ listingId: this.props.listing.id });
}
onMouseLeave() {
this.props.onMouseLeave();
}
render() {
const { listing, hoveredId } = this.props
const listingId = listing.id;
const isHovered = this.props.hoveredId === listing.id;
return (
<div className="result-card" onMouseEnter={this.onMouseEnter} onMouseLeave={onMouseLeave}>
<div className={isHovered ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
Collections
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
onMouseEnter({ listingId }) {
this.setState({ listingId });
}
onMouseLeave() {
this.setState({ listingId: null });
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} hoveredId={this.state.hoveredId} result={result} options={options} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}/>
)}
</div>
)
}
}
I'm really confused now about lifecycle hooks. Here's my code:
App.js:
class App extends Component {
constructor(props){
super(props);
this.state = {
arrayOfComponents: []
}
}
componentDidMount(){
//i get the properties from the server which responds with the database's elements
fetch('http://localhost:3001/')
.then(res => res.json())
.then(arrayOfData => this.setState({arrayOfComponents: arrayOfData}))
.catch(err => console.log(err))
}
render() {
console.log(this.state) //first returns empty array, after the mount returns the db's array of elements
return (
<div className="App">
<Component name='First' id={1} componentsComponents={this.state.arrayOfComponents} />
</div>
);
}
}
Component.js:
class Component extends React.Component{
constructor(props){
super(props);
this.state={
componentsComponents: []
}
}
//here i was tried with componentDidMount, componentWillMount to set the
//this.props.componentsComponents to this.state.componentsComponents
//but it didn't work
renderComponents = () => {
if(this.state.componentsComponents.length){
return this.state.componentsComponents.filter(c => c.inhertedFromId === this.props.id).map(c => {
return <Component name={c.name} id={c.id} componentsComponents={this.props.componentsComponents} />
})
}
}
render(){
return(
<div>
{this.renderComponents()}
</div>
)
}
}
So what i want to do is to the components renders themselves, depending on the array they get from the App.js. But how to set the state before the render happens? Can i somehow ask the component to render again if it did mount? Or any other solutions?
You can simply assign this.props.componentsComponents in constructor itself only.
constructor(props){
super(props);
this.state={
componentsComponents: this.props.componentsComponents||[]
}
}
Bring Filter Up To App
Here it appears you are not calling renderComponents, and you are also trying to render a Component inside itself, which is difficult to reason about. Bring the renderComponents function up to App, and render the data using Component inside of App, and simply pass props down to a stateless Component, which may be a simpler solution to represent the data.
If a recursive call is indeed the best way to represent this data, may need to use getDerivedStateFromProps to move props into state on update, if you wish to store that data in state - something like:
static getDerivedStateFromProps(nextProps) {
return {
componentsComponents: nextProps.componentsComponents
}
}
Added to Component.