I'm trying to make my context work properly.
I've added it and it works as it should if I pass a string as a property to the state. However, I want to pass a prop as the state.
So this works:
export class DataProvider extends Component {
constructor(props) {
super(props);
this.state = {
continent: props.continent,
};
this.updateState = this.updateState.bind(this);
}
updateState() {
this.setState({ continent: this.props.continent});
}
componentDidMount() {
console.log(this.props.continent);
this.updateState();
}
render() {
return (
<DataContext.Provider value={{ state: this.state }}>
{this.props.children}
</DataContext.Provider>
);
}
}
But this does not work (results in undefined)
this.state = {
continent: this.props.continent,
};
Results in "undefined" when I try to access it.
I get the prop from a component named "Africa", which does this:
const Africa = ({}) => {
return (
<div>
<DataProvider continent={["Africa"]} />
........irrelevant code
It successfully passes to my DataProvider component.
But, as I stated, when I try to pass that as a property for my state, it results in "undefined".
class JumbotronPage extends Component {
static contextType = DataContext;
render() {
console.log(this.context)
A(DataProvider), B(Africa), C(JumbotronPage)
I'm not sure if it's because A and B recognizes each other.
B and C does not.
So whenever I access C from A, B gets re-rendered, resulting in giving C nothing as state. Does that make sense?
Please, forgive me for being very green and new to React. I hope I make some sense.
Thanks
Edit:
setState did not seem to work properly. I threw it into a componentDidMount, I can now set string-states, however, as soon as I pass my props to it, it's undefined.
Edit2:
This is part of my App.js:
<Route exact path="/Login" component={Login} />
<Route exact path="/Jumbotron">
<DataProvider>
<JumbotronPage />
</DataProvider>
</Route>
<Route exact path="/CreateNewMemories" component={renderForm} />
Edit3:
I created a gist, if someone has the time an patience to have a look at my abomination.
https://gist.github.com/kalleftw/e79412034eafd29a2e26b1af24149e67
Edi4:
Do I even need to set the state?
this.state = {
continent: props.continent,
};
This seems to work when I log it with componentDidMount.
However, as soon as I try to access the component "Jumbotron", the context there is undefined.
The correct way to update the state is with setState, right, but it is a function. Use this.setState({ ... }) not this.setState = { ... }
Related
In App.js, I am passing setURL(page){ ... } as a prop to HealthForm.
In HealthForm, I have an input field that takes a String of an URL and a button that initiates a fetch call to my backend server and some data is received back in a promise object. I also call that.props.changeUrl(that.state.someURL);inside the promiseStatus function because that's the only place I could place it without getting the following warning:
Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
However, every time that that.props.changeUrl(that.state.someURL) is called, the page re-renders. Basically -- the input field and the additional functions that were rendered due to the fetch call -- all reset. The url state in App.js gets updated though.
Why does the whole page re-renders when I'm calling the parent props?
The app does not re-render if the line that.props.changeUrl(that.state.someURL) is simply deleted but of-course it doesn't change the App state
I need the page to not re-render because vital information is rendered after the fetch call which cannot be seen since the re-render resets that route.
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
url: '',
};
this.setURL = this.setURL.bind(this);
}
setURL(link) {
this.setState({
url: link
});
}
render(){
return(
<MuiThemeProvider>
<Router>
<div className="App">
<Route path="/" component={Header}></Route>
<Route path="/health" component={()=>(
<HealthForm changeUrl={this.setURL}/>)}></Route>
<Route path="/path1" component={wForm}></Route>
<Route path="/path2" component={xForm}></Route>
<Route path="/path3" component={yForm}></Route>
<Route path="/path4" component={zForm}></Route>
</div>
</Router>
</MuiThemeProvider>
);
}
}
HealthForm.js
class HealthForm extends React.Component {
constructor(props) {
super(props);
this.state = {
exampleURL: '',
exampleURLError: '',
status: '',
showStatus: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
validate = () => {
//…checks for input errors
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
const err = this.validate();
let that = this;
if (!err) {
this.setState({
exampleURLError: ''
});
console.log(this.state);
var data = this.state.exampleURL
fetch('htpp://...', {
method: 'POST',
body: JSON.stringify(data)
})
.then((result) => {
var promiseStatus = result.text();
promiseStatus.then(function (value) {
that.setState({
status: value,
showStatus: true
});
that.props.changeUrl(that.state.jarvisURL);
});
}).catch((error) => {
console.log(error);
});
}
}
render() {
return (
<form>
<TextField
...
/>
<br/>
<Button variant="contained" size="small" color="primary" onClick={e => this.handleSubmit(e)} >
Check
</Button>
<br /> <br />
...
</form>
);
}
}
export default HealthForm;
This is happening because you're calling setState() on the App component, causing it to re-render, including re-creating all the routes you've set up. I'm not sure which router you're using exactly but it seems that it is recreating the components under the routes, probably by calling the component function that's passed in as a prop again and getting a new instance of your HealthForm component.
I assume the state you're storing inside App is required by all components in the application and that's why you're putting it there? If not, move it down into the HealthForm component, but if so maybe it's time to think about storing state externally to your components, e.g. in a state container like Redux or something else in a Flux style.
EDIT: I think the root of your problem is here:
<Route path="/health" component={()=>(<HealthForm changeUrl={this.setURL}/>)}></Route>
In the fact that a function is passed as the component prop, resulting in a new instance of the component each time. I can see why you needed to do that, to get the reference to setURL() passed into the HealthForm - it's also something that could be avoided by extracting the state out of the component.
I'm having issues passing a prop to a componentDidMount() call in a child component on my React application.
In my App.js I am passing props via Router like below:
App.js
class App extends Component {
state = {
city: ""
}
componentDidMount() {
this.setState({city: this.props.city});
}
render() {
return (
<div>
<Route path="/" exact render = {() => <Projections city={this.state.city} />} />
<Route path="/:id" component={FullPage} />
</div>
);
}
}
In my Projections.js I have the following:
Projections.js
constructor(props) {
super(props);
this.state = {
location: this.props.city
}
}
componentDidMount () {
console.log(this.state.location);
console.log(this.props.city);
}
console.log(this.state);' returns an empty string.console.log(this.props.city);` returns an empty string as well.
But I need to access the value of the city prop within componentDidMount(). console.log(this.props.city); within render() returns the prop, but not in componentDidMount()
Why is this and how do I return props within componentDidMount()?
In the constructor you should reference props, not this.props:
location: props.city
<Route path="/" exact render = {() => <Projections city={this.state.city} {...this.props} />} />
Try passing rest of props in route
this is because you assigned props in constructor that time it may or may not receive actual value. And it gets called only once in a component lifecycle.
You can use componentWillReceiveProps to get props whenever it receive and update state accordingly.
Inside Projections.js
UNSAFE_componentWillReceiveProps(nextProps){
if(nextProps.city){
this.setState({location:nextProps.city})
}
}
Here is working codesand
I've got a parent component with react-router, setup like this :
constructor(props){
super(props);
this.state = {
diner: false
};
this.updateFromInvite = this.updateFromInvite.bind(this);
}
updateFromInvite(Souper) {
this.setState({diner: Souper});
}
I can't figure out how to setup the route to have both URL parameters and be able to pass a function to update the parent's state from the children component...
<Route path="/Invitation/:NomParam1?/:NomParam2?"
component = {() => (<Invitation updateApp = {this.updateFromInvite} />)} />
I think it's the closest I got...
From children's component :
class Invite extends Component {
constructor(props){
super(props);
this.state = {
diner: this.props.match.params.NomParam1 ,
JSONInfo: this.props.match.params.NomParam2
};
}
componentDidMount() {
const { diner } = this.state;
const { JSONInfo } = this.state;
const { updateApp } = this.props;
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
export default Invite;
The component property of the route takes a component Class, not an instance of the component. I believe you are looking to use the render property, which takes a rendered component. Your visual component shouldn't be concerned with the routing details, so you can pass that in in the Route configuration like so:
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={({match}) => (
<Invitation
updateApp={this.updateFromInvite}
diner={match.params.NomParam1}
JSONInfo={match.params.NomParam2}
/>
)}
/>
Then, in the component, don't utilize state, as that's not really what it is for:
class Invite extends Component {
componentDidMount() {
const { diner, JSONInfo, updateApp } = this.props;
// Not exactly sure what is going on here... how you
// will use JSONInfo, etc
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
Also, I'm not exactly sure what the parent component is doing, and why it is passing both the route params and the function down to the child, only to have the child call it back... but that is probably out of the scope of the question.
Enjoy!
If finally got it (thanks to that answer and the official documentation):
I needed to add props as parameter of my render and
use it with {...props} inside the children element!
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={ (props) =>
(<Invitation updateApp = {this.updateFromInvite} {...props} />)
}
/>
With that, I have access to BOTH :
my custom props
generic props (match, location and history)
How I think/understood was that react components update whenever their props or state change.
So I declare my variable:
let percentage = {
width: '10%',
};
and have a setInterval function running to change that variable after so long:
setInterval(function() {
percentage = {
width: '50%',
};
}, 5000);
and below this I render my component:
Meteor.startup(() => {
render((
<Router>
<div>
<Nav />
<Route exact path="/" render={() => <Start percentage={percentage} />} />
<Route path="/app" component={App} />
<Modal />
</div>
</Router>
), document.getElementById('render-target'));
});
Where I display the percentage in another file that looks like:
export default class Start extends Component {
render() {
return (
<div className="home">
<div className="meter orange">
<span style={this.props.percentage} />
</div>
</div>
);
}
}
My component never updates though, I have put a console.log into the setInterval function and get the update variable back but it never refreshes my component.
Have I misunderstood how props updates work?
The parameters passed to a component are copied by value, not reference. So when you render the outermost component, you're passing the current value of percentage into the Start component:
<Start percentage={percentage} />
From the perspective of the Start component, its property never changes, even though the variable that provided its initial value is.
You can't be clever and try to get around this with an object that contains a property percentage either...because the object (the parameter itself) won't change, only its properties.
So what's a poor programmer to do?
It's a bit misleading to say that a component updates when its properties change; components actually update when they're re-rendered. Very often, this happens because the enclosing (parent) component's state changes (or its been re-rendered) and it will be passing new props down to the inner component. The solution in your case is to make percentage part of the state of the enclosing component. So you would have something like this:
class Parent extends Component {
constructor(props, ...args) {
super(props, ...args)
this.state = { percentage: { width: '0%' } }
setInterval(() => this.setState({ percentage: { width: '50%' } }), 5000)
}
render() {
return <Start percentage={this.state.percentage} />
}
}
It's technically correct that a component updates when its props change; however, the only way to change its props is to re-render it! Props are read-only inside a component. Which is why I say it's misleading (or incomplete) to think about prop changes driving component re-rendering.
I have a PageBuilder component that dynamically builds edit/list pages according to a configuration file. I want to have dynamic routes (like "/collection/list", "/collection/edit/123123", "/dashboard", etc.) that use the same PageBuilder component.
I'm having trouble getting this to work - if I'm in "/collection/list" for example, when clicking on a link to "/collection/edit/1231" doesn't work. Only a refresh to that URL works (and vice-versa).
I tried putting my initialization code PageBuilder's componentWilLReceiveProps but it seems to call it every second.
My routes look like this:
<Route path="/" component={App}>
<IndexRedirect to="/dashboard" />
<Route path="/:page/:collection/:action(/:entity_id)" component={PageBuilder} />
<Route path="/:page" component={PageBuilder} />
</Route>
And my PageBuilder:
constructor(props) {
super(props);
this.createSectionsHTML = this.createSectionsHTML.bind(this);
this.onChange = this.onChange.bind(this);
this.onSave = this.onSave.bind(this);
}
getPageName() {
return this.props.params.page.replace(/-/g, '_').toLowerCase();
}
componentWillReceiveProps(props) {
this.action = this.props.params.action;
}
componentWillMount() {
let pageName = this.getPageName();
this.props.dispatch(setInitialItem(pageName));
}
componentDidMount() {
let pageName = this.getPageName();
let { collection, entity_id } = this.props.params;
if (collection && entity_id) {
let { dispatch } = this.props;
dispatch(getCollectionEntity(collection, entity_id, pageName));
}
}
Any ideas of how to re-render the page each time I redirect to a different route?
It would be great if I could unmount and re-mount the component when redirecting, but I'm not sure how to go about telling React Router to do that....
Thanks!
Make this.state such that it will control how your component gets rendered.
Now, within componentWillReceiveProps, check the nextProps argument
componentWillReceiveProps(nextProps, nextState) {
if( <check in nextProps if my route has changed> ) {
let newState = Object.assign({}, this.state);
// make necessary changes to the nextState Object by calling
// functions which would change the rendering of the current page
this.setState({ nextState });
}
}
This would make componentWillReceiveProps take action only when the route changes.
Now in your render function,
render() {
const { necessary, variables, to, render } = this.state;
let renderVariables = this.utilityFunctionsReqToRender(someArgs);
return (
<toRenderJsx>
...
</toRenderJsx>
)
}
This would make your component "refresh" whenever the route changes.
componentWillReceiveProps is deprecated since React 16.3.0
(as says a warning in the browser console)
Reference :
https://reactjs.org/docs/react-component.html#componentdidupdate
So componentDidUpdate can be used to get the new state and reload data depending on params
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate " +prevState.id);
reloadData(prevState.id);
}