React - Predicate functional component - reactjs

I created a react functional component to wrap sections of code based on a predicate (instead of using ternary within JSX).
So, the component looks like this:
const PredicateWrapper = ({ children, iff, otherwise }) => (
iff ? children : (otherwise || null)
);
And can be used like this:
<PredicateWrapper iff={students}>
{students.map(student => <Student />)}
</PredicateWrapper>
The issue is that this throws an error "cannot read property map of null" if students is null. If I replace the students.map with some text, the text does not render, proving the wrapper is working, however, the point of the wrapper is to deal with cases where students is null, and I would expect it not to "enter" inside the wrapper.
How can I achieve something like this that allows for the inner code to not evaluate and throw runtime errors if iff is falsy?

Really interesting question!
I could be woefully wrong here but I think it's because students.map(student => <Student /> is still part of your parent component's render tree. So when React tries to build the parent component's tree, it's trying to execute that line which throws an error.
PredicateWrapper is mounted and resolved when the child components are recursed upon, which is when the iff would kick in - but that's on the second iteration.
For eg, if I do this, I don't get the error
const Child = ({ students }) => {
return students.map(student => <Student />);
};
<PredicateWrapper iff={students}>
<Child students={students} />
</PredicateWrapper>

Let's have a look at how the JSX will be compiled to JavaScript with the online Babel compiler:
const PredicateWrapper = ({ children, iff, otherwise }) =>
iff ? children : otherwise || null;
const students = null;
const App = () =>
React.createElement(
PredicateWrapper,
{
iff: students,
},
students.map((student) => React.createElement(Student, null))
);
Now you can see students.map will be executed and cause the error "Cannot read property map of null"
One possible implementation to achieve your goal is to use render props:
The term “render prop” refers to a technique for sharing code between
React components using a prop whose value is a function.
const PredicateWrapper = ({ render, iff, otherwise = () => null }) =>
iff ? render() : otherwise();
const StudentA = () => <span>😎</span>;
const StudentB = () => <span>😂</span>;
const students = [StudentA, StudentB];
//const students = null;
function App() {
return (
<PredicateWrapper
iff={students}
render={() => students.map(Student => <Student />)}
/>
);
}

Related

Create Dynamic Components

I want to dynamically create a component, when I implement something like this:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const App = () => {
const Comp = gen_Comp("Hello");
return (
<Comp txt="World" />
);
}
Something goes wrong (what exactly goes wrong is hard to explain because it's specific to my app, point is that I must be doing something wrong, because I seem to be losing state as my component gets rerendered). I also tried this with React.createElement, but the problem remains.
So, what is the proper way to create components at runtime?
The main way that react tells whether it needs to mount/unmount components is by the component type (the second way is keys). Every time App renders, you call gen_Comp and create a new type of component. It may have the same functionality as the previous one, but it's a new component and so react is forced to unmount the instance of the old component type and mount one of the new type.
You need to create your component types just once. If you can, i recommend you use your factory outside of rendering, so it runs just when the module loads:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const Comp = gen_Comp("Hello");
const App = () => {
return (
<Comp txt="World" />
);
}
If it absolutely needs to be done inside the rendering of a component (say, it depends on props), then you will need to memoize it:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const App = ({ spec }) => {
const Comp = useMemo(() => {
return gen_Comp(spec);
}, [spec]);
return (
<Comp txt="World" />
);
}

Conditionally assign ref in react

I'm working on something in react and have encountered a challenge I'm not being able to solve myself. I've searched here and others places and I found topics with similar titles but didn't have anything to do with the problem I'm having, so here we go:
So I have an array which will be mapped into React, components, normally like so:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr
return (<>
{arr.map((item, id) => {<ChildComponent props={item} key={id}>})}
</>)
}
but the thing is, there's a state in the parent element which stores the id of one of the ChildComponents that is currently selected (I'm doing this by setting up a context and setting this state inside the ChildComponent), and then the problem is that I have to reference a node inside of the ChildComponent which is currently selected. I can forward a ref no problem, but I also want to assign the ref only on the currently selected ChildComponent, I would like to do this:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and there's a state which holds the id of a selected ChildComponent called selectedObjectId
const selectedRef = createRef();
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={selectedObjectId == id ? selectedRef : null}
>
})}
<someContextProvider />
</>)
}
But I have tried and we can't do that. So how can dynamically assign the ref to only one particular element of an array if a certain condition is true?
You can use the props spread operator {...props} to pass a conditional ref by building the props object first. E.g.
export default ParentComponent = () => {
const selectedRef = useRef(null);
return (
<SomeContextProvider>
{arr.map((item, id) => {
const itemProps = selectedObjectId == id ? { ref: selectedRef } : {};
return (
<ChildComponent
props={item}
key={id}
{...itemProps}
/>
);
})}
<SomeContextProvider />
)
}
You cannot dynamically assign ref, but you can store all of them, and access by id
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and theres a state wich holds the id of a selected ChildComponent called selectedObjectId
let refs = {}
// example of accessing current selected ref
const handleClick = () => {
if (refs[selectedObjectId])
refs[selectedObjectId].current.click() // call some method
}
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={refs[id]}
>
})}
<someContextProvider />
</>)
}
Solution
Like Drew commented in Medets answer, the only solution is to create an array of refs and access the desired one by simply matching the index of the ChildElement with the index of the ref array, as we can see here. There's no way we found to actually move a ref between objects, but performance cost for doing this should not be relevant.

When are components defined in functions evaluated? (React Hooks)

Suppose I have a component that renders a list item:
const ListItem = ({ itemName }) => {
return (
<div>
{itemName}
</div>
)
}
And because this list item is used in many places in my app, I define a custom hook to render the list and control the behavior of each list instance:
const useListItems = () => {
const [ showList, setShowList ] = useState(true)
const { listItemArray, isLoaded } = useListContext() // Context makes api call
const toggleShowList = setShowList(!showList)
function renderListItems() {
return isLoaded && !!listItemArray ? listItemArray.map((itemName, index) => (
<ListItem key={index} isVisible={showList} itemName={itemName}/>
))
:
null
}
// Some other components and logic...
return {
// ...Other components and logic,
renderListItems,
toggleShowList,
}
}
My first question is, when will the array of ListItems actually be evaluated ? Will the jsx resulting from renderListItems() be calculated every time renderListItems() is called? Or would it happen every time useListItems() is called?
Second question: if I call useListItems() in another component but don't call renderListItems(), does that impact whether the components are evaluated?
I have been struggling to find an answer to this, so thanks in advance.

Why React renders props.children even when it can be avoided?

How exactly react works with children props?
An example below Demo component will fail with NPE on the someArray iteration when it is null. Idea behind Wrapper component is to protect the case when there is no data - here I just show message 'working...' but normally it can be extended to anything, spinner for example, or progress bar. Also, in this example, even if I'll fix it with:
{someArray && someArray.map((arrayValue) => <li>{arrayValue}</li>)}
In case of someArray null react WILL do unnecessary rendering of everyhing inside Wrapper which is not very smart.
The problem as I see it is that react always renders children of Wrapper component, even if it can skip it. What stops react from having children as function that does render it content on demand instead?
const Demo = () => {
const someArray = null; // will fail with NPE
//const someArray = ['aaa', 'bbb', 'ccc']; // will render content
return(
<Wrapper isWorking={someArray === null}>
<h1>Inside</h1>
<ul>
{someArray.map((arrayValue) => <li>{arrayValue}</li>)}
</ul>
</Wrapper>
);
}
const Wrapper = ({ isWorking, children }) => {
if( isWorking ){
return <div>working...</div>
}else{
return <div>{children}</div>
}
}

React - Decorating Children and Handling Keys

I'm writing a small component that decorates some children. Given the following sample:
const Child = props => <div>{props.name}</div>;
const Parent = props => {
let wrappedChildren = React.Children.map(props.children, c => {
return (<Decorator key={c.key}>
{c}
</Decorator>);
});
return (<div>
{wrappedChildren}
</div>);
}
const Consumer = props => {
let children = [0, 1, 2].map(num => {
return <Child key={num} name={num} />;
});
return <Parent>{children}</Parent>;
});
In this code, I'm wanting to take each child and decorate it with some wrapping container or some behaviour. Forgetting for the moment that there may only be one child, I need to give each instance a key.
Currently I'm assuming that each child does have a key which isn't fantastic, lifting it off the child and applying it to the Decorator directly.
Is this the "correct" way of doing this? Is there a better way?
I think your approach is fine. And you do need the key on the top level. Use the child's key, if it is there. If not, fall back to the index, as React recommends:
When you don't have stable IDs for rendered items, you may use the item index as a key as a last resort.
Be advised though:
We don't recommend using indexes for keys if the items can reorder, as that would be slow.
Source: React Docs about keys
const Child = props => <div>{props.name}</div>;
const Parent = props => {
let wrappedChildren = React.Children.map(props.children, (c, i) => {
const key = c.key ? `key-${c.key}` : `index-${i}`
return (
<Decorator key={key}>
{c}
</Decorator>
);
});
return (
<div>
{wrappedChildren}
</div>
);
};
const Consumer = () => {
let children = [ 0, 1, 2 ].map(num => {
return <Child key={num} name={num} />;
});
return <Parent>{children}</Parent>;
};
That would work with the current version of React, 15.6.1 (and probably with prior versions as well). However, there is a slightly better way to achieve your goal with a small tweak, which would be delegating the lifting on a prop, rather than using directly the key.
The reason is that the concept of a key is something that is controlled by React internals before your component gets created, so it falls into the implementation details category, with the consequence that future versions of React could break your code without you even noticing it.
Instead, you can use a prop on your Child component, for instance id, based on your assumption that each child does have some sort of unique value. Your code would result in:
const Child = props => <div>{props.name}</div>;
const Parent = props => {
return React.Children.map(props.children, c => {
return (<Decorator key={c.props.id}>
{c}
</Decorator>);
});
return (<div>
{wrappedChildren}
</div>);
}
const Consumer = props => {
let children = [0, 1, 2].map(num => {
return <Child id={num} name={num} />;
});
return <Parent>{children}</Parent>;
});
If you have a more complex structure and want to avoid passing props individually, the parent component should held the responsibility of generating unique ids for the children. You can follow the ideas explained in this answer https://stackoverflow.com/a/29466744/4642844.
Those are the steps I'd follow:
Implement your own utility function or use an existing browser library to generate unique ids.
Implement componentWillMount in your Parent component to create an array of unique ids. You can use an internal member class variable instead of local state. It would be something like:
componentWillMount() {
this.uuids = React.Children.map(() => uuid());
}
Inside your Parent render, assign each key to the Decorator component in the following way.
render() {
return React.Children.map(props.children, (c, i) => {
return (<Decorator key={this.uuids[i]}>
{c}
</Decorator>);
});
}
Some useful links:
http://mxstbr.blog/2017/02/react-children-deepdive/
https://medium.com/#dan_abramov/react-components-elements-and-instances-90800811f8ca
Javascript Array.map(fn) passes actually 3 arguments to fn second being an index. So, what you need to do is:
let wrappedChildren = props.children.map((c, i) => <Decorator key={i}>{c}</Decorator>);

Resources