When I look at the following line in this example:
const SortableItem = SortableElement(({value}) => <li>{value}</li>);
then I don't understand where is the lambda function ({value}) => <li>{value}</li> used in SortableElement ?
Can someone please enlighten me ?
SortableElement's code:
import React, {Component, PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import invariant from 'invariant';
// Export Higher Order Sortable Element Component
export default function SortableElement (WrappedComponent, config = {withRef: false}) {
return class extends Component {
static displayName = (WrappedComponent.displayName) ? `SortableElement(${WrappedComponent.displayName})` : 'SortableElement';
static WrappedComponent = WrappedComponent;
static contextTypes = {
manager: PropTypes.object.isRequired
};
static propTypes = {
index: PropTypes.number.isRequired,
collection: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
disabled: PropTypes.bool
};
static defaultProps = {
collection: 0
};
componentDidMount() {
let {collection, disabled, index} = this.props;
if (!disabled) {
this.setDraggable(collection, index);
}
}
componentWillReceiveProps(nextProps) {
const {index} = this.props;
if (index !== nextProps.index && this.node) {
this.node.sortableInfo.index = nextProps.index;
}
if (this.props.disabled !== nextProps.disabled)
{
let {collection, disabled, index} = nextProps;
if (disabled) {
this.removeDraggable(collection);
}
else {
this.setDraggable(collection, index);
}
}
}
componentWillUnmount() {
let {collection, disabled} = this.props;
if (!disabled) this.removeDraggable(collection);
}
setDraggable(collection, index){
let node = this.node = findDOMNode(this);
node.sortableInfo = {index, collection};
this.ref = {node};
this.context.manager.add(collection, this.ref);
}
removeDraggable(collection) {
this.context.manager.remove(collection, this.ref);
}
getWrappedInstance() {
invariant(config.withRef, 'To access the wrapped instance, you need to pass in {withRef: true} as the second argument of the SortableElement() call');
return this.refs.wrappedInstance;
}
render() {
const ref = (config.withRef) ? 'wrappedInstance' : null;
return (
<WrappedComponent ref={ref} {...this.props} />
);
}
}
}
export default function SortableElement (WrappedComponent, config = {withRef: false}) {
return class extends Component {
...
render() {
const ref = (config.withRef) ? 'wrappedInstance' : null;
return (
<WrappedComponent ref={ref} {...this.props} />
);
}
}
}
Look at the render() method in the returned React Component by the higher order SortableElement function, the lambda function (which is a stateless component) is passed as the first argument to the higher order function, and this first argument is going to end up as being the parameter WrappedComponent you see in the signature of this higher order function.
So this higher order function is going to spit a React component with a render() method that uses/calls your actual React component that you just passed in (the lambda function).
<WrappedComponent ref={ref} {...this.props} />
Thanks to ({value}) => <li>{value}</li> in
const SortableItem = SortableElement(({value}) => <li>{value}</li>);
we will be actually rendering an li element with a value passed as a prop from the map method below.
const SortableList = SortableContainer(({items}) => {
return (
<ul>
{items.map((value, index) =>
<SortableItem key={`item-${index}`} index={index} value={value} />
)}
</ul>
);
});
In the context of the SortableElement's API code the important thing is that it renders the WrappedComponent (lines 67-69). We can treat SortableElement as any other Higher Order Component - a component that wraps another component to deliver some extra functionality. In this case - a fancy sorting animation of the lambda function.
Put simply
({value}) => <li>{value}</li> is a short hand for
React.crateClass({
render:function(){
return <li>{this.props.value}</li>
}
})
refer to pure functional component in React
and SortableElement is a higher order component wrapping another React component ,such the functional component above
Related
interface ModalType {
component: JSX.Element;
props: object;
}
function Modal({ title, message, onConfirm }: ModalProps) {
const [modal, setModal] = useState<ModalType|{}>({});
// useEffect(()=> {
// ModalServcie.on('open', (({ component, props })) => {
// setModal({
// component
// props,
// close: () => {
// setModal({});
// }
// })
// })
// },[]);
const ModalComponent = (modal as ModalType).component ? (modal as ModalType).component : null;
return (
<ModalContainer>
{
ModalComponent && (
<ModalComponent
{ ...modal.props }
close={ modal.close }
className={ ModalComponent ? 'd-block' : '' }
/>
)
}
</ModalContainer>
);
}
I think there's an error because the modal component can be null.
Is it a problem that the correct object value was not added when setting the model state for the first time?
I thought it’s ambiguous to initialize state to a specific value before receiving props in useEffect.
How should I fix this?
I had a similar issue while using 'react-scroll' component Link. I observed when I modified my import statement as :
import {Link} from "react-scroll";
After this modification, the error was resolved.
Hope this helps.
Try declaring the type and only rendering the component if it actually exists:
const ModalComponent: React.Component | null = (modal as ModalType).component;
if (ModalComponent) {
return (
<ModalContainer>
<ModalComponent
{...modal.props}
close={modal.close}
className='d-block'
/>
</ModalContainer>
);
} else {
return <ModalContainer></ModalContainer>;
}
I am trying to get ReactDOM.createPortal to override the contents of the container I am mounting it too. However it seems to appendChild.
Is it possible to override contents? Similar to ReactDOM.render?
Here is my code:
import React from 'react';
import { createPortal } from 'react-dom';
class PrivacyContent extends React.Component {
render() {
return createPortal(
<div>
<button onClick={this.handleClick}>
Click me
</button>
</div>,
document.getElementById('privacy')
)
}
handleClick() {
alert('clicked');
}
}
export default PrivacyContent;
If you know what you're doing, here is a <Portal /> component that under the hoods creates a portal, empties the target DOM node and mounts any component with any props:
const Portal = ({ Component, container, ...props }) => {
const [innerHtmlEmptied, setInnerHtmlEmptied] = React.useState(false)
React.useEffect(() => {
if (!innerHtmlEmptied) {
container.innerHTML = ''
setInnerHtmlEmptied(true)
}
}, [innerHtmlEmptied])
if (!innerHtmlEmptied) return null
return ReactDOM.createPortal(<Component {...props} />, container)
}
Usage:
<Portal Component={MyComponent} container={document.body} {...otherProps} />
This empties the content of document.body, then mounts MyComponent while passing down otherProps.
Hope that helps.
In the constructor of the component, you could actually clear the contents of the div before rendering your Portal content:
class PrivacyContent extends React.Component {
constructor(props) {
super(props);
const myNode = document.getElementById("privacy");
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
}
render() {
return createPortal(
<div>
<button onClick={this.handleClick}>
Click me
</button>
</div>,
document.getElementById('privacy')
)
}
handleClick() {
alert('clicked');
}
}
export default PrivacyContent;
I find this is better and doesn't need useState:
export const Portal = () => {
const el = useRef(document.createElement('div'));
useEffect(() => {
const current = el.current;
// We assume `root` exists with '?'
if (!root?.hasChildNodes()) {
root?.appendChild(current);
}
return () => void root?.removeChild(current);
}, []);
return createPortal(<Cmp />, el.current);
};
Bit of an old question, but here's another sync solution (without useState). Also in a reusable component format.
const Portal = ({ selector, children, replaceContent = true }) => {
const target = useRef(document.querySelector(selector)).current;
const hasMounted = useRef(false);
if (!target) return null;
if (replaceContent && !hasMounted.current) {
target.innerHTML = '';
hasMounted.current = true;
}
return createPortal(children, target);
};
A solution with zero hook dependencies
import { createPortal } from 'react-dom';
const getNode = (id) => {
const domNode = document.getElementById(id);
const div = document.createElement("div");
domNode?.replaceChildren(div);
return div;
};
const Portal = ({ children }) => {
const domNode = getNode("privacy");
if (domNode) {
return createPortal(children, domNode);
}
return null;
};
When I type anything into the input which is inside of the <Input> component, I wanna execute the handleInput() function which is inside of the <MainProvider> component.
This (onChange={store.handleInput.bind(this)}) looks like working but it can't pass this.
In the console I just get undefined message.
Here's an example code.
const MainContext = React.createContext()
class MainProvider extends React.Component {
handleInput (e) {
console.log(e)
}
render () {
const store = {
handleInput: () => this.handleInput()
}
return (
<MainContext.Provider value={store}>
{this.props.children}
</MainContext.Provider>
)
}
}
class Input extends React.Component {
render () {
return (
<MainContext.Consumer>
{(store) => (
<input {...this.props} onChange={store.handleInput.bind(this)} />
)}
</MainContext.Consumer>
)
}
}
class App extends React.Component {
render () {
return (
<MainProvider>
<Input name='one' />
<Input name='two' />
</MainProvider>
)
}
}
How can I pass this in the onChange event? I'm using React 16.3.1.
The problem comes because you have used arrow function in MainProvider component which overrides the context being passed when you are calling function
render () {
const store = {
handleInput: () => this.handleInput() // using arrow function here overrides the contect
}
}
Change it to
class MainProvider extends React.Component {
handleInput (e) {
console.log(e)
}
render () {
const store = {
handleInput: this.handleInput
}
return (
<MainContext.Provider value={store}>
{this.props.children}
</MainContext.Provider>
)
}
}
However in this case you would explicitly need to bind from child components or it will take the context from where it is called.
I am really new in React.js. I wanna pass a state (that i set from api data before) to a component so value of selectable list can dynamically fill from my api data. Here is my code for fetching data :
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
From that code, i set a state called item. And i want to pass this state to a component. Here is my code :
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
But i get an error that say
TypeError: Cannot read property 'item' of undefined
I am sorry for my bad explanation. But if you get my point, i am really looking forward for your solution.
Here is my full code for additional info :
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {List, ListItem, makeSelectable} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.getListSiswa();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
export default ListSiswa;
One way to do it is by having the state defined in the parent component instead and pass it down to the child via props:
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.props.fetchItem();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
{this.props.item}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
class ListSiswa extends Component {
state = {
item: {}
}
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
render() {
return (
<SelectableList item={this.state.item} fetchItem={this.getListSiswa}>
<Subheader>Daftar Siswa</Subheader>
</SelectableList>
);
}
}
export default ListSiswa;
Notice that in wrapState now I'm accessing the state using this.props.item and this.props.fetchItem. This practice is also known as prop drilling in React and it will be an issue once your app scales and multiple nested components. For scaling up you might want to consider using Redux or the Context API. Hope that helps!
The error is in this component.
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
This component is referred as Stateless Functional Components (Read)
It is simply a pure function which receives some data and returns the jsx.
you do not have the access this here.
The Api for react-test-render says:
TestRenderer.create(element, options);
What are valid options for create?
Can I use it to set a component's context?
https://reactjs.org/docs/test-renderer.html#reference
The TestRenderer API doesn't support setting context directly - check out the create implementation here.
You can create a simple wrapper that just passes context:
import React from 'react'
import TestRenderer from 'react-test-renderer'
import PropTypes from 'prop-types'
// The example component under test
export default class WithContext extends React.Component {
static contextTypes = {
someProperty: PropTypes.any,
}
render () {
return (
<div>{ this.context.someProperty }</div>
)
}
}
describe('<WithContext>', () => {
it('renders the supplied context', () => {
const tree = TestRenderer.create(
<PassContext value={{ someProperty: 'context' }}>
<WithContext />
</PassContext>
)
tree.root.find(findInChildren(node =>
typeof node === 'string' &&
node.toLowerCase() === "context"
))
});
});
class PassContext extends React.Component {
static childContextTypes = {
someProperty: PropTypes.any,
}
getChildContext () {
return this.props.value
}
render () {
return this.props.children
}
}
// Returns a TestInstance#find() predicate that passes
// all test instance children (including text nodes) through
// the supplied predicate, and returns true if one of the
// children passes the predicate.
function findInChildren (predicate) {
return testInstance => {
const children = testInstance.children
return Array.isArray(children)
? children.some(predicate)
: predicate(children)
}
}