Using recompose utility as element - javascript

I want to use recompose utility functions as react element, so that I can use them in JSX as hoc.
const enhancedInput = props => {
return (<OnlyUpdateForKeys keys={['name']}>
<Input
id="name"
value={props.name}
onBlur={props.handleNameBlur}
onChange={props.updateName}
type="text"
className="validate"
placeholder="Enter Component Name"
/>
</OnlyUpdateForKeys>)
}
This is what I have tried till now, but it utterly fails.
import { onlyUpdateForKeys } from 'recompose';
export const OnlyUpdateForKeys = ({ keys, children }) => {
return onlyUpdateForKeys([...keys])(children)(children.props);
};
export default OnlyUpdateForKeys;
because children is a symbol and react-element instance and not a class/function.
react.development.js:368 Uncaught TypeError: Cannot set property 'props' of undefined
at Component (react.development.js:368)
at ShouldUpdate (Recompose.esm.js:558)
at OnlyUpdateForKeys (recomposeComponent.js:4)
at mountIndeterminateComponent (react-dom.development.js:14324)
Can somebody guide me?

onlyUpdateForKeys([...keys]) higher-order component expects React component as an argument, while children is React element:
onlyUpdateForKeys([...keys])(children)
It would be workable like:
export const OnlyUpdateForKeys = ({ keys, children }) => {
const EnhancedChild = onlyUpdateForKeys([...keys])(props => children);
return <EnhancedChild/>;
};
But it doesn't make sense because it doesn't prevent child component from being updated. OnlyUpdateForKeys is created on each EnhancedInput render. Input is rendered every time EnhancedInput is rendered too, because children are rendered any way - otherwise they wouldn't be available as props.children.
While onlyUpdateForKeys is supposed to be used as:
const EnhancedInput = onlyUpdateForKeys(['name'])(props => (
<Input ... />
))
It's shorter and more efficient than OnlyUpdateForKeys utility component.

Related

React don't render mapping elements

I'm mapping Array and React don't want to render this.
Typescript gives this error: "This JSX tag's 'children' prop expects a single child of type 'ReactNode', but multiple children were provided.".
But when I use React fragment in component instead of div error isn't detected.
Why React don't render?
import { useDispatch, useSelector } from 'react-redux';
export const VkForm = () => {
const messages = useSelector((state: appStateType) => state.MessageReducer.Messages)
return (<div>
{messages.map((m) => {
<div key={m._id}>{m.text}</div>
})}
</div>)
}
This is a common mistake for beginners. You are missing the actual return statement in your .map function.
Since you are using curly brackets you have to explicity add the return statement before your JSX Component like:
{messages.map((m) => {
return <div key={m._id}>{m.text}</div>
})}
If you want to avoid explicitly returning that Component you can use the implicit return with parentheses:
{messages.map((m) => (
<div key={m._id}>{m.text}</div>
))}

React HOC: Pass data attributes to the first child/element of wrapped component

I have a hoc component like this:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => (
<WrappedComponent {...props} data-test-id="this-is-a-element" />
);
return ModifiedComponent;
};
export default withAttrs;
and I use it like this:
import React from 'react';
import withAttrs from './withAttrs';
const SomeLink = () => <a><p>hey</p</a>;
export default withAttrs(SomeLink);
I expect to have an anchor tag like this:
<a data-test-id="this-is-a-element"><p>hey</p></a>
But the hoc doesn't add the data-attribute to the first element. Is there a way to achieve this?
But the hoc doesn't add the data-attribute to the first element.
It's not the HOC that isn't adding it, it's SomeLink, which doesn't do anything with the props the HOC passes to it.
The simple answer is to update SomeLink:
const SomeLink = (props) => <a {...props}><p>hey</p></a>;
That's by far the better thing to do than the following.
If you can't do that, you could make your HOC add the property after the fact, but it seems inappropriate to have the HOC reach inside the component and change things. In fact, React makes the element objects it creates immutable, which strongly suggests you shouldn't try to mess with them.
Still, it's possible, it's probably just a bad idea:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
/*export*/ const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
// THIS IS PROBABLY A VERY BAD IDEA. React makes these objects
// immutable, probably for a reason. We shouldn't be mucking
// with them.
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
const SomeLink = () => <a><p>hey</p></a>;
const SomeLinkWrapped = withAttrs(SomeLink);
const Example = () => {
return <div>
<div>Unwrapped:</div>
<SomeLink />
<div>Wrapped:</div>
<SomeLinkWrapped />
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
/* So we can see that it was applied */
[data-test-id=this-is-a-element] {
color: green;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Again, I don't think I'd do that except as a very last resort, and I wouldn't be surprised if it breaks in future versions of React.

Understanding Module Scope in Nested React Components

While refactoring some code at work, I ran into a circular dependency with nested React components. The feature was basically "Use a switch to dynamically render components nested in other components, including themselves".
I solved the problem by creating a component "registry" with two methods, registerBlock and an HOC BlockRegistry.
src/BlocksNode.js
// src/BlocksNode.js - accepts and renders a block passed from the API
import BlockRegistry, { registerBlock } from './BlockRegister'
import ComponentOne from './ComponentOne'
import ComponentTwo from './ComponentTwo'
// accepts a name and a component
registerBlock('componentOne', ComponentOne)
registerBlock('componentTwo', ComponentTwo)
// reads a block from the API and uses the type passed from props
const BlocksNode = (props) => {
const { type, blocks } = props
return <BlockRegistry type={type} blocks={blocks} />
}
export default BlocksNode
src/BlockRegister.js
const components = {}
export function registerBlock(name, Component) {
components[name] = Component
}
const BlockRegistry = (props) => {
const { type, ...rest } = props
const Component = components[type]
return <Component {...rest} />
}
export default BlockRegistry
src/ComponentOne.js
import BlockRegistry from './BlockRegister'
function ComponentOne(props) {
const { blocks } = props
return (
<div>
{blocks.map((block) => {
const { type, blocks } = block
return <BlockRegistry type={type} blocks={blocks} />
})}
</div>
)
}
export default ComponentOne
ComponentOne.js can pass any number of other blocks, including ComponentTwo.js or itself. I've simplified some of the logic, but the gist is there.
This solution works great. But I don't understand how it's able to function. Logically I'd expect the nested component's scope to not include the top-level, registered components. How is scope being handled so that nested components are working without new, nested calls to registerBlock()?
For instance, how does <BlockRegistry /> in ComponentOne.js find a match for block.type === 'componentTwo'? I'd expect to need to re-register it, ie. by doing registerBlock('componentTwo', ComponentTwo) inside ComponentOne.js. The fact that it works without the call seems strange.

Use ref in Higher Order Components

I have a Table component that I want ref to be attached to.
Use: Table.js
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
this.tableRef = React.createRef();
}
componentDidUpdate() {
//using ref
this.tableRef.current ..... //logic using ref
this.state.rows ..... //some logic
}
render() {
<TableContainer ref={this.tableRef} />
<CustomPagination />
}
}
This works fine, but now my requirement has changed, and I want to reuse the Table component with pagination applied to all the Tables in my App. I have decided to make a HOC withCustomPagination.
Use: withCustomPagination.js HOC
import CustomPagination from 'path/to/file';
const withCustomPagination = tableRef => Component => {
return class WithCustomPagination extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
}
componentDidUpdate() {
tableRef.current.state ..... //logic using ref, Error for this line
this.state.rows ..... //some logic
}
render() {
return (
<Component {...state} />
<CustomPagination />
)
}
}
}
export default withCustomPagination;
New Table.js:
import withCustomPagination from '/path/to/file';
const ref = React.createRef();
const Table = props => (
<TableContainer ref={ref} />
);
const WrappedTable = withCustomPagination(ref)(Table);
HOC withCustomPagination returns a class WithCustomPagination that has a componentDidUpdate lifecycle method that uses Table ref in the logic. So I try to pass ref created in Table.js as argument to withCustomPagination, i.e curried with ref and Table stateless component.
This use of ref is wrong and I get error: TypeError: Cannot read property 'state' of null.
I tried using Forwarding Refs, but was unable to implement it.
How do I pass the Table ref to withCustomPagination and be able to use it in HOC?
In this case you can use useImperativeHandle
It means you have to forward ref and specify which function or object or,...
you want to share with ref inside your functional component.
Here is my Hoc example :
import React from 'react';
import { View } from 'react-native';
export function CommonHoc(WrappedComponent) {
const component = class extends React.Component {
componentDidMount() {
this.refs.myComponent.showAlert();
}
render() {
return (
<>
<WrappedComponent
ref='myComponent'
{...this.state}
{...this.props}
/>
</>
);
}
};
return component;
}
and it's my stateless component
const HomeController=(props,ref)=> {
useImperativeHandle(ref, () => ({
showAlert() {
alert("called");
},
}));
return (
<Text>home</Text>
);
};
export default CommonHoc(forwardRef(HomeController));
Either restructure your code to not use a HOC for this or try using React.forwardRef:
Refs Aren’t Passed Through
While the convention for higher-order components is to pass through
all props to the wrapped component, this does not work for refs.
That’s because ref is not really a prop — like key, it’s handled
specially by React. If you add a ref to an element whose component is
the result of a HOC, the ref refers to an instance of the outermost
container component, not the wrapped component.
The solution for this problem is to use the React.forwardRef API
(introduced with React 16.3). Learn more about it in the forwarding
refs section.
via Higher-Order Components: Refs Aren’t Passed Through
In the forwarding refs section there are code examples you could use to pass refs down, but trying to yank them up will fail in your case with:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
In a project we took a different approach. There's an EnhancedTable component that handles all of the pagination logic and in itself has the dumb table component and the pagination component. It works pretty well but this means you would have to drill props (or use a store lib like Redux or Mobx) and add new ones that will handle pagination options. This will result in some refactoring of Table uses and you'll have to be more explicit but I would take it as a boon rather than a hindrance.
I was able to solve a simmilar issue that brought me to this thread without using forwardRef or useImperativeHandle.
By creating the ref at a higher level, and passign it down into the component and sub components that I needed to act on with the ref.
/** Parent Component has access to ref and functions that act on ref **/
import { useRef } from 'react';
const formRef = useRef(); // ref will have dom elements need accessing
const onClickFunction=()=>{ //sample function acts on ref
var inputs = formRef.current.querySelectorAll('input')
/* Act on ref here via onClick function, etc has access to dom elements
in child component and childs child components */
};
return(
<ComponentGetsAttachedRef formRef={formRef} />
//^ref sent down to component and its children
<ComponentNeedingRef onClickFunction={onClickFunction}/>
//^function with access to ref sent down to component
)
/** Child component needs to act on ref**/
export const ComponentNeedingRef = ({ onClickFunction}) =>{
return(
<button onClick={onClickFunction}>
)
}
/* Child component recieves ref and passes it down */
export const ComponentGetsAttachedRef = ({ formRef}) =>{
//ref comes in as prop gets attached to props or utilized internally
return (
<ChildsChildComponent formRef={formRef}/> //sub component passed ref down
)
}

ReactJS using jquery for animations

I'm trying to render a component, but the value is inside a jQuery function that checks for changes in an input field.
The console returns this error:
Objects are not valid as a React child (found: [object HTMLDocument]).
If you meant to render a collection of children, use an array instead
or wrap the object using createFragment(object) from the React
add-ons. Check the render method of Result
the code:
import React, { Component } from 'react';
import Result from './Result';
import * as $ from 'jquery';
import ReactFM from '../lib/ReactFM';
import { config } from '../config';
export let reactfm = new ReactFM(config.apiKey);
let name = $(() => {
$('.input-search').keypress(() => {
let inp = $('.input-search').val();
return reactfm.searchArtists(inp);
});
});
class SearchResults extends Component {
render() {
return (
<div className="search-results">
<Result avatar="" name={name} desc="um deus" />
</div>
);
}
}
export default SearchResults;
There really is no good reason to use jQuery when you are using React.
If you are (and you should be) writing the html that contains the input element in JSX as a higher level React component, you can reference the input either by a ref attribute or add a keydown event listener to the input itself.
Example:
const HigherLevelParentComponent = React.createClass({
getDefaultState() {
searchQuery: ''
},
searchArtists(event) {
this.setState({
searchQuery: event.target.value
})
},
render() {
(
<div>
<input type="text" onKeyDown={(event) => this.searchArtists(event)}/>
<Result ... /> // You probably want this to be a mapped array of Result components with unique props instead
</div>
)
}
})
You will also want to include a function that searches through the searchable data and returns an array of objects that you could then render into Result components using mapping and props. How you do this will depend on where the searchable data is coming from (React store, database table on the back-end, etc.).
Also, you mentioned using jQuery for animations but did not elaborate.

Categories

Resources