I manage to pass context through children but only once. Context is never updated.
Yet I have seen many examples working like that, including react docs: https://facebook.github.io/react/docs/context.html
Here is my code:
Parent Component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
window:{
height:null,
width:null
}
};
}
getChildContext() {
return {
window: this.state.window
}
}
componentDidMount () {
window.addEventListener('resize', this.handleResize.bind(this));
this.handleResize();
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize.bind(this));
}
handleResize (){
this.setState({
window:{
width:window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth,
height:window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight
}
});
}
render() {
console.log(this.state.window);
// --> working
return (
{this.props.children}
);
}
}
App.propTypes = {
children: React.PropTypes.node.isRequired
};
App.childContextTypes = {
window: React.PropTypes.object
}
export default App;
Child Component:
class Child extends React.Component {
constructor(props, context) {
super(props);
this.state = {};
}
render () {
console.log(this.context.window);
// --> passed on first render, but never updated
return (
...
)
}
}
Child.contextTypes = {
window: React.PropTypes.object.isRequired
};
export default Child
Am i missing something?
Related
I have a state counter in my main App.js class. Also I have a Countdown.js, which updates the counter of his parent class every time he has finished. But i get an Error, when the timer finished once. Also, state counter jumps from 0 to 2 and not from 0 to 1...
Warning: Cannot update during an existing state transition (such as within `render`).
How can i get rid of this error? Or do you have a solution how to count++, when the timer is finished?
My class App.js:
import React from "react"
import "./App.css"
import Countdown from "./Countdown.js"
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 0
};
this.count = this.count.bind(this);
}
count() {
this.setState(prevState => ({
count: prevState.counter++
}));
}
render() {
return (
<div className="window">
<p>{this.state.counter}</p>
<Countdown count={this.count} />
</div>
);
}
}
export default App
My Countdown.js
import React from "react";
import CountDown from "react-countdown";
class CountdownQuestion extends React.Component {
constructor() {
super();
this.state = {
time: 3000
};
}
render() {
const renderer = ({ seconds, completed }) => {
if (completed) {
this.props.count();
return <h2>Zeit abgelaufen</h2>;
} else {
return <h3>{seconds}</h3>;
}
};
return (
<CountDown date={Date.now() + this.state.time} renderer={renderer} />
);
}
}
export default CountdownQuestion;
Well, it's exactly like the error says. You can't update state (like in your count() function) during a render. You're probably better of using the onComplete hook.
class CountdownQuestion extends React.Component {
constructor() {
super();
this.state = {
time: 3000
};
}
render() {
// Removing this.props.count() from this function also keeps it more clean and focussed on the rendering.
const renderer = ({ seconds, completed }) => {
if (completed) {
return <h2>Zeit abgelaufen</h2>;
} else {
return <h3>{seconds}</h3>;
}
};
return (
<CountDown
date={Date.now() + this.state.time}
onComplete={this.props.count} // <-- This will trigger the count function when the countdown completes.
renderer={renderer}
/>
);
}
}
I have a higher order function that wraps the service calls. The data is streamed on a callback which I have to pass to the wrapped components. I have written the code below currently, where the child assigns handleChange to an empty object passed by the HOC. The wrapped component is a regular JS grid and hence I have to call the api to add data than pass it as a prop.
Is there a cleaner way of doing this?
function withSubscription(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.handler = {};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange(row) {
if (typeof this.handler.handleChange === "function") {
this.handler.handleChange(row);
}
}
render() {
return <WrappedComponent serviceHandler={this.handler} {...this.props} />;
}
};
}
class MyGrid extends React.Component {
constructor(props) {
super(props);
if (props.serviceHandler !== undefined) {
props.serviceHandler.handleChange = this.handleChange.bind(this);
}
this.onReady = this.onReady.bind(this);
}
onReady(evt) {
this.gridApi = evt.api;
}
handleChange(row) {
this.gridApi.addRow(row);
}
render() {
return <NonReactGrid onReady={this.onReady} />;
}
}
const GridWithSubscription = withSubscription(MyGrid);
That wrapped component should be aware of handler.handleChange looks awkward.
If withSubscription can be limited to work with stateful components only, a component may expose changeHandler hook:
function withSubscription(WrappedComponent) {
return class extends React.Component {
...
wrappedRef = React.createRef();
handleChange = (row) => {
if (typeof this.wrappedRef.current.changeHandler === "function") {
this.wrappedRef.current.changeHandler(row);
}
}
render() {
return <WrappedComponent ref={this.wrappedRef}{...this.props} />;
}
};
}
class MyGrid extends React.Component {
changeHandler = (row) => {
...
}
}
const GridWithSubscription = withSubscription(MyGrid);
To work with stateful and stateless components withSubscription should be made more generalized to interact with wrapped component via props, i.e. register a callback:
function withSubscription(WrappedComponent) {
return class extends React.Component {
handleChange = (row) => {
if (typeof this.changeHandler === "function") {
this.changeHandler(row);
}
}
registerChangeHandler = (cb) => {
this.changeHandler = cb;
}
render() {
return <WrappedComponent
registerChangeHandler={this.registerChangeHandler}
{...this.props}
/>;
}
};
}
class MyGrid extends React.Component {
constructor(props) {
super(props);
props.registerChangeHandler(this.changeHandler);
}
changeHandler = (row) => {
...
}
}
In case the application already uses some form of event emitters like RxJS subjects, they can be used instead of handler.handleChange to interact between a parent and a child:
function withSubscription(WrappedComponent) {
return class extends React.Component {
changeEmitter = new Rx.Subject();
handleChange = (row) => {
this.changeEmitter.next(row);
}
render() {
return <WrappedComponent
changeEmitter={this.changeEmitter}
{...this.props}
/>;
}
};
}
class MyGrid extends React.Component {
constructor(props) {
super(props);
this.props.changeEmitter.subscribe(this.changeHandler);
}
componentWillUnmount() {
this.props.changeEmitter.unsubscribe();
}
changeHandler = (row) => {
...
}
}
Passing subjects/event emitters for this purpose is common in Angular because the dependency on RxJS is already imposed by the framework.
How do I call a child component function from the parent component? I've tried using refs but I can't get it to work. I get errors like, Cannot read property 'handleFilterByClass' of undefined.
Path: Parent Component
export default class StudentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
newStudentUserCreated() {
console.log('newStudentUserCreated1');
this.refs.studentTable.handleTableUpdate();
}
render() {
return (
<div>
<StudentTable
studentUserProfiles={this.props.studentUserProfiles}
ref={this.studentTable}
/>
</div>
);
}
}
Path: StudentTable
export default class StudentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
studentUserProfiles: props.studentUserProfiles,
};
this.handleTableUpdate = this.handleTableUpdate.bind(this);
}
handleTableUpdate = () => (event) => {
// Do stuff
}
render() {
return (
<div>
// stuff
</div>
);
}
}
UPDATE
Path StudentContainer
export default StudentContainer = withTracker(() => {
const addStudentContainerHandle = Meteor.subscribe('companyAdmin.addStudentContainer.userProfiles');
const loadingaddStudentContainerHandle = !addStudentContainerHandle.ready();
const studentUserProfiles = UserProfiles.find({ student: { $exists: true } }, { sort: { lastName: 1, firstName: 1 } }).fetch();
const studentUserProfilesExist = !loadingaddStudentContainerHandle && !!studentUserProfiles;
return {
studentUserProfiles: studentUserProfilesExist ? studentUserProfiles : [],
};
})(StudentPage);
My design here is: component (Child 1) creates a new studentProfile. Parent component is notified ... which then tells component (Child 2) to run a function to update the state of the table data.
I'm paraphrasing the OP's comment here but it seems the basic idea is for a child component to update a sibling child.
One solution is to use refs.
In this solution we have the Parent pass a function to ChildOne via props. When ChildOne calls this function the Parent then via a ref calls ChildTwo's updateTable function.
Docs: https://reactjs.org/docs/refs-and-the-dom.html
Demo (open console to view result): https://codesandbox.io/s/9102103xjo
class Parent extends React.Component {
constructor(props) {
super(props);
this.childTwo = React.createRef();
}
newUserCreated = () => {
this.childTwo.current.updateTable();
};
render() {
return (
<div className="App">
<ChildOne newUserCreated={this.newUserCreated} />
<ChildTwo ref={this.childTwo} />
</div>
);
}
}
class ChildOne extends React.Component {
handleSubmit = () => {
this.props.newUserCreated();
};
render() {
return <button onClick={this.handleSubmit}>Submit</button>;
}
}
class ChildTwo extends React.Component {
updateTable() {
console.log("Update Table");
}
render() {
return <div />;
}
}
I have functional component GetWeather which I want to pass result of GetLocation function as props based on which GetWetaher will do something i.e. another get request (in the example below it only renders its props). I think it has to happen inside ComponentDidMount, not sure how to do it
function GetLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
return res.data.loc;
})
}
function GetWeather(props) {
//more code here, including another get request, based on props
return <h1>Location: {props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//???
}
render() {
return (
<div >
<GetWeather location={GetLocation}/> //???
</div>
);
}
}
Update: So based on suggestion from Damian below is working for me
function GetWeather(props) {
return <h3>Location: {props.location}</h3>;
}
class LocalWeather extends Component {
constructor(props) {
super(props);
this.state = {
location: []
};
}
getLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
this.setState({location:res.data.loc});
})
}
componentDidMount() {
this.getLocation();
}
render() {
return (
<div >
<GetWeather location={this.state.location}/>
</div>
);
}
}
You can do it alternatively also
constructor() {
super();
this.state = {
Location:[]
}
}
function GetLocation() {
axios.get('http://ipinfo.io').then((res) => {
this.setState ({
Location:res.data.loc;
});
});
}
function GetWeather(props) {
return <h1>Location: {this.props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//code
}
render() {
return (
<div >
<GetWeather location={this.GetLocation.bind(this)}/>
</div>
);
}
}
I wish to add the checks done (once the component mounts in CDM) to detect userAgent - for the purposes of mobile/flash/touchDevice detections to context rather than to the state. Is this possible? if so how would you do that? I am currently getting undefined when I attempt to access the value fo the context for the isFlashInstalled. Here is glimpse into the component setting the context:
App.js
export class App extends Component {
static childContextTypes = {
isFlashInstalled: React.PropTypes.bool
};
constructor() {
super();
this.state = {
isFlashInstalled: false
};
}
getChildContext() {
return {
isFlashInstalled: this.state.isFlashInstalled
};
}
componentDidMount() {
const flashVersion = require('../../../client/utils/detectFlash')();
// I know this could be done cleaner, focusing on how for now.
if (flashVersion && flashVersion.major !== 0) {
this.setFlashInstalled(true);
} else {
this.setFlashInstalled(false);
}
}
setFlashInstalled(status) {
this.setState({isFlashInstalled: status});
}
}
Later when trying to access isFlashInstalled from context I will get undefined
ChildComponent.js
export class ChildComponent extends Component {
// all the good stuff before render
render() {
const {isFlashInstalled} = this.context
console.log(isFlashInstalled); // undefined
}
}
did you correctly set up context types for parent and child? I did a test and it works, see the componentDidMount that set the state asynchronously:
class Parent extends React.Component {
state = {
color: 'red'
}
getChildContext() {
return {
color: this.state.color
};
}
componentDidMount() {
setTimeout(() => this.setState({color: 'blue'}), 2000)
}
render() {
return (
<div>Test <Button>Click</Button></div>
);
}
}
Parent.childContextTypes = {
color: React.PropTypes.string
}
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
http://jsbin.com/cogikibifu/1/edit?js,output