I have a react component called editPhoto.js it has a onDeleteFile method that I want to override when I extend it. The problem is editPhoto.js is connected to a redux store. Is inheritance the wrong approach here ? Or how would I go about extending and overriding the EditPhoto onDeleteFile and save methods ?
UPDATE: 11-1
editPhoto.js does all the same functionality that editBlog needs to do. So in OOP terms this is a classic case for inheritance by extending editPhoto with editBlog so editBlog inherits all of editPhoto's functions and properties. With inheritance I could also override two methods that editBlog uses differently from editPhoto. The save and onDeleteFile functions.
Here is how both those functions are used differently.
save() method should call a different action:
save(){
all the same (save) method code in editPhoto goes here....
// this.props.editPhoto(formData, this.props.history) should be changed to
this.props.editBlog(formData, this.props.history).
}
onDeleteFile() method should call a different action:
onDeleteFile(){
all the same (onDeleteFile) method code in editPhoto goes here....
// this.props.editBlog(this.state.id, this.props.history) should be changed to
this.props.deletePhoto(this.state.id, this.props.history)
}
Thats the only two changes in the code which seems crazy to have to duplicate 112 lines of code for just 2 lines to be changed.
Id' also like to know how you would override a method using composition but its not necessary in this case (maybe best for another post).
If there is a way of doing this with composition I'm totally open to not using inheritance as it wont inherit when using the Redux connect in EditPhoto.
END OF UPDATE 11-1 -------------------------------
See all code below trying to do the same thing in inheritance.
import React, {Component} from 'react';
import EditPhoto from './editPhoto';
export default class EditBlog extends EditPhoto{
constructor(props){
super(props);
}
// override original EditPhoto
onDeleteFile(event){
event.preventDefault();
console.log("DeleteBlog : onDeleteFile");
this.props.deleteBlog(this.state.id, this.props.history)
}
save( event ){
event.preventDefault();
const formData .....
// call different action for editBlog(formData, this.props.history);
}
}
Here is my editPhoto.js code
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Dropzone from 'react-dropzone';
import FieldEditor from '../../components/admin/fieldEditor';
import DropzoneArea from '../../components/admin/dropzoneArea';
import {editPhoto, deletePhoto} from '../../actions/index';
import style from '../../../styles/admin/editPhoto.scss';
class EditPhoto extends Component{
constructor(props){
super(props);
this.onUpdateState = this.onUpdateState.bind(this);
this.handleDrop = this.handleDrop.bind(this);
this.save = this.save.bind(this);
this.onDeleteFile = this.onDeleteFile.bind(this);
this.state = {
fieldEdits:'',
preview:'',
file:'',
id:''
}
}
componentWillMount(){
if(this.props.location.state)
this.setState({
preview: this.props.location.state.photo.photo,
fieldEdits: this.props.location.state.photo,
id: this.props.location.state.photo._id
})
}
onUpdateState(fieldEdits){
this.setState({fieldEdits});
}
handleDrop(acceptedFiles, rejectedFiles){
acceptedFiles.forEach( (file, index) => {
const reader = new FileReader();
reader.onload = ( event ) => {
const img = event.target.result;
this.setState({preview:img, file});
};
reader.onabort = () => console.log('file reading was aborted');
reader.onerror = () => console.log('file reading has failed');
reader.readAsDataURL(file);
});
}
onDeleteFile(event){
event.preventDefault();
this.props.deletePhoto(this.state.id, this.props.history);
}
save( event ){
event.preventDefault();
var form = document.forms.namedItem("photoEditUpload");
var formData = new FormData(form);
if(this.state.file.name){
formData.append('photos', this.state.file, this.state.file.name);
}
formData.append('id', this.state.id);
// for (var pair of formData.entries()) {
// console.log(pair[0]+ ' = ' + pair[1]);
// }
this.props.editPhoto(formData, this.props.history);
}
render(){
if(!this.props.location.state){
// TO DO : ADD IN A ERROR COMPONENT FOR THIS.
return <div>No photo to edit. GO BACK !</div>
}
return(
<div id="edit-photo" className='container'>
<form onSubmit={this.save} ref='form' role="save" encType="multipart/form-data" name="photoEditUpload" method="post">
<div className="row">
<div className="photo-container col-sm-8">
<img src={this.state.preview} className="edit-photo"></img>
</div>
<div className="col-sm-4">
<div>
<Dropzone onDrop={this.handleDrop} multiple={false} accept="image/jpeg" className="dropzone">
<DropzoneArea />
</Dropzone>
</div>
<div className="delete-container">
<button onClick={this.onDeleteFile} className='btn btn-primary' type='button'>Delete</button>
</div>
</div>
</div>
<FieldEditor callback={this.onUpdateState} {...this.state.fieldEdits} />
<div className="button-container">
<button className='btn btn-primary btn-save' type='submit'>Save</button>
</div>
</form>
</div>
)
}
}
export default connect(null, {editPhoto, deletePhoto})(EditPhoto);
NOTE:
Here is a stripped down version in jsfiddle showing the issue after palsrealm suggested passing the context so the store would not be undefined.
https://jsfiddle.net/chapster11/jzprkx96/31/
Your problem is that you are extending the component created by
export default connect(null, {editPhoto, deletePhoto})(EditPhoto);
If you look into the react-redux documentation, you can see that connect returns the Connect component, which extends React.Component.
When you declare
export default class EditBlog extends EditPhoto {
it might look like you are extending class EditPhoto but you're actually extending Connect. Connect wraps EditPhoto by composition and has no idea what EditPhoto's methods and properties are. There is no inheritance chain to help you out. Connect is also getting pissed off and throwing errors because it doesn't expect to get used like this.
The naming of your import at import EditPhoto from './editPhoto'; is misleading.
If you want to extend the class EditPhoto, you need to export the class itself
export class EditPhoto extends Component {
and import that in your EditBlog module
import {EditPhoto} from './editPhoto';
export default class EditBlog extends EditPhoto {
BUT - I wouldn't recommend doing this.
Inheritance is an antipattern in React. Check out the React docs on Composition vs Inheritance
At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies.
So... I would step back and try to solve the problem again with composition.
Update - Composition Example
Based on the extra detail you give in your update, I would probably start with a generic EditAsset component something like...
class EditAsset extends React.Component {
static propTypes = {
deleteAsset: PropTypes.func.isRequired,
updateAsset: PropTypes.func.isRequired,
...any other asset-specific props
}
handleDelete = () => {
...do generic stuff...
this.props.deleteAsset(id, whatever, whatever)
}
handleSave = () => {
...do generic stuff...
this.props.updateAsset(id, whatever, whatever)
}
render = () => {
...all your generic render stuff...
}
}
Then you can use redux connect to compose different components to deal with specific asset types. e.g.
import {EditAsset} from './path/to/EditAsset';
import {deletePhoto, updatePhoto, deleteBlog, editBlog} from './path/to/actions';
export const EditPhoto = connect(
undefined,
dispatch => ({
deleteAsset: (...args) => dispatch(deletePhoto(...args)),
updateAsset: (...args) => dispatch(updatePhoto(...args)),
})
)(EditAsset);
export const EditBlog = connect(
undefined,
dispatch => ({
deleteAsset: (...args) => dispatch(deleteBlog(...args)),
updateAsset: (...args) => dispatch(updateBlog(...args)),
})
)(EditAsset);
So, you write the generic component once and use composition to create one, two, one thousand specific components with appropriate data and methods passed down as props.
In this example we are just passing down two action dispatchers but you could obviously use mapStateToProps to pass down whatever specific data you need.
Does that make sense?
The store is passed around in Redux using the context, which is then connected to the class using the connect HOC. So, if you want your EditBlog component to have access to the store you need to pass the context in the constructor.
constructor(props,context){
super(props,context);
}
The call to super in the constructor creates the this keyword for the class. When you pass the context in the constructor and then to the super, the context is also used to create the this for the class along with the props. This will give access to the store to the EditBlog class.
More information at : https://reactjs.org/docs/context.html
Example code : https://codesandbox.io/s/1qrnmqror3
Edit 11/1: As described in #MarcDavies answer there is no inheritance chain to help you achieve what you are trying to do. Instead you should use composition. To use composition you can redesign your components such that the parts that are common are in one component and the parts that change are in different components.
I do not know enough about your components to make an informed design but from what I could get from your update, you are going to vary the functionality of the delete button based on whether it is a photo or a blog. To do that you can create a DeletePhoto button and a DeleteBlog button which would get the common delete functionality as props. The common parent component would know whether it is a blog or a photo and render the appropriate button accordingly.
Related
I want to control my react-router with the following component:
import React from 'react';
export interface INavigationMenuProps{
routes: string[];
}
export class NavigationMenu extends React.Component<INavigationMenuProps> {
constructor(props: INavigationMenuProps) {
super(props);
}
handleButtonClick(path: string) : void {
//this.props.history.replace(path);
console.log(path);
}
render() {
let self = this;
return (
<div>
{
this.props.routes.map(function(item,id) {
let buttonText = item.replace('/','');
return <button key={id} onClick={ (e) => {self.handleButtonClick(item);}}>{buttonText}</button>
})
}
</div>
)
}
}
This does work. But looking at the render-function made me think if this is a proper solution. I have to safe this to a variable and pass a lambda-function to the on-click-handler of the button which calls the handleButtonClick-function of my this-variable.
Is this really the proper way of handling events?
I also tried this:
return <button key={id} onClick={this.handleButtonClick(item)}>{buttonText}</button>
But this doesn't work because onClick doesn't have the same signature as handleButtonClick
So I had to wrap my function-call and tried this:
return <button key={id} onClick={()=> {this.handleButtonClick(item);}}>{buttonText}</button>
But then I got the following error:
'this' implicitly has type 'any' because it does not have a type
annotation
Can someone explain why this works when I store this to a variable?
Is this really the way-to-do?
In regards to the line let self = this;, the more common approach seen in React codebases is to bind those functions in the component's constructor. So you'll want to add a line like the following at the end of your constructor:
this.handleButtonClick = this.handleButtonClick.bind(this);
You should not run into that "implicit any" error in this case. This pattern is necessary to bind the functions to an instance of the component, which is what you're doing at runtime in render() by assigning this to self.
To answer your numbered questions:
1. My guess here is that TypeScript is able to resolve the type of your variables, but is not resolving this to an instance of the class NavigationMenu.
2. Yes. Your overall pattern here (writing a click handler in the component to handle whatever custom logic you may need) is correct. I see this commonly in React code & write React like that myself. The spot where you're deviating from what I've commonly seen is not binding the function in the constructor.
#michael-langan's answer is correct, but just to be "complete" and propose another solution... you could also take this approach (which #tmeans alludes to in his comment...):
import React from 'react';
export interface INavigationMenuProps{
routes: string[];
}
export class NavigationMenu extends React.Component<INavigationMenuProps> {
constructor(props: INavigationMenuProps) {
super(props);
}
handleButtonClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
const path = event.currentTarget.getAttribute('data-route');
console.log(path);
this.props.history.replace(path);
}
render() {
return (
<div>
{
this.props.routes.map((item, id) => {
let buttonText = item.replace('/','');
return <button key={id} data-route={item} onClick={this.handleButtonClick }>{buttonText}</button>
})
}
</div>
)
}
}
One of the nice things about this approach is that it avoid's creating an arrow function inside of the click handler of the Component, which can sometimes cause performance issues due to the way that React will "recreate" this function each time it renders.
Also, this avoids doing a bind in the constructor, which if you have a lot of click handlers in the same Component, can sometimes be messy to read.
But, all of that is really up to preference, and what linting rules you strictly are trying to follow. Like I said #michael-langan's answer is also perfectly correct.
I am getting username from server and i want to use the same user name from some other component. I know session Storage is one of the way to deal with it but i dont want to use for security reason. How can we create a global object in react?
// most simplistic
window.myAppData = {
userName: 'chad123',
language: 'EN',
// other stuff
};
window.myAppData.userName // 'chad123'
But most apps require something a bit more complex. You could use React context.
https://reactjs.org/docs/context.html
Context provider
// create context provider and consumer
const UserContext = React.createContext();
export default UserContext;
// wrap part of your app (or whole app)
// with Provider that needs access to user
class App extends React.Component {
constructor() {
super();
this.state = {
user: null
};
}
componentDidMount() {
yourUserAPI().then(user => this.setState({ user }));
}
render() {
return (
<UserContext.Provider value={this.state.user}>
<MyComponent />
</UserContext.Provider>
);
}
}
Context consumer
A) Current standard
// use anywhere in your app like this
// PS! must be descendant of Provider
class MyComponent extends React.Component {
render() {
return (
<UserContext.Consumer>
{user => {
// do stuff with 'user'
}}
</UserContext.Consumer>
);
}
}
B) React Hooks (IN ALPHA)
// only works with functional
// components (no classes)
function MyComponent() {
const user = React.useContext(UserContext.Consumer);
// do stuff with 'user'
return 'something';
}
I think to achieve that you need to use "React's context API"
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
For further info do visit the link React context api
You need a global state management like Redux.
Once you have this setup you can map your global state to your local component props and access it like you do any other prop: this.props.globalUsername.
I recommend you learn Redux by following their example program on the official website https://redux.js.org/basics/exampletodolist
Well you can create a global variable in ReactJS but it doesn't make it more "secure" over Session/Local storage.
I think creating a global variable in React project is not the best practice at all because of this simply reason: Should components track down this variable for any change ? If the answer is yes, what you are looking at should be "How to manage global state in React" not "How to create a Global Variable in React".
You can achieve it with Redux. As official documentation says "Redux is a predictable state container" but you can think it as Global State Container for your app.
You can check redux out from that url: https://redux.js.org/
USE CUSTOM HOOKS
It is very simple
globals.js
let _obj = {}
export const setGlobal = (obj) => {
Object.assign(_obj, obj)
}
export const getGlobal = varName => {
if(_obj[varName] !== undefined){
return _obj[varName]
}
else {
return null
}
}
component1.jsx
import React.....
import { setGlobal } from "./globals";
import.....
setGlobal({ title : "welcome" })
class comp.... {
render{
return(){
<i onClick={()=>setGlobal({location: "House"})}>Cmponent1</i>
}
}
}
module exp...
Component2.jsx
import React.....
import { setGlobal, getGlobal } from "./globals";
import.....
setGlobal({ greet : "Hi"})
class comp.... {
render{
return(){
<i>{getGlobal("greet")}, {getGlobal("title")} to our {getGlobal("location")}</i>
}
}
}
module exp...
I'm new to draftjs and I was wondering if there was a way to render my custom components inline in the editor.
I have a string with twitter handles. I use the decorator to detect regex #[{handle}] which replaces the handle and renders the component inline. However my handle component needs properties such as a callback function and a URL.
I'm not too sure how to pass my component the URL and callback function which I pass into my ContentEditable component.
I'm sure I'm just missing something. I've checked the contentState.getEntity(entityKey).getType() but it only sees the content I pass into the composite decorator as unstyled and not the decorated parts as separate blocks.
I've seen that you can modify the entity map, but I'm not sure if this is the right approach or how to define my own entity in the entity map
Does anyone know what I am missing to give properties to my component?
const decorator = new CompositeDecorator([
{
strategy: handleStrategy,
component: Handle,
},
]);
export default class ContentEditable extends component {
const content = 'some messages and my handle #[handle]';
if (this.props.content.trim() !== '') {
const processedHTML = DraftPasteProcessor.processHTML(content);
const entityMap = processedHTML.entityMap;
const contentState = ContentState.createFromBlockArray(processedHTML.contentBlocks, entityMap);
// Create with content with decorator
editorState = EditorState.createWithContent(contentState, decorator);
} else {
// Create empty content with decorator
editorState = EditorState.createEmpty(decorator);
}
this.state = {
editorState,
}
}
render() {
return (
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
ref="editor"
/>
);
}
I'm sorry the document is missing it. You can provide props in CompositeDecorator like CompositeDecorator({strategy:xxx,component:xxx,props:{...}})
Checking the source
What the the best way to inherit a complex Object or class down to every ReactJS-Component ?
As described on their documentation its not recommended to use context and I guess using the prop will also not the best solution.
This is our case:
Edit: Maybe a better example is when we try to inherit the request object from router into the 20th level of children for SSR reasons
We are using a Router which works on client and also for SSR. For every route which gets rendered we need on client and server a generated class to work with. So one solution should look like this:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myAwesomeObject = new AwesomeClass({/* some settings */});
}
getChildContext() {
let { myAwesomeObject } = this;
return {
myAwesomeObject
};
}
render(){
return (
<span><ComponentLevel1 /></span>
);
}
}
const ComponentLevel1 = () => {
return (
<div>
<ComponentLevel2 />
</div>
);
};
const ComponentLevel2 = () => {
return (
<div>
<ComponentLevel3 />
</div>
);
};
......
const ComponentLevel20 = (props) => {
content = this.context.myAwesomeObject.doSomeThingWithProps(props);
return (
<div>
{content}
</div>
);
};
In this example I am now able to use the awesomeObject in every Component I will render but the context is not recommended by ReactJS:
https://facebook.github.io/react/docs/context.html
So my question now is: What is the best recommended way to inherit classes like that down to all components ?
I know: on client this is not a problem because I can create the object once and use it on client global but on server (SSR) I also need access to the created class. And recreating the object every time I need it in another component seems not to be the best solution - also not to add this class to any component as property - right ?
EDIT
I have edited the code snippet to show that it maybe has a case where the 20th level of my children needs access to the instance. With context it would work like in my example but ReactJS says its not recommended. So the other solution would be to add the class to the properties of my children - 20 levels down - but is this really the best recommended solution to solve this ?
What about composition? (https://facebook.github.io/react/docs/composition-vs-inheritance.html)
class SomeComponent extends React.Component {
render() {
return(
<SomeOtherComponent {...this.props} />
)
}
}
By passing all the props down to different component (including it's children) you can easily customize child component behavior.
I know it's not a functional approach to be able to do something like this.parent in a React component, and I can't seem to find any properties on a React component instance that lead to the parent, but I'm just looking to be able to do some custom things where I need this.
Before anyone wastes their time explaining it's not the functional React "way," understand that I need this because of the following I'm trying to achieve:
Build a transpiler for Meteor's Spacebars templating engine, whose rendering model does take into consideration parent components/templates.
I've already built a transpiler that modifies the output jsx to achieve this. I do this by passing in parent={this} in all child components composed. However, after the fact it occurred to me that maybe I simply don't know of something that will give me a way to access the parent component instance without additional transpilation modifications.
Any tips would be much appreciated.
There's nothing wrong if you need to access the parent's props and functions from the children.
The point is that you should never use React internals and undocumented APIs.
First of all, they are likely to change (breaking your code) and, most importantly, there are many other approaches which are cleaner.
Passing props to children
class Parent extends React.Component {
constructor(props) {
super(props)
this.fn = this.fn.bind(this)
}
fn() {
console.log('parent')
}
render() {
return <Child fn={this.fn} />
}
}
const Child = ({ fn }) => <button onClick={fn}>Click me!</button>
Working example
Using context (if there's no direct parent/child relation)
class Parent extends React.Component {
constructor(props) {
super(props)
this.fn = this.fn.bind(this)
}
getChildContext() {
return {
fn: this.fn,
}
}
fn() {
console.log('parent')
}
render() {
return <Child fn={this.fn} />
}
}
Parent.childContextTypes = {
fn: React.PropTypes.func,
}
const Child = (props, { fn }) => <button onClick={fn}>Click me!</button>
Child.contextTypes = {
fn: React.PropTypes.func,
}
Working example
Update for React 0.13 and newer
Component._owner was deprecated in React 0.13, and _currentElement no longer exists as a key in this._reactInternalInstance. Therefore, using the solution below throws Uncaught TypeError: Cannot read property '_owner' of undefined.
The alternative is, as of React 16, this._reactInternalFiber._debugOwner.stateNode.
You've already recognized that this is not a good thing to do almost always, but I'm repeating it here for people that don't read the question very well: this is generally an improper way to get things done in React.
There's nothing in the public API that will allow you to get what you want. You may be able to get to this using the React internals, but because it's a private API it's liable to break at any time.
I repeat: you should almost certainly not use this in any sort of production code.
That said, you can get the internal instance of the current component using this. _reactInternalInstance. In there, you can get access to the element via the _currentElement property, and then the owner instance via _owner._instance.
Here's an example:
var Parent = React.createClass({
render() {
return <Child v="test" />;
},
doAThing() {
console.log("I'm the parent, doing a thing.", this.props.testing);
}
});
var Child = React.createClass({
render() {
return <button onClick={this.onClick}>{this.props.v}</button>
},
onClick() {
var parent = this._reactInternalInstance._currentElement._owner._instance;
console.log("parent:", parent);
parent.doAThing();
}
});
ReactDOM.render(<Parent testing={true} />, container);
And here's a working JSFiddle example: http://jsfiddle.net/BinaryMuse/j8uaq85e/
Tested with React 16
I was playing around with something similar using context, tho to anyone reading this, for most usual cases, accessing the parent is not advised!
I created a holder that when used, would always have a reference to the first holder up the display list, so its 'parent' if you will. Looked something like this:
const ParentContext = React.createContext(null);
// function to apply to your react component class
export default function createParentTracker(componentClass){
class Holder extends React.PureComponent {
refToInstance
render(){
return(
<ParentContext.Consumer>
{parent => {
console.log('I am:', this, ' my parent is:',parent ? parent.name : 'null');
return(
<ParentContext.Provider value={this}>
<componentClass ref={inst=>refToInstance=inst} parent={parent} {...this.props} />
</ParentContext.Provider>
)}
}
</ ParentContext.Consumer>
)
}
}
// return wrapped component to be exported in place of yours
return Holder;
}
Then to use it you would pass your react component to the method when you export it like so:
class MyComponent extends React.Component {
_doSomethingWithParent(){
console.log(this.props.parent); // holder
console.log(this.props.parent.refToInstance); // component
}
}
// export wrapped component instead of your own
export default createParentTracker(MyComponent);
This way any component exporting the function will get its parent's holder passed in as a prop (or null if nothing is further up the hierarchy). From there you can grab the refToInstance. It will be undefined until everything is mounted though.