i have a parent component where i have a handleClick which is passed as a prop to the child.
// parent.js
_handleClick = async (buttonName, id) => {
if(buttonName === 'yes'){
... some logic
}else{
... some logic
}
}
<Child
handleClick={(buttonName, id) => this._handleClick(buttonName, id)}
/>
so right now how can i call the _handleClick and run the test cases. How should i call the method .
I have tried below but didnt worked as expected since its an arrow function and it expects two parameters.
//test.js
const wrapper = shallow(<parent />)
expect(wrapper.find('Child').length).toEqual(1)
wrapper.find('Child').prop('handleClick')
wrapper.find('Child').prop('handleClick') is the function, so you can just call it like this:
wrapper.find('Child').prop('handleClick')( /* your args here */ );
Here is a simplified working example:
import { shallow } from 'enzyme';
import * as React from 'react';
const Child = () => (<div></div>);
class Parent extends React.Component {
constructor(...args) {
super(...args);
this.state = { val: 'initial' };
}
_handleClick = async (buttonName, id) => {
// ... await something ...
this.setState({ val: buttonName });
}
render() {
return (<Child handleClick={(buttonName, id) => this._handleClick(buttonName, id)} />);
}
}
test('click handler', async () => {
const wrapper = shallow(<Parent />);
expect(wrapper.find('Child').length).toEqual(1); // Success!
await wrapper.find('Child').prop('handleClick')('the button name'); // <= call the handler
expect(wrapper.state()).toEqual({ val: 'the button name' }); // Success!
});
Well, you may need to render the parent component with the mount method as opposed to shallow. This will render the child element, otherwise just a placeholder will be rendered. Then, you you may want to trigger the actual click event by clicking the button or whatever triggers the event in the child component.
Related
A have two files, with two functional components A and B, in the first component A, i have a specialFunction that gets called with onClick, what i want to do is raise an event in specialFunction when it's called, and then in component B add a Listener for the event in specialFunction.
Component A:
function specialFunction(){
//raise the event and send some data
}
Component B:
//contains a listener that does some work when specialFunction is called, example:
(data) => {console.log("am called:",data)};
1. Create notifier class using observer pattern
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
2. ComponentA receives notifier as a prop and used to notify all subscribers
const ComponentA = ({ notifier }) => {
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div>{/** Some content */}</div>
}
3. ComponentB receives notifier and subscribes to it to receive data sent by from ComponentB
const ComponentB = ({ notifier }) => {
useEffect(() => {
const callbackFn = data => {/** Do whatever you want with received data */ }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [])
}
4. App holds both component. Create instance of notifier there and pass as a props
const App = () => {
const dataNotifier = new ChangeNotifier();
return <div>
<ComponentA notifier={dataNotifier} />
<ComponentB notifier={dataNotifier} />
</div>
}
If you have components on different levels deeply nested and it is hard to pass notifier as a prop, please read about React Context which is very helpful when you want to avoid property drilling
React Context
Here's implementation with context
class ChangeNotifier {
subscribers = [];
subscribe(callback) {
this.subscribers.push(callback);
return this.unsubscribe.bind(this, callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
notifyAll(data) {
this.subscribers.forEach(callback => callback(data));
}
}
const NotifierContext = React.createContext();
const ComponentA = () => {
const { notifier } = useContext(NotifierContext);
const triggerNotifier = () => {
notifier.notifyAll('Some data that will subscribers receive');
}
return <div><button onClick={triggerNotifier}>Notify</button></div>
}
const ComponentB = () => {
const { notifier } = useContext(NotifierContext);
useEffect(() => {
const callbackFn = data => { console.log(data) }
notifier.subscribe(callbackFn);
return () => notifier.unsubscribe(callbackFn);
}, [notifier])
}
Now all components wrapped in NotifierContext.Provider (no matter how deep they are nested inside other components) will be able to use useContext hook to receive context value passed as value prop to NotifierContext.Provider
const App = () => {
const dataNotifier = useMemo(() => new ChangeNotifier(), []);
return <NotifierContext.Provider value={{ notifier: dataNotifier }}>
<ComponentA />
<ComponentB />
</NotifierContext.Provider>
}
export default App;
Last but not least, I guess you can avoid context or properties drilling and just create instance of ChangeNotifier in some utility file and export it to use globally...
Andrius posted a really good answer, but my problem was that the two components, one of them is used as an API, and the other had a parent component, am a beginner so maybe there is a way to use them but i just didn't know how.
The solution that i used, (maybe not the best) but did the job was to dispatch a custom event in a Promise from the specialFunction:
function specialFunction(){
new Promise((resolve) => {
console.log("am the promise");
document.dispatchEvent(event);
resolve();
});
And add a Listener in the other component using a useEffect hook:
useEffect(() => {
let handlePreview = null;
new Promise((resolve) => {
document.addEventListener(
"previewImg",
(handlePreview = (event) => {
event.stopImmediatePropagation();
//Stuff...
})
);
return () =>
window.removeEventListener("previewImg", handlePreview, false);
});
}, []);
Thank you for your help.
I am trying to create a function when user hit browser back button it will run function deleteHeldResort that I created.
Here is my code for deleteHeldResort:
deleteHeldResorts(ReservedInventoryID: number | null = null, refreshHeldResorts: boolean = true) {
return new Promise((resolve, reject) => {
this.setState({heldResortsShowLoader: true});
this.reservationService.deleteHeldResorts(ReservedInventoryID)
.then(() => {
refreshHeldResorts && this.getHeldResorts();
resolve();
})
.catch((error: any) => {
this.catchHeldResortsError(error);
reject(error);
});
this.setState({
heldResortsShowLoader: false
});
});
}
Updated code base:
handleNavigateBack = useCallback(
(event) => {
// call your function here with whatever argument your code provides
this.reservationService.deleteHeldResorts(this.props.resId);
}
, []);
useEffect(() => {
document.addEventListener('popstate', this.handleNavigateBack );
return () => {
document.removeEventListener('popstate', this.handleNavigateBack)
}
}, [this.handleNavigateBack]);
Updated answer: class component based
Since you're using class components, I'm adding this update.
Inside the class component having deleteHeldResorts function, you can listen to popstate event:
Remark how bind keyword helps us keep the right this.
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.handleNavigateBack = this.handleNavigateBack.bind(this);
}
componentDidMount() {
document.addEventListener("popstate", this.handleNavigateBack);
}
componentWillUnmount() {
document.removeEventListener("popstate", this.handleNavigateBack);
}
handleNavigateBack(event) {
console.log("inside callback", event);
// change arguments as you want
this.deleteHeldResorts(null, false);
}
deleteHeldResorts(ReservedInventoryID = null, refreshHeldResorts = true) {
// your function goes down there content
console.log("inside deleteHeldResorts");
}
render() {
return <h2>popstate browser listener</h2>;
}
}
render(<App />, document.getElementById("root"));
Initial answer: function component based
Inside the component having deleteHeldResorts function, you can listen to popstate event:
// your-component.js
function YourComponent() {
function deleteHeldResorts(ReservedInventoryID: number | null = null,
refreshHeldResorts: boolean = true) {
// your function content
}
const handleNavigateBack = useCallback(
(event) => {
// call your function here with whatever argument your code provides
deleteHeldResorts(reservedInventoryID,refreshHeldResorts);
}
// depending on your logic, add deps to this array dependency
}, [])
useEffect(() => {
document.addEventListener('popstate', handleNavigateBack );
return () => {
document.removeEventListener('popstate', handleNavigateBack)
}
}, [handleNavigateBack])
return ( <>something dope</>);
}
I've created a validation function that I can call externally like so:
const isValid = validateChildren(this.props.children)
And I have a component I'd like to validate.
class MyComponent extends React.Component {
constructor(props) {
super(props)
}
isValid() {
// Validation will check against the render method in this component.
return true;
}
render() {
return false;
}
}
Within that function I'm using the component props to check for a validation function using React.Children. This looks something like this:
React.Children.map(children, (child) => {
// Validation here.
});
What I'd like to do in addition to checking for props, is to check for a internal class method of isValid and then fire it. That way in the case of MyComponent I could do the following:
if (child.current.isValid) child.current.isValid()
Is something like this possible in React? I'm trying to solve a performance issue with cloning the child elements that I'd like to avoid with this approach.
You can do this using forwardRef and the useImperativeHandle hook, as described here.
If you change the name in the App function, you'll see the validity change.
import React, { useState, useImperativeHandle, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
const validateNameProp = nameProp => {
return nameProp === "Colin";
};
let Child = ({ name, childRef }) => {
const [nameIsValid, setNameIsValid] = useState(false);
// We want to expose the isValid function so it can be called by parent.
useImperativeHandle(childRef, () => ({
isValid
}));
const isValid = () => {
setNameIsValid(true);
};
return (
<div ref={childRef}>
<h1>
Name is {name} and this name is: {nameIsValid ? "valid" : "invalid"}
</h1>
</div>
);
};
const App = () => {
const childRef = useRef();
const name = "Colin";
// Wait until component mounts so ref is not null.
useEffect(() => {
if (validateNameProp(name)) {
childRef.current.isValid();
}
}, []);
return <Child childRef={childRef} name={name} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I am trying to wrap my head around ReactJS and I am stumped with an issue where I want to be able to update the value of a local variable and return the updated value.
I've read about state and I've used that when working with React Components, however, this class is just defined as const and it doesn't extend React.Component.
Is there a different way I should be defining setting the variable?
Here is a simplified version of my code:
import React from 'react';
const WelcomeForm = ({welcome}) => {
var welcomeMsg = 'Test';
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
// response.text has content
welcomeMsg = response.text;
}).catch(() => {
welcomeMsg = '';
});
return (
<p>{welcomeMsg}</p> // Returns 'Test'
);
};
export default WelcomeForm;
The easiest option here is to change your stateless component to a stateful component.
Stateless components are just JavaScript functions. They take in an
optional input, called prop.
Stateful components offer more features, and with more features comes more baggage. The primary reason to choose class components (stateful) over functional components (stateless) is that they can have state, that is what you want to update to re-render.
Here is what you can do:
class WelcomeForm extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
return (
<p>{welcomeMsg}</p>
);
}
};
If you want, for any reason, to keep your component stateless, you will have to put the loadDynamicContent() function on the Parent and pass the text to WelcomeForm as a prop. For example:
// Your WelcomeForm Component
const WelcomeForm = ({welcomeMsg}) => (
<p>{welcomeMsg}</p>
);
// Whatever it's Parent Component is
class Parent extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
// response.text has content
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
<WelcomeForm welcomeMsg={this.state.welcomeMsg} />
}
}
As suggested in the comments, you can pass the DynamicContentApi logic to outside:
import ReactDOM from 'react-dom'
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
ReactDOM.render(<WelcomeForm data={response.text} />, document.getElementById('where you wanna render this'));
}).catch(() => {
console.log('error while fetching...');
});
And where you have your component:
import React from 'react';
export default class WelcomeForm extends React.Component {
render() {
return (
<p>{this.props.data}</p>
);
}
}
I'm trying to do some unit testing using jest/enzyme for my react components.
But I'm facing problems with an function I'm passing to a second component.
I don't understand if I have to test or mock this function. If I have to mock it, I don't know how to do that for a function.
Parent Component
export default class Parent extends Component {
togglePosition (term, event) {
this.setState({
top: term.length >= 3
})
}
render () {
return (
<div>
<Child togglePosition={this.togglePosition} />
</div>
)
}
}
Child component
export default class Child extends Component {
handleChange (event) {
const term = event.target.value
this.props.togglePosition(term) // <-- Test/mock it?
this.setState({
loading: 'loading',
term
})
}
render () {
return (
<div>
<Input id="target-input" onChange={this.handleChange} />
</div>
)
}
}
This is how I do a test for the Child component - testing handleChange:
Unit test (Child)
it('handleChange() should set state.term', () => {
const event = { target: { value: 'test' } }
const wrapper = shallow(<Child />)
wrapper.find('#target-input').simulate('change', event)
const state = wrapper.instance().state
expect(state).toEqual({ loading: 'loading', term: 'test' })
})
Do get this error: TypeError: this.props.togglePosition is not a function
Without actually testing it, I believe this is what you need:
it('handleChange() should set state.term', () => {
const togglePosition = jest.fn();
const event = { target: { value: 'test' } };
const wrapper = shallow(<Child togglePosition={togglePosition} />);
wrapper.find('#target-input').simulate('change', event);
const state = wrapper.instance().state;
expect(state).toEqual({ loading: 'loading', term: 'test' });
expect(togglePosition).toHaveBeenCalledWith('test');
})
Mock the passed function: const togglePosition = jest.fn();, and test the condition/response: expect(togglePosition).toHaveBeenCalledWith('test');.