I am using a component called DocumentPicker from the Fluent UI library.
This components has several methods:
<DocumentPicker
removeButtonAriaLabel="Remove"
onRenderSuggestionsItem={SuggestedBigItem as any}
onResolveSuggestions={ /* do some stuff here */ }
onRenderItem={SelectedDocumentItem}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
disabled={isPickerDisabled}
inputProps={inputProps}
/>
For my specific scenario, I'd like to have a method of this component call another method. For example, have onEmptyInputFocus trigger onResolveSuggestions. How can I accomplish this?
[edit] Basically I am trying to accomplish with a function component what I would be able to do using "this" on a class component. In my class component I could write something like:
public onEmptyInputFocus () {this.onResolveSuggestions();}
Since you specify these methods, it's pretty easy:
const _onEmptyInputFocus = () => {
onResolveSuggestions()
}
<DocumentPicker
removeButtonAriaLabel="Remove"
onEmptyInputFocus={_onEmptyInputFocus}
onRenderSuggestionsItem={SuggestedBigItem as any}
onResolveSuggestions={onFilterChanged}
onRenderItem={SelectedDocumentItem}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
disabled={isPickerDisabled}
inputProps={inputProps}
/>
I think I am pretty clear now that it cannot be accomplished with function components. You would have to know the internals of the component and tweak it.
A workaround is to use a ref and work with the underlying HTML element. In Fluent UI the prop is actually called componentRef, not just ref.
Related
I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.
I am building an app for posting tutorials. Two of the components I have are EditTutorialForm and NewTutorialForm. These two components are essentially the same except for the methods componentDidMount and onSubmit.
What seems to make the most sense is to have an abstract component type called TutorialForm and to extend it to make EditTutorialForm and NewTutorialForm.
I have read on the React docs that inheritance is not recommended with React. Would it be "better" to pass the componentDidMount and onSubmit functions as props to the TutorialForm component, as opposed to extending the component itself?
I would create one component and check within something like the following:
For a new tutorial
<TutorialForm edit={false}>
To edit a tutorial
<TutorialForm edit={true}>
And in TutorialForm
class TutorialForm extends Component{
componentDiMount() {
this.props.edit ? do edit stuff... : do new stuff
}
submitForm = () {
this.props.edit ? submit edit... : submit new
}
}
I recently switched from React to Vue. In React, i often had a method in a child component, that would look something like this:
onClick = (e)=>{
const val = e.target.value;
this.props.onClick(val)
}
I see that Vue is very "free", and allows you to treat component methods and props as "the same thing".
Is there some way to distinguish between the two, like with this.props? What is the convention regarding this issue?
Props, data, computed properties and methods end up as properties on Vue instance - members may also include lifecycle hooks when a component is declared as a class. As with any other class, their names may collide.
In case there are members that are private (onClick method) and are parts of public API (onClick prop), they can be named differently, e.g. with underscore naming convention that is common in JS OOP:
...
props: {
onClick: Function
},
methods: {
_onClick() {...}
}
...
The use of naming conventions for private properties is suggested by Vue style guide.
In composition API and Vue 3, there is a clear distinction in setup because props object is available, so it could be done similarly to how the question suggests. There is no difference between props and instance properties in templates so the rest of the answer is still applicable.
You should take advantage of another Vue technique, instead of passing a function, using its event emitter system:
// MyComponent:
methods: {
onClick(e) {
const val = e.target.value;
this.emit('click', val);
}
}
...
<button #click="onClick">Click me</button>
//When you actually use your component
<MyComponent #click="() => { console.log('delegated onClick!'); }"/>
As mentioned in one of the comments try to avoid using duplicate names for props, methods, etc...
If you have a component like this
class Editor extends Component {
handleChange() {
// some code
}
render() {
<div>
<input className="Editor" onChange={this.handleChange} />
</div>
}
}
Is it better to test the handle change by simulating the change event with simulate like this:
wrapper.simulate('change', { // })
Or by calling the method directly by using instance:
wrapper.instance().handleChange()
If you are using Shallow Rendering then .simulate just tries to find the right prop and call it. From the Common Gotchas section for .simulate:
Even though the name would imply this simulates an actual event, .simulate() will in fact target the component's prop based on the event you give it. For example, .simulate('click') will actually get the onClick prop and call it.
There isn't any advantage to calling .simulate when using Shallow Rendering and simply calling the prop directly avoids issues caused by the event not mapping to the correct prop.
If you are using Full DOM Rendering then .simulate will fire an event that ends up calling runEventsInBatch from the comically named ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events.
So using .simulate with mount will actually simulate the event, just note from the Common Gotchas section that:
ReactWrapper will pass a SyntheticEvent object to the event handler in your code. Keep in mind that if the code you are testing uses properties that are not included in the SyntheticEvent, for instance event.target.value, you will need to provide a mock event...for it to work.
For Full DOM Rendering it is up to you to determine if there is any value in having ReactDOM simulate the event to call your handler or to just call your handler directly.
From this post by an Airbnb dev:
In general, I've found it's best to invoke the prop directly and avoid .simulate.
For your test it would look something like this:
test('onChange', () => {
const wrapper = shallow(<Editor />); // works with shallow or mount
const onChangeHandler = wrapper.find('input').prop('onChange');
// test onChangeHandler
})
I have a large React app and I have a few components that I would like to completely disable from a config or global level. Is there any kind of global hook that I can use that is called before any component is rendered? If so, I imagine I can check the name of the component and return null if the name is on the disabled list. How would you do this?
There are a lot of ways to do this:
React's Context API allows you pass props through every level of the component tree so you can use them as flags to enable/disable components. Should be used sparingly however.
Higher Order Components are basically just functions that return a component. You could wrap your components in logic to render them as needed.
Or of course you could use a global state manager like redux to set global states.
There are many ways to do this, so, I'll just describe one simple way: using references and updating the states accordingly.
Full working feature hide/showing sandbox online: codesandbox.io ReactJS Feature Hide/Show Demo
Defined are two classes, class Feature extends React.Component and class App extends React.Component. The render() for <Feature/> is...
render() {
if (!this.state.enabled) {
return <div />;
}
return (
<div className="Feature">
<h1>My Feature!</h1>
</div>
);
}
And the option for enabling/disabling a feature in <App /> would handle display/hiding like so...
handleOnClick(e) {
if (e.target.checked) {
this.feature.setState({ enabled: true });
} else {
this.feature.setState({ enabled: false });
}
}
Of course, you need to make sure that <Feature /> has the reference set...
<Feature
ref={instance => {
this.feature = instance;
}}
/>
If you need simplest solution just use browser global vars and check it in render.
render() {
if( window.globalFlag ) return null
return (
<div> feature content...
Drawbacks:
modifying component,
using global scope,
some unnecessary code can be run earlier (f.e. constructor) and later (f.e. componentDidMount).
Use HOCs - wrap your component - connecting with global store using redux or context API.
<FlagsProvider store={flagStore}>
<SomeComponent_1>
<SomeComponent_2>
<FlagsConsumer flag="someFeatureFlag">
<SomeFeatureComponent />
<FlagsConsumer/> connects to store (redux connect would be an inner wrapper - composing HOCs) and conditionally renders <SomeFeatureComponent /> (or null).
Of course HOC can pass received props to wrapped component - it can be functionally transparent.
Don't reinvent the wheel - use some ready module, read tutorials, google for sth suitable.
HOC can also play a role of A/B testing.