I have two commponents. Map and LfMap.
import React, { Component } from 'react';
import { LfMap } from '../../components/Localisation/LfMap';
export class Map extends Component {
constructor(props) {
super(props);
this.state = {
dev: [],
};
}
componentDidMount() {
fetch("URL")
.then(resp => resp.json())
.then(pas => this.setState({dev: pas}));
}
render() {
return(
<React.Fragment>
<LfMap list={this.state.dev}/>
</React.Fragment>
);
}
}
import React from 'react';
import { Map, CircleMarker, TileLayer, Polyline} from "react-leaflet";
import "leaflet/dist/leaflet.css";
import Control from 'react-leaflet-control';
class LfMap extends React.Component {
constructor(props) {
super(props);
this.state = {
devices: this.props.list,
}
}
render() {
return(
<React.Fragment>
<Map
style={{ height: "480px", width: "100%" }}
zoom={12}
center={[22.22, 21.00]}
>
<TileLayer url="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
</Map>
</React.Fragment>
);
}
}
export { LfMap };
And I'm passing a prop list to LfMap with list of my objects.
But when I'm trying to console.log(this.state.devices) it shows Undefined but it should console my list of objets which i fetched in other component. Why it's happening?
Why set state = props ? is an anti-pattern, you should keep using this.prop.list in the other component.
Do this:
console.log(this.props.list)
it will print [], and then when the results come it will print the full array.
When you need to use this array always use this.props.list.
Based in your comment here is how you give solution to that:
At the parent you add a function
listUpdate(newList){
this.setState({
list: newList
})
}
and then you pass this function to the child
<LfMap list={this.state.dev} listUpdate={this.listUpdate}/>
when you need to update it you need to call this.props.listUpdate.
Always update the props :) that's where the reference is, this is the pattern you should follow.
Related
App.js
import React from 'react';
import './App.css'
import Tools from './components/class/Tools'
import Loading from './components/inc/Loading'
export default class App extends React.Component {
componentDidMount() {
Tools.showLoading(); // or new Tools();
}
render() {
return (
<div className="App">
<Loading />
</div>
)
}
}
Loading.js:
import React from 'react'
export default class Loading extends React.Component {
constructor(props) {
super(props)
this.state = {
display: 'none'
}
}
render() {
return (
<div className="loading" style={{display: this.state.display}}>
<span></span>
</div>
)
}
}
Tools.js
export default class Tools extends React.Component {
static showLoading(){ // or non-static
Loading.setState ...
}
}
I want change display state from outside of Loading component.
I use Loading in whole my project and I want create function for handle it.
Example for another use:
function xxx(){
Tools.showLoading(); // or new Tools();
}
Or:
<span onClick={Tools.showLoading(); // or new Tools();}></span>
Actually, I want create only one function to manage and handle display of Loading.
In Tools.js
let loadingStateSetter = null
export function setLoadingStateSetter(setter) {
loadingStateSetter = setter
return () => loadingStateSetter = null
}
export function setLoadingState(value) {
if (loadingStateSetter !== null) loadingStateSetter(value)
}
In Loading.js:
import { setLoadingStateSetter } from './Tools.js'
export default class Loading extends React.Component {
constructor(props) {
super(props)
this.state = {
display: 'none'
}
}
render() {
return (
<div className="loading" style={{display: this.state.display}}>
<span></span>
</div>
)
}
componentDidMount() {
this.removeStateSetter = setLoadStateSetter((value) => {
this.setState((state) => ({
...state,
display: value,
})
})
}
componentWillUnmount() {
this.removeStateSetter()
}
}
Usage:
import { setLoadingState } from './Tools.js'
function xxx(){
setLoadingState('some value')
}
While you can easily expose a setState function externally, it acts just like any other function, its not usually a good idea. You should instead consider rewriting your Loading component to use the property object to tell it if its loading and track the loading state higher up the component tree where it is accessible by things that would want to change its status.
I think you can using redux as store manager global state
https://redux.js.org/
another way pass it through props and handle it at parent component
I'm trying to assign props that I get from parent component and assign it to state in child component(because I want to manipulate the props data I assign it to state first).
When I log the state variable it comes out as an empty array but when I make a new variable in render and assign props to it and log it. It does show the data I need. Also, when I just log this.props I can definitely see that props holds the data I need.
I've assigned props to state a couple of times before, so I'm not sure what is so different this time for it not to work.
Parent component where I pass props to child:
<ShowAvailableTimeslots onClick={this.getSelectedTimeslot} allTimeSlots={this.state.AvailabletimeSlots} />
Child component where I try to assign props to state:
class ShowAvailableTimeslots extends Component {
constructor(props) {
super(props)
this.state = {
sliceEnd: 5,
sliceStart:0,
selectedSlotValue: "",
timeSlotArr: this.props.allTimeSlots,
// timeSlotSlice: timeSlotArr.slice(this.state.sliceStart, this.state.sliceEnd)
}
}
handleTimeSlotClick = (timeSlot) => {
this.setState({ selectedSlotValue: timeSlot }, () => {
this.props.onClick(this.state.selectedSlotValue)
console.log('time slot value', timeSlot)
});
}
previousSlots =()=>{
var test;
}
forwordSlots =()=>{
var test;
}
render() {
var timeSlotArrRender = this.props.allTimeSlots;
return (
<React.Fragment>
{console.log("state", this.state.timeSlotArr)} // --> doesn't show data
{console.log("props", this.props)} // --> does show data
{console.log("render variable", timeSlotArrRender )} // --> does show data
<button className="button btn" onClick={() => this.previousSlots()} disabled={this.state.sliceStart === 0}>left</button>
{/* {this.state.timeSlotArr.map(timeSlot => <a className="timeslot btn " key={timeSlot} value={timeSlot} onClick={() => this.handleTimeSlotClick(timeSlot)}>{timeSlot}</a>)
} */}
<button className="button btn">right</button>
</React.Fragment>
)
}
}
export default ShowAvailableTimeslots
the constructor is called when the component life cycle begins.
You are passing the this.state.AvailabletimeSlots from the parent and by then the constructor have already been called and the assignment for timeSlotArr is already done, so
timeSlotArr: this.props.allTimeSlots // will not work
You have to get help of life cycle methods or hooks
componentWillReceiveProps(nextProps){
this.setState({timeSlotArr: nextProps.allTimeSlots })
}
According to new changes you have to use
static getDerivedStateFromProps(nextProps, prevState){
return {
timeSlotArr: nextProps.allTimeSlots
};
}
I have it working just fine. https://jsfiddle.net/85zc4Lxb/
class App extends React.Component {
render() {
return (<Child passing="I am being passed to child" />);
}
}
class Child extends React.Component {
constructor(props) {
super(props)
this.state = {
passedProp: this.props.passing,
}
}
render() {
return (
<React.Fragment>
<button>{this.state.passedProp}</button>
</React.Fragment>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
I try to recreate the scenario and it work try saving all your files again and then check
parents component
import React, { Component } from "react";
import TestOne from "./Components/TestOne/TestOne";
export class App extends Component {
state = {
x: "x data",
y: "y data",
};
render() {
return (
<div>
<TestOne x={this.state.x} allTimeSlots={this.state.y}/>
</div>
);
}
}
export default App;
Child component
import React, { Component } from "react";
export class TestOne extends Component {
constructor(props) {
super(props);
this.state = {
sliceEnd: 5,
sliceStart: 0,
selectedSlotValue: "",
timeSlotArr: this.props.x,
};
}
render() {
var timeSlotArrRender = this.props.allTimeSlots;
return (
<React.Fragment>
{console.log("state", this.state.timeSlotArr)}
{console.log("props", this.props)}
{console.log("render variable", timeSlotArrRender)}
<button className="button btn">right</button>
</React.Fragment>
);
}
}
export default TestOne;
Result:
I think you are missing this.setState({})
I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!
I am running a client and server setup (react and axios api calls) And I would like to understand how to access the returned data from my child components within the React Framework. I have the connection working to the http server, however i lack the foundational knowledge of working with this.state or props.
here is effectively my app.js
import React, { Component } from 'react';
import axios from 'axios';
import ChildComponent from "./components/childComponent"
class App extends Component {
state = {
data: [],
intervalIsSet : false
};
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 10000);
this.setState({ intervalIsSet: interval });
}
}
getDataFromDb = () => {
fetch("http://localhost:3001/api/getData")
.then(data => data.json())
.then(res => this.setState({ data: res.data }));
};
render() {
const { data } = this.state;
return (
<div>
<childComponent />
</div>
);
};
}
export default App;
and here is the child component. --> my intention is to simply access (or print out) my returned data from the server from within the child component.
import React, { Component } from 'react';
class ChildComponent extends React.Component {
render() {
return (
{this.props.data}
);
}
}
export default ChildComponent;
First make sure you uppercase the first letter of ChildComponent. If you want to pass data you should add that object as an attribute to the component, and then access it throught this.props. Also you need to render a single top element, and if you don't need div or any other html element, you can wrap it with React.Fragment.
Regarding to data, if its an array you can simply map through it and return data you want to see, if you want the entire object to show as a string, you can use JSON.stringify(). And if that's an object you can show only data you want.
class App extends React.Component {
//...
render() {
const { data } = this.state;
return (
<div>
<ChildComponent data={data} />
</div>
);
}
}
//for array, ex: data = ["first","name"];
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
{this.props.data.map(item =>
<p>{item}</p>
)}
</React.Fragment>
);
}
}
//for object, ex: data = {id: 1, name: 'John'};
class ChildComponent extends React.Component {
render() {
const {data} = this.props;
return (
<React.Fragment>
<p>{data.id}</p>
<p>{data.name}</p>
</React.Fragment>
);
}
}
//for single value (string, int, etc), ex: data = "my name";
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
<p>{this.props.data}</p>
</React.Fragment>
);
}
}
//to show object as a string (could be any object mentioned before)
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
{JSON.stringify(this.props.data)}
</React.Fragment>
);
}
}
You can pass down the data array as the data prop to the child component. Make sure you also uppercase the first character in the component name, or it won't work properly.
A component needs to render a single top level element, so you could e.g. render a div with the JSON stringified data prop inside of it in the child component.
class App extends React.Component {
// ...
render() {
const { data } = this.state;
return (
<div>
<ChildComponent data={data} />
</div>
);
}
}
class ChildComponent extends React.Component {
render() {
return <div>{JSON.stringify(this.props.data)}</div>;
}
}
I am not able to dynamically AJAX loaded HTML format data(as mentioned below) in dangerouslySetInnerHTML(ie., it is setting in DOM as the same string and not compiling). Please note that I am using axios plugin for AJAX API calls. When I try with the same HTML data giving statically in dangerouslySetInnerHTML, it is working perfectly. I don't know what is going there.
[{id: 1, image: "assets/img/news-sm.png", content: "<p>சிறுவனே!</p>"}]
My React Component code is
import React, { Component, PropTypes } from 'react';
import List from 'material-ui/List/List';
import ListItem from 'material-ui/List/ListItem';
import Divider from 'material-ui/Divider';
import { Media } from 'react-bootstrap';
import { map } from 'lodash';
class NewsList extends Component {
constructor(props) {
super(props);
}
handleListClick(d){
console.log(d);
}
renderLoadingComponent() {
return <ListItem style={{textAlign: 'center'}}>Loading...</ListItem>;
}
renderNothingFoundElement() {
return <ListItem style={{textAlign: 'center'}}>Nothing found</ListItem>;
}
render() {
const { data, loading } = this.props;
return (
<List className="list">
{
map(data, (d, index) =>
React.Children.toArray([
<ListItem className="list-item">
<Media onClick={(ev) => this.handleListClick(d)} className="media-align">
<Media.Left align="middle">
<img width={75} height={56} src={d.image} alt="news-image"/>
</Media.Left>
<Media.Body>
{d.content && <p dangerouslySetInnerHTML={{ __html: d.content }}></p>}
</Media.Body>
</Media>
</ListItem>,
(index + 1) !== data.length ? <Divider /> : ''
])
)
}
{ loading ? this.renderLoadingComponent() : data.length === 0 ? this.renderNothingFoundElement() : '' }
</List>
)
}
}
NewsList.propTypes = {
data: React.PropTypes.array.isRequired
};
export default NewsList;
I am calling the above Component from another Component like this
import React, { Component, PropTypes } from 'react';
import NewsList from '../components/news-list';
class Home extends Component {
constructor(props) {
super(props);
this.state = { ajaxData: [], ajaxLoading: false }
}
componentDidMount() {
axios.get('/ajaxUrl')
.then(response => this.setState({ajaxData: response.data}))
}
render() {
return (
<NewsList data={this.state.ajaxData} loading={this.state.ajaxLoading} />
)
}
}
Can someone please help me to solve this issue? Thanks.
Probably you have to specify The Component Lifecycle shouldComponentUpdate or forceUpdate or componentWillReceiveProps facebook.github.io
But you can also use a huck and set state in your Parent component to '' and on a callback set the NewList and pass props
this.setState({newList: ''},()=> this.setState({ newList: <NewList {...props}/>}))
Update The simple one
class Home extends Component {
constructor(props) {
super(props);
this.state = { ajaxData: [], ajaxLoading: false, newsList:'' }
}
componentDidMount() {
axios.get('/ajaxUrl')
.then(response => this.setState({ajaxData: response.data},
()=> this.setState({newsList: <NewsList data={this.state.ajaxData} loading={this.state.ajaxLoading} />})))
}
render() {
return (
<div> {this.state.newsList} <div/>
)
}}
Also you can implement method for a life cycle
shouldComponentUpdate(nextProps, nextState) {
return this.state.ajaxData != nextState.ajaxData;
}