After a post I would like to reload my table to be able to display the data after the post. Now the question arises how to get my "DataProvider" to render again?
I would do this as a function call in "FormOPCConnect". But I don't know how to start. I already tried to use the "props" of the "DataProvider", but I can't figure out how to render the new table.
Enclosed my source code.
TableOPCConnections.js
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
import Table from 'react-bootstrap/Table'
const OPCVarTable = ({ data }) =>
!data.length ? (
<p>Nothing to show</p>
) : (
<div>
<h2 className="subtitle">
Showing <strong>{data.length}</strong> OPC Variables
</h2>
<Table striped bordered hover>
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</Table>
</div>
);
OPCVarTable.propTypes = {
data: PropTypes.array.isRequired
};
export default OPCVarTable;
DataProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
FormOPCConnect.js
(Here I'd like to refresh the state of the DataProvider)
After the fetch method I would like to render the table again as long as the post to the database was successful.
import React, { Component, useState } from "react";
import PropTypes from "prop-types";
import Button from "react-bootstrap/Button";
import Form from 'react-bootstrap/Form'
import DataProvider from "./DataProvider";
import csrftoken from './csrftoken';
class FormOPCConnect extends Component {
constructor(props) {
super(props);
this.state = {
validated: false
};
}
static propTypes = {
endpoint: PropTypes.string.isRequired
};
state = {
ip_address: "",
port: "",
namespace_name: "",
root_name: "",
owner: ""
};
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = event => {
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
event.preventDefault();
const { ip_address, port, namespace_name, root_name, owner } = this.state;
const opcConn= { ip_address, port, namespace_name, root_name, owner };
const conf = {
method: "post",
body: JSON.stringify(opcConn),
headers: new Headers({ "Content-Type": "application/json", "X-CSRFTOKEN": csrftoken })
}
fetch(this.props.endpoint, conf).then(response => console.log(response));
//>>
//if response is valid -> refresh the Dataprovider and the table...
//<<
this.setState({ validated: this.state.validated = true })
};
App.js
const App = () => (
<React.Fragment>
<Container>
<Row>
<Col> <NavBarTop fixed="top" /> </Col>
</Row>
<Row>
<Col> <DataProvider endpoint="opcconnection/"
render={data => <OPCVarTable data={data} />} /></Col>
<Col><FormOPCConnect endpoint="opcconnection/" /></Col>
</Row>
</Container>
</React.Fragment>
);
const wrapper = document.getElementById("app");
wrapper ? ReactDOM.render(<App />, wrapper) : null;
I'm new to React, so please forgive my mistakes. :D
Finally it looks like this.
OPCConnection_Image
Your code currently contains 2 problems that need to be fixed in order to update your table when you post data.
1)
Your DataProvider does actually rerender on props change. The problem here is that your logic to fetch your data is in componentDidMount. componentDidMount only triggers the first time the component mounts and doesn't trigger on rerender.
If you want your data to fetch everytime the component rerenders you could place your fetch functionality in your render method of DataProvider.
To rerender a component all you have to do is update its props or it's state.
2) You want your DataProvider to update when your FormOPCConnect has finished some logic.
The thing with React is. You can only pass variables from parents to children. You can't directly communicate from sibling to sibling or from child to parent.
In your App your DataProvider is a siblign of FormOPCConnect, they are next to eachother.
<App> // App can communicate with every component inside it.
<DataProvider /> // This component can't communicate with the component next to it.
<FormOPCConnect />
</App>
The easiest thing to do here would be to either render DataProvider inside FormOPCConnect and update DataProvider's props directly.
Or if that is not possible, keep a state in App which checks if your logic in FormOPCConnect has finished.
class App extends Component {
constructor(props) {
super(props);
this.state = { boolean: false }; //state is remembered when a component updates.
}
flipBoolean() { //this function updates the state and rerenders App when called.
this.setState(
boolean: boolean!
);
};
render {
return (
<Fragment>
<DataProvider />
<FormOPCConnect flipBoolean={this.flipBoolean} />
</Fragment>
)
}
}
Pass a function to FormOPCConnect which updates the state of App. When you want your DataProvider to rerender you simply call that flipBoolean function in FormOPCConnect. This will update the state in App. Which will trigger App to rerender. Which will in it's turn rerender it's children DataProvider andFormOPCConnect`.
(This variable doesn't need to be a boolean, you can do here whatever you want. This boolean is just an example).
Related
I am having a question about how to implement a callback function. In my case, I have a React app with this structure: App > Child > Button components
The problem is I do not know how to write a callback function from Button to Child
I would like to update a value in Child (e.g: inputFromButton) after clicking the button in Button Component. The handleClick() is triggered and a value will be sent to the Child component.
Could someone help me to do this?
Here is my code:https://codesandbox.io/s/nifty-stonebraker-0950w8
The App component
import React from 'react';
import Child from './Child';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: 'Data from App'
}
}
handleCallback = (childData) => {
this.setState({ data: childData })
}
render() {
const { data } = this.state;
return (
<div>
<Child dataFromApp={data} />
</div>
)
}
}
export default App
The Child component
import React from 'react';
import { renderButton } from './Button';
class Child extends React.Component {
state = {
inputFromApp: "",
inputFromButton: ""
}
componentDidMount() {
this.setState({
inputFromApp: this.props.dataFromApp
})
}
render() {
const renderButtonItem = renderButton(this.props);
const inputFromApp = this.state.inputFromApp
const inputFromButton= this.state.inputFromButton
return (
<div>
<input value={inputFromApp}></input>
<br></br>
<input value={inputFromButton}></input>
<div>{renderButtonItem}</div>
</div>
)
}
}
export default Child
The Button component
import React from 'react';
export const renderButton = (props) => {
const handleClick = () => {
console.log('handleClick() props data from App: ' + props.dataFromApp)
}
return (
<button onClick={handleClick}>Click</button>
)
}
renderButton is a function component and, therefore, needs to be in PascalCase: RenderButton (although it would be better off as Button).
Move handleClick to the Child component.
Then in Button the call to handleClick should be props.handleClick since handleClick will now be a property of the props object passed into the component. We don't need to pass down the data as a prop to the button but can, instead just log the data prop passed into Child.
handleClick = () => {
console.log(`handleClick(): ${props.dataFromApp}`);
}
In Child, instead of calling renderButton, import Button, and then use that in the render passing down the handler in the props. By doing this you're making the component as "dumb" as possible so it can be reused elsewhere in the application.
<Button handleClick={this.handleClick} />
So I´m having this issue with my react class component.
Since it´s handling "a lot" of state, I don´t want to use a functional component bc I think it is more readable like so.
A few layers down from my class component I have a features component, which dispatches selected features from checkboxes to the redux state.
My class component needs to know these changes and re-renders based on the selected features, stored in the redux store.
The problem is, it always gets the new props from the mapStaeToProps function, but I can´t setState(...) based on the new props, bc componentDidMount only runs once at the beginning or componentWillReceiveProps() doesn´t exist anymore.
import { connect } from 'react-redux';
import TaggingComponent from '../../../content-components/TaggingComponent';
import SupplyAmount from '../../../content-components/SupplyAmount';
import FeaturesComponent from '../../../content-components/FeaturesComponent';
import './TokenGenerator.css';
const features = {
Features: [
'Mintable',
'Burnable',
'Pausable',
'Permit',
'Votes',
'Flash Minting',
'Snapchots'
],
'Access Control': ['Ownable', 'Roles'],
Upgradeability: ['Transparent', 'UUPS']
};
class TokenGenerator extends React.Component {
constructor(props) {
super(props);
this.state = {
tokenName: '',
shortName: '',
supplyAmount: null,
selectedFeatures: this.props.selectedFeatures
};
}
static getDerivedStateFromProps(props, current_state) {
if (current_state.selectedFeatures !== props.selectedFeatures) {
return {
selectedFeatures: props.selectedFeatures
};
}
return null;
}
setTokenName = (e) => {
this.setState({ tokenName: e.target.value });
};
setShortageName = (e) => {
this.setState({ shortName: e.target.value });
};
setAmount = (e) => {
this.setState({ supplyAmount: e.target.value });
};
render() {
return (
<div className="grid grid-cols-7 gap-10">
{/* Name and shortage input */}
<TaggingComponent
setTokenName={this.setTokenName}
setShortageName={this.setShortageName}
/>
{/* Token supply */}
<SupplyAmount setAmount={this.setAmount} />
{/* Tokenfeatures */}
<FeaturesComponent features={features} />
{/* Selected Features listing */}
<div className="card-bg border-2 border-white text-white p-11 rounded col-span-5 row-span-5">
<h4 className="text-center">Selected Features</h4>
{this.state.selectedFeatures.map((feature) => {
return <p key={feature}>{feature}</p>;
})}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log(state);
return { selectedFeatures: state.tokenGeneratorState.selectedFeatures };
};
export default connect(mapStateToProps)(TokenGenerator);
Is the new getDerivedStateFromProps method the only way to update state vom changing props?
Cheers
You don't need to set state for each prop change. Because the component which receives props will re-render automatically whenever any of it's props changes.
Take this as example,
class ComponentA {
render() {
<ComponentB someProp={someDynamicallyChangingValue} />
}
}
class ComponentB {
render() {
<div>{this.props.someProp}</div>
}
}
Here, the ComponentB will be automatically re-rendered whenever someProp is changed in ComponentA.
So, i am trying to pass data from child component to parent component. I was able to do this through using props and is able to print the data using console.log. However, when i try to setState in the parent component using this data from child component. I am stuck in infinite loop of updating state which caused error "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."
My code is of below
Parent component
import React, { Component } from 'react'
import TagsPaper from './tagsPaper'
import CurrentTagPaper from './currentTagPaper'
import ApiCall from './Backend/apiCall';
export default class Tags extends Component {
constructor(props) {
super(props);
this.state = {
tags: ["apple"],
toUpdate: true,
};
}
componentDidMount() {
}
getTags = (e) => {
const array = e;
console.log(array)
this.setTags(array)
return array
}
setTags(e) {
this.setState({
tags: e
})
}
render() {
return (
<div >
<p>
Get recommendations for articles that matches your
interests when you follow more tags:
</p>
<b>
You are following
</b>
<TagsPaper></TagsPaper>
<b>
From this article
</b>
{/* pass Tag array name here*/}
<CurrentTagPaper tagArray={this.state.tags} />
<ApiCall getTags={this.getTags} />
</div>
)
}
}
Child component
import React, { Component } from 'react';
import '../App.css';
export default class ApiCall extends Component {
constructor(props) {
super(props);
this.state = {
response: '',
post: '',
responseToPost: '',
tags: this.props.tags
};
this.sendTags = this.sendTags.bind(this);
}
componentDidMount() {
this.getTagApi()
}
getTagApi(){
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('http://localhost:5000/api/scrap');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
sendTags = (value) => {
this.props.getTags(value);
}
handleSubmit = async e => {
e.preventDefault();
const response = await fetch('http://localhost:5000/api/forms', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ post: this.state.post }),
});
const body = await response.text();
this.setState({ responseToPost: body });
};
render() {
let tagArray = [...this.state.response];
this.sendTags(tagArray);
return (
<div className="App">
{/* {tagArray.map(e => <p key={e}>{e}</p>)} */}
<form onSubmit={this.handleSubmit}>
<p>
<strong>Post to Server:</strong>
</p>
<input
type="text"
value={this.state.post}
onChange={e => this.setState({ post: e.target.value })}
/>
<button type="submit">Submit</button>
</form>
<p>{this.state.responseToPost}</p>
</div>
);
}
}
When the parent changes state, it renders the child. When the child renders, it updates the parent state. There would be a few clever ways to fix this, but your ultimate problem is that copying data into both a parent and child's state isn't very idiomatic.
Generally, you should "hoist" any state used by both parent and child to only exist in the parent, and this "hoisting" should happen right off the API call.
In ApiCall, you could switch to
getTagApi(){
this.callApi()
.then(res => this.sendTags(res.express))
.catch(err => console.log(err));
}
In your parent Tags component, you would need to pass the parents tag state back down.
<ApiCall getTags={this.getTags} tags={this.state.tags} />
Your ApiTags would then just use the parent's tags.
let tagArray = this.props.tags;
You also would not want to copy this.props.tags into this.state during construction.
React expects that there be one source-of-truth for all data. If a higher-level component needs the data as well as a lower-level component, you should always hoist the data, as in, pass it up whenever it changes, and pass it back down as a prop on every render.
I'm currently learning React/hooks/redux. To do so, I'm building a react app that takes in data from a climate API.
The problem I'm having is correctly setting state for a couple of items in useEffect. One state relies on the other, so I'm trying to figure out how to properly call useEffect so I don't get infinite loops and follow best-practices.
A little background before the code included below:
-The user creates a project, and selects a city. This produces a cityId that I'm storing in my "project" state.
-On the user's dashboard, they can click a project that sends the project ID in a queryString to my ClimateData component.
-ClimateData passes the project ID queryString to the "getProjectByID" redux action to get the project state, including it's cityId.
-ClimateData includes the IndicatorList component, which brings in a list of all the climate data breakouts. I want the user to click one of these list items and have ClimateData's "indicatorByCityData" state set. So I passed ClimateData's setState function to IndicatorList and have the list call with onClicks. Is there a better way I should do this?
-On ClimateData, once I have the project's cityId, and the selected item from IndicatorList, I need to call "getIndicatorByCity" and pass both the cityId and indicator to have the result saved in the "indicatorByCityData" state
I keep trying to change how my ClimateData's useEffect is written, but I'm either getting infinite loops or errors. How can I best change this to set both states and follow best practices?
The redux actions and reducers have been tested elsewhere and work fine, so for brevity, I'll exclude them here and just focus on my ClimateData and IndicatorList components:
import React, { Fragment, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import IndicatorList from './IndicatorList';
import Spinner from '../layout/Spinner';
import { getProjectById } from '../../actions/projects';
import { getIndicatorByCity } from '../../actions/climate';
const ClimateData = ({
getProjectById,
getIndicatorByCity,
project: { project, loading },
auth,
match
}) => {
const [indicatorByCityData, setIndicatorByCityData] = useState({});
const nullProject = !project;
useEffect(() => {
if (!project) getProjectById(match.params.id);
// Once we have the cityID, set the indicatorByCityData state, with a default selected Indicator
if (!loading) setIndicatorByCityData(getIndicatorByCity(project.cityId));
}, []);
// Get the selected indicator from IndicatorList and update the indicatorByCityData state
const setIndicator = indicator => {
setIndicatorByCityData(getIndicatorByCity(project.cityId, null, indicator));
};
return (
<Fragment>
{project === null || loading || !indicatorByCityData ? (
<Spinner />
) : (
<Fragment>
<Link to='/dashboard' className='btn btn-light'>
Back To Dashboard
</Link>
<h1 className='large text-primary'>{`Climate Data for ${project.city}`}</h1>
<IndicatorList setIndicator={setIndicator} />
</Fragment>
)}
</Fragment>
);
};
ClimateData.propTypes = {
getProjectById: PropTypes.func.isRequired,
getIndicatorByCity: PropTypes.func.isRequired,
project: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
project: state.projects,
auth: state.auth
});
export default connect(mapStateToProps, { getProjectById, getIndicatorByCity })(
ClimateData
);
/******************************************************************/
import React, { useEffect, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Spinner from '../layout/Spinner';
import { getIndicatorList } from '../../actions/climate';
const IndicatorList = ({
getIndicatorList,
auth: { user },
climateList: { indicatorList, loading },
setIndicator
}) => {
useEffect(() => {
getIndicatorList();
}, [getIndicatorList]);
return loading ? (
<Spinner />
) : (
<Fragment>
{indicatorList.length > 0 ? (
<Fragment>
<ul>
{indicatorList.map(indicator => (
<li key={indicator.name}>
<a href='#!' onClick={() => setIndicator(indicator.name)}>
{indicator.label}
</a>
<br />- {indicator.description}
</li>
))}
</ul>
</Fragment>
) : (
<h4>No climate indicators loaded</h4>
)}
</Fragment>
);
};
IndicatorList.propTypes = {
auth: PropTypes.object.isRequired,
climateList: PropTypes.object.isRequired,
setIndicator: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
climateList: state.climate
});
export default connect(mapStateToProps, { getIndicatorList })(IndicatorList);
I have a React APP which displays images perfectly fine from the Flickr API based on a 'dogs' query which is built into the code--this is container which accomplishes this:
import React, { Component } from 'react';
import axios from 'axios';
import apiKey from './config.js';
import Navigation from './Navigation.js';
import Photos from './Photos.js';
import SearchBar from './SearchBar.js';
class Container extends Component {
constructor (){
super();
this.state = {
imgs:[],
query: '',
loading: true
}
}
componentDidMount() {
this.performSearch();
}
performSearch = (query = 'dogs') => {
axios.get(`https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&format=json&nojsoncallback=1`)
.then(response => {
this.setState({
imgs: response.data.photos.photo,
loading: false
});
})
.catch(err => {
console.log('Error happened during fetching!', err);
});
}
render() {
return (
<div className="container">
<SearchBar onSearch={this.performSearch}/>
<Navigation />
{
(this.state.loading)
? <p>Loading....</p>
: <Photos data={this.state.imgs}/>
}
</div>
);
}
}
export default Container;
I have a nav bar with three different links--and I would like to call the Container component for each-- passing a specific value for the query in the API.
So for example, if my links are bears, cats, and mice, I would like the respective link to generate the correct api based on the query term.
I am just uncertain as to how I pass the query term utilizing the Container component. Go easy, as I'm a bit new to react, but I was trying something like
<Container query="bears" />
or
<Container performSearch("bears") />
But I can't seem to figure out how to pass the appropriate value.
It looks like you want to pass the query in as a prop. I would begin by updating the constructor to receive props, pass them to super(), and use them to set the value of the state's query property:
constructor(props) {
super(props);
this.state = {
imgs: [],
query: this.props.query,
loading: true
}
}
Next, update the performSearch() method to use the state's query property instead of the hard-coded 'dogs' value:
performSearch = (query = this.state.query) => {
Now you can pass the appropriate query property to the Container like you were thinking:
<Container query="bears" />
<Container query="cats" />
<Container query="mice" />