Trigger setState function in parent from promise.then in child - javascript

I am trying to find a solution to setState from a parent within child promise.
The parent component is
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
transition: false
};
}
handleTransition = () => {
this.setState(state => ({ transition: !state.transition }));
};
render() {
return <Child handleTransition={this.handleTransition} />;
}
}
of which this.props.handleTransition is to be triggered from a child component as
class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
onSubmit = event => {
firebase
.doCreateUserWithEmailAndPassword(email, password)
.then(() => {
// Trigger this.props.handleTransition here
})
...
Where this.props.handleTransition is wanting to be triggered with then of onSubmit
Please let me know if you require more detail? I would prefer not to use a library or package to achieve this but if it makes life easier I may consider. Redux is likely the best option but I would prefer not to unless necessary.
Note: this.props.handleTransition(); does the job but esLint returns an error of Must use destructuring props assignmenteslint(react/destructuring-assignment) and I am considering that this method is not the correct method.

// --- parent.js
import React, { Component, Fragment } from "react";
import { ChildComponent } from './containers/child'
class ParentContainer extends Component {
handleUpdate = () => {
// whatever you want to do here
}
render(){
return (
<Fragment>
<ChildComponent onUpdate={this.handleUpdate} />
</Fragment>
);
}
}
export default ParentContainer;
// --- child.js
import React, { Component, Fragment } from "react";
export class ChildComponent extends Component {
this.someAsyncFunction = () => {
fetch('/just/for/example')
.then(res =>
// Do whatever you need here, then hit your function on parent by bubbling the request up the chain
this.props.onUpdate();
)
}
render(){
return (
// whatever you want to do with this data
);
}
}

Related

passing data from the parent component state to the child component using React.createContext

I have a component that contains a state, and I will pass the state data into another component, I use a static contextType to throw the state data but the data does not reach the intended component, what do you think this is wrong? thank you
this is my parent component
export const MyContext = React.createContext();
export class MerchantByPromo extends Component {
constructor(props) {
super(props);
this.state = {
dataPromo: [],
loading: true
};
}
async componentDidMount() {
const merchant_id = this.props.match.params.id_merchant
await Api.post('language/promo-voucher-by-merchant', { MERCHANT_ID: merchant_id })
.then((response) => {
if (response.data.STATUS_CODE === '200') {
this.setState({
dataPromo: response.data.DATA,
loading: false
});
}
})
}
this is my child component
import React, { Component } from 'react'
import { MyContext } from './MerchantByPromo'
export class MerchantByPromoDetail extends Component {
constructor(props){
super(props)
this.state = {
detailPromo:[],
}
}
UNSAFE_componentWillMount(){
let value = this.context
console.log(value)
}
componentDidMount(){
}
render() {
return (
<MyContext.Consumer>
<p>tes</p>
</MyContext.Consumer>
)
}
}
I always get an error message like this "TypeError: render is not a function", what's the solution?
<MyContext.Consumer>
{() => <p>tes</p>}
</MyContext.Consumer>
change to this and Check

React forwardRef HoC not giving reference to container element

I am trying to build a generic HOC for a closing element on click of outside its space(generic close on outside solution).
As I see it ,this could be achieved with forwardRef and HOC implementation and although there is an example in official docs I cannot seem to get it right.
So I want my HOC to create a reference to the container of component. It is wrapping because it has handlers to track clicks and act upon them. For instance, lets say we have a generic Dropdown component, one would expect that I can close it on any click outside the area of this component.
The code I currently have:
import React from 'react';
function withClose(Component) {
class ClickContainer extends React.Component {
constructor() {
super();
this.handleClose = this.handleClose.bind(this);
}
componentDidMount() {
document.addEventListener('click', this.handleClose);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClose);
}
handleClose(e) {
// I expect having here context of container of wrapped component to do something like
const { forwardedRef } = this.props; // <- I expect having context in forwardedRef variable
}
render() {
const { forwardedRef, ...rest } = this.props;
return <Component ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <ClickContainer {...props} forwardedRef={ref} />;
});
}
export default withClose;
What am I missing here? I cannot make it work, I only get context of wrapped component not the element itself.
Thanks a bunch!
Ref should be passed down to the element
Checkout https://codesandbox.io/s/7yzoqm747x
Assuming
export const Popup = (props,) => {
const { name, forwardRef } = props;
return (
<div ref={forwardRef}> // You need to pass it down from props
{name}
</div>
)
}
And the HOC
export function withClose(Component) {
class ClickContainer extends React.Component {
constructor() {
super();
this.handleClose = this.handleClose.bind(this);
}
componentDidMount() {
document.addEventListener('click', this.handleClose);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClose);
}
handleClose(e) {
const { forwardRef } = this.props;
console.log(forwardRef);
}
render() {
const { forwardRef, ...rest } = this.props;
return <Component forwardRef={forwardRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <ClickContainer {...props} forwardRef={ref} />;
});
}
And expect
const CloseablePopup = withClose(Popup);
class App extends Component {
popupRef = React.createRef();
render() {
return (<CloseablePopup ref={popupRef} name="Closable Popup" />);
}
}
I Implemented the same thing few days ago.
I wanted a HOC that handle onClickoutside
I wanted that component to check on each click if the child was click or not and if it did to invoke a function on the child.
this was my solution:
import React from 'react';
export default function withClickOutside(WrappedComponent) {
class WithClickOutside extends WrappedComponent {
constructor(props) {
super(props);
this.handleClickOutside = this.handleClickOutside.bind(this);
this.wrappedElement = React.createRef();
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside, true);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside, true);
}
handleClickOutside(event) {
if (event.type !== 'click') return;
if (!this.wrappedElement.current) {
throw new Error(`No ref for element ${WrappedComponent.name}. Please create ref when using withClickOutside`);
}
if (!this.wrappedElement.current.contains(event.target)) {
if (!this.onClickOutside) {
throw new Error(`${WrappedComponent.name} does not implement onClickOutside function. Please create onClickOutside function when using withClickOutside`);
}
this.onClickOutside(event);
}
}
render() {
const wrapped = super.render();
const element = React.cloneElement(
wrapped,
{ ref: this.wrappedElement },
);
return element;
}
}
return WithClickOutside;
}
Then the component you wrap must implement a function called onClickOutside .

Not rendering component onClick

The component I am trying to render:
import React, { Component } from 'react';
export default class QueryPrint extends Component {
render() {
console.log('working');
return (
<div>Hello</div>
)
}
}
The component that is trying to call it:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Button,
} from 'reactstrap';
import QueryPrint from './bq_print';
class QueryResults extends Component {
constructor(props) {
super(props);
this.print = this.print.bind(this);
}
print() {
console.log('Clicked');
return (
<QueryPrint />
);
}
render() {
return (
<Button
className='cuts-btn'
color='success'
onClick={this.print}
>
Print
</Button>
)
}
}
function mapStateToProps(state) {
return {
query_data: state.results.query_data
}
}
export default connect (mapStateToProps, null)(QueryResults);
The console.log('clicked') is working, but the component that is supposed to render in that method doesn't--no console.log('working') or <div>.
Returning something from a click callback has no effect. If you want to render something, you do so in the render method. The click callback's job is to call this.setState(), which will then kick off a render.
Perhaps something like this:
class QueryResults extends Component {
constructor(props) {
super(props);
this.print = this.print.bind(this);
this.state = {
queryPrint: false,
}
}
print() {
console.log('Clicked');
this.setState({ queryPrint: true })
}
render() {
const { queryPrint } = this.state;
return (
<React.Fragment>
{queryPrint && <QueryPrint />}
<Button
className='cuts-btn'
color='success'
onClick={this.print}
>
Print
</Button>
</React.Fragment>
)
}
}
React Native works differently. It is more like a web app - you need to navigate to the other component.
Look at this example its very to the point: https://facebook.github.io/react-native/docs/navigation
Alternatively if you want to make only part of the screen change you will need to include it into your own render and control it thru a flag or a state machine.
https://facebook.github.io/react-native/docs/direct-manipulation

Forward ref through React Router's withRouter HOC

I'd like to manage to focus on a component that I've wrapped with withRouter. However, when I give the component a ref, I get the a warning about assigning a ref to a stateless component. I'm assuming this is because the ref is being attached to the withRouter HOC and not my component, as it is stateful. My general set up looks like this:
// InnerComponent.js
class InnerComponent extends Component {
constructor(props) {
super(props);
}
}
export default withRouter(InnerComponent);
// App.js
class App extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<Router>
<InnerComponent ref={this.myRef}>
</Router>
);
}
I see this question has been asked before, but never answered. I'm new to React so please forgive me if I'm missing something obvious. Thanks in advance.
EDIT: I'm fairly sure what I need is here: https://reacttraining.com/react-router/web/api/withRouter, in the wrappedComponentRef section of the withRouter docs, but I don't understand how to implement it.
Based on #Ranjith Kumar answer I came up with the following solution that:
Is a bit shorter/simpler (no need for class component or withRef option)
Plays a bit better in tests and dev tools
const withRouterAndRef = Wrapped => {
const WithRouter = withRouter(({ forwardRef, ...otherProps }) => (
<Wrapped ref={forwardRef} {...otherProps} />
))
const WithRouterAndRef = React.forwardRef((props, ref) => (
<WithRouter {...props} forwardRef={ref} />
))
const name = Wrapped.displayName || Wrapped.name
WithRouterAndRef.displayName = `withRouterAndRef(${name})`
return WithRouterAndRef
}
Usage is the same:
// Before
export default withRouter(MyComponent)
// After
export default withRouterAndRef(MyComponent)
HOC component to forward inner component refs with withRouter HOC
const withRouterInnerRef = (WrappedComponent) => {
class InnerComponentWithRef extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent {...rest} ref={forwardRef} />;
}
}
const ComponentWithRef = withRouter(InnerComponentWithRef, { withRef: true });
return React.forwardRef((props, ref) => {
return <ComponentWithRef {...props} forwardRef={ref} />;
});
}
Usage
class InnerComponent extends Component {
constructor(props) {
super(props);
}
}
export default withRouterInnerRef(InnerComponent);
Finally, I have done this way! this will work for sure
import { withRouter } from 'react-router';
//Just copy and add this withRouterAndRef HOC
const withRouterAndRef = (WrappedComponent) => {
class InnerComponentWithRef extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent {...rest} ref={forwardRef} />;
}
}
const ComponentWithRouter = withRouter(InnerComponentWithRef, { withRef: true });
return React.forwardRef((props, ref) => {
return <ComponentWithRouter {...props} forwardRef={ref} />;
});
}
class MyComponent extends Component {
constructor(props) {
super(props);
}
}
//export using withRouterAndRef
export default withRouterAndRef (MyComponent)
I think you could use React.forwardRef (https://reactjs.org/docs/forwarding-refs.html) like this:
// InnerComponent.js
class InnerComponent extends Component {
constructor(props) {
super(props);
}
}
export default withRouter(InnerComponent);
// App.js
const InnerComponentWithRef = React.forwardRef((props, ref) => <InnerComponent ref={ref} props={props} />);
class App extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<Router>
<InnerComponentWithRef ref={this.myRef}>
</Router>
);
}
Note: Untested code!
I thing you can use withRef option available while exporting the component.
See the below sample to export
// InnerComponent.js
class InnerComponent extends Component {
constructor(props) {
super(props);
this.exampleMethod = this.exampleMethod.bind(this);
}
exampleMethod(){
}
}
export default withRouter(InnerComponent , { withRef: true });
and also try the below method to access the methods using reference
this.myRef.getWrappedInstance().exampleMethod()
A much easier way to do this:
// InnerComponent.js
class InnerComponent extends Component {
constructor(props) {
super(props);
}
}
export default withRouter(InnerComponent);
// App.js
import InnerComponentWithRouter, { InnerComponent } from '/InnerComponent'
class App extends Component {
private myRef: InnerComponent|null = null;
constructor(props) {
super(props);
}
render() {
return (
<Router>
<InnerComponentWithRouter wrappedComponentRef={(r: InnerComponent) => this.myRef = r} />
</Router>
);
}
}
Simplified the good answer by #pandaiolo a bit more. Uses wrappedComponentRef, which is already used by withRouter
function withRouterAndRef(WrappedComponent) {
const RoutableComponent = withRouter(WrappedComponent);
const RoutableComponentWithRef = React.forwardRef((props, ref) => (
<RoutableComponent {...props} wrappedComponentRef={ref} />
));
const name = WrappedComponent.displayName || WrappedComponent.name;
RoutableComponentWithRef.displayName = `withRouterAndRef(${name})`;
return RoutableComponentWithRef;
}

Test whether React component has rendered

I have a React component that I am trying to test using Enzyme/Jest. I am trying to figure out what the most appropriate test would be to ensure the component has rendered.
My component has a prop shouldRender that, if false, will cause the component to not render. My component looks like this:
import React from 'react';
const propTypes = {
shouldRender: React.PropTypes.bool,
};
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar',
};
}
render() {
if (!this.props.shouldRender) {
return null;
}
return (
<div>
<span>My component</span>
</div>
);
}
}
MyComponent.propTypes = propTypes;
export default MyComponent;
I have a test that looks like this:
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from '../MyComponent';
describe('MyComponent', () => {
it('Should render if we want it to', () => {
const component = shallow(<MyComponent shouldRender />);
expect(component).toBeDefined(); // Passes
});
it('Should not render if we do not want it to', () => {
const component = shallow(<MyComponent />);
expect(component).not.toBeDefined(); // Does not pass, as component isn't undefined.
});
});
I'd like the second test to fail, as the component isn't rendering. Is there a better way to go about testing whether or not a component has rendered?
Happy to provide any more information if it is needed.
Thanks!
So I've had a chat to some people and decided that maybe I am going about this the wrong way.
It's probably a better idea to determine whether or not this gets rendered by the parent component, otherwise any time I want to use MyComponent, I am going to have to pass this shouldRender prop into it.
MyComponent now looks like this:
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar',
};
}
render() {
return (
<div>
<span>My component</span>
</div>
);
}
}
MyComponent.propTypes = propTypes;
export default MyComponent;
and MyParentComponent that uses MyComponent looks like this:
import React from 'react';
const propTypes = {
myComponent: React.PropTypes.bool,
};
class MyParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
boz: 'baz',
};
}
render() {
return (
<div>
{ this.props.myComponent &&
<MyComponent />
}
</div>
);
}
}
export default MyComponent;
Not only does allow MyComponent to be more reusable, it removes the need for the test I wanted to write altogether. Thank you to everyone that looked at this.
I think Jest's snapshot testing is what you need. With snapshot testing when a test fails you can check to see if it's intended or unintended change. Check out their example here

Categories

Resources