Conditionally add function as prop in React? - reactjs

I need to conditionally add a function in React (Native).
This code works but presumably the onScroll event is needlessly called when the Platform is web, which I assume is not ideal for performance?
<ScrollView
onScroll={() => {
if(Platform.OS === 'web') {
return;
}
container.current?.onScroll();
}}
>
Is there a way to conditionally set the onScroll prop?

You can but it's probably more effort than it is worth in terms of performance gains. There is very little cost in calling a function that does nothing.
You can build the props as an object outside of JSX and pass them in using the spread operator, like so:
const scrollViewProps = {}
if (Platform.OS !== "web") {
scrollViewProps.onScroll = () => {
container.current?.onScroll()
}
}
<ScrollView {...scrollViewProps} />

You can use && to create the condition:
<ScrollView
onScroll={Platform.OS !== 'web' && () => container.current?.onScroll()}
>

Related

How do I conditionally render elements based off of path react.js

So I'm creating a blog app with react.js and ruby on rails. I have these buttons that are in my nav component that I need to conditionally render based off of the path the user is in. I'm using useLocation to accomplish this and almost have it where I want it. The problem I'm having is getting them to render in the three main paths where posts can be seen ('/general', '/resources', & '/events') while hiding them when a user goes into a post to view the comments. The buttons will show up in those paths if I remove the /path/id but as I stated I need them not to render in the /path/id just the /path. How can I accomplish this?
const [getAllPosts, setGetAllPosts] = useState([])
useEffect(() => {
const fetchData = async () => {
const res = await fetchAllPosts()
setGetAllPosts(res.filter(post => {
return post.category.title === 'general' || post.category.title === 'resources' || post.category.title === 'events'
}))
}
fetchData()
}, [])
return (
{getAllPosts.forEach((generalPost, resourcePost, eventsPost) => {return location.pathname === '/user' || location.pathname === '/about' || location.pathname === '/create' || location.pathname === `/general/${generalPost.id}` || location.pathname === `/general/${resourcePost.id}` || location.pathname === `/general/${eventsPost.id}` ? (<></>) : (<div className={open ? 'RegisterContainerMobileOpen' : 'RegisterContainerMobile'}>
{currentUser ? (
<button onClick={handleLogout} className='logoutmobile'>Logout</button>
) : (
<Link to='/user' className='resgisterlinkmobile'>
<button className='registermobile'>Signin/Signup</button>
</Link>
)}
</div>)})})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
If you need to see the rest of my code just let me know. I've been working day and night on this with little sleep so I feel I am missing something simple.
Edit: I've also tried using .map() but get back multiple instances of the same button since map returns a new array. I've looked into React.memo in my research, but I'm not fully sure how I'd use that or even if it would be a fix to the mapping issue
Note: after reading more documentation on React.memo it does not seem like that would help
Without going too deeply into the task, try to add return and instead of forEach use map. Remember that forEach returns nothing, unlike map.
So it could look like this:
useEffect(() => {
...
}
return getAllPosts.map(...

ReactJS how to memoize within a loop to render the same component

I have a component that creates several components using a loop, but I need to rerender only the instance being modified, not the rest. This is my approach:
function renderName(item) {
return (
<TextField value={item.value || ''} onChange={edit(item.id)} />
);
}
function renderAllNames(items) {
const renderedItems = [];
items.forEach(x => {
const item = React.useMemo(() => renderName(x), [x]);
renderedItems.push(item);
});
return renderedItems;
};
return (
<>
{'Items'}
{renderAllNames(names)};
</>
);
This yells me that there are more hooks calls than in the previous render. Tried this instead:
function renderAllNames(items) {
const renderedItems = [];
items.forEach(x => {
const item = React.memo(renderName(x), (prev, next) => (prev.x === next.x));
renderedItems.push(item);
});
return renderedItems;
};
Didn't work either... the basic approach works fine
function renderAllNames(items) {
const renderedItems = [];
items.forEach(x => {
renderedItems.push(renderName(x));
});
return renderedItems;
};
But it renders all the dynamic component everytime I edit any of the fields, so how can I get this memoized in order to rerender only the item being edited?
You're breaking the rules of hooks. Hooks should only be used in the top level of a component so that React can guarantee call order. Component memoisation should also really only be done using React.memo, and components should only be declared in the global scope, not inside other components.
We could turn renderName into its own component, RenderName:
function RenderName({item, edit}) {
return (
<TextField value={item.value || ''} onChange={() => edit(item.id)} />
);
}
And memoise it like this:
const MemoRenderName = React.memo(RenderName, (prev, next) => {
const idEqual = prev.item.id === next.item.id;
const valEqual = prev.item.value === next.item.value;
const editEqual = prev.edit === next.edit;
return idEqual && valEqual && editEqual;
});
React.memo performs strict comparison on all the props by default. Since item is an object and no two objects are strictly equal, the properties must be deeply compared. A side note: this is only going to work if edit is a referentially stable function. You haven't shown it but it would have to be wrapped in a memoisation hook of its own such as useCallback or lifted out of the render cycle entirely.
Now back in the parent component you can map names directly:
return (
<>
{'Items'}
{names.map(name => <MemoRenderName item={name} edit={edit}/>)}
</>
);

Store it in a useRef Hook and keep the mutable value in the '.current' property

Depending on one variable I want to implement conditional rendering. Source code:
My variable
let hideDeleteButton = false;
useEffect - to check value of the variable
useEffect(() => {
if (projects?.length === 0 || useCases?.length === 0) {
hideDeleteButton = false;
} else {
hideDeleteButton = true;
}
}, []);
Conditional rendering
const DeleteButton = () =>
hideDeleteButton ? (
<Tooltip
content={<TooltipContent title="The component belongs to either project or use case." />}
>
<span>
<Button disabled onClick={handleDelete} color="error" classes={{ root: classes.button }}>
{t('catalogPage.componentDetails.actionBar.deleteComponent')}
</Button>
</span>
</Tooltip>
) : (
<Button onClick={handleDelete} color="error" classes={{ root: classes.button }}>
{t('catalogPage.componentDetails.actionBar.deleteComponent')}
</Button>
);
return (
<ActionBar>
<DeleteButton />
</ActionBar>
);
And I got such kind of warning:
Assignments to the 'hideDeleteButton' variable from inside React Hook
useEffect will be lost after each render. To preserve the value over
time, store it in a useRef Hook and keep the mutable value in the
'.current' property. Otherwise, you can move this variable directly
inside useEffect
What sould I do exactly?
For any rendering related stuff, use a state instead of a ref. Changing a ref won't trigger a render.
let hideDeleteButton = false;
can be
const [hideDeleteButton,setHideDeleteButton] = useState(false);
and then in your useEffect :-
useEffect(() => {
const hideDeleteButton = projects?.length === 0 || useCases?.length === 0)?false:true
setHideDeleteButton(hideDeleteButton);
}, []);
Also important stuff - declaring simply let ,var or const variables isn't useful in React flow. In each render they will be reinitialized and lose their old values.
Here a ref is of no use since you want a conditional render. It would make sense where you don't want a value to participate in render flow but still want to remember that value between multiple renders for any other use case.

Handle button click with two different handler in different condition - ReactJS

I want to handle the click of my custom button component in two separate conditions.
This is the custom-button component:
function CustomButton({status, onStop, onClick}) {
return (
// ...
<button onClick={status ? onStop : onClick}/>
// ...
)
}
This code is just a simple example to explain my question. and I can't separate my component according to the value of status.
My question is, Is this an anti-pattern or bad practice in a component? If yes, what's the best practice to do?
It's not an anti-pattern and more like a bad practice, such code isn't maintainable for when more conditions and callbacks will be added.
Components should be simple, readable and reusable while using all provided props as possible (i.e don't add unused props to component's logic):
const NOOP = () => {}
function CustomButton({ onClick = NOOP }) {
return <button onClick={onClick}>Cool Button</button>;
}
Better practice is to handle condition in parent's logic:
function Parent() {
const onContinue = // ...
const onStop = // ...
return <CustomButton onClick={status ? onStop : onContinue} />;
}
I think that is better to pass a single callback onClick and handle the business logic inside it.
Create a function handleClick inside CustomButton component before return and use if-else blocks to achieve this functionality. It is the right way, according to me.
Create your custom button and use it into any place with any props
import React from 'react';
const CustomButton = ({ children, type = 'button', onClick }) => (
<button
type={type}
onClick={onClick}
>
{children}
</button>
);
const Parent = ({ status }) => {
const stop = useCallback(() => {}, []);
const onClick = useCallback(() => {}, []);
return (
status
? <CustomButton onClick={useCallback}>stop</CustomButton>
: <CustomButton onClick={onClick}>click</CustomButton>
);
}

Re-Rendering of ReactiveList Results

I am trying to build a Search-Engine using reactivesearch library. Does anybody know how i can trigger a re-rendering of the ReactiveList Results? (e.g. with a button onClick event?)
If you have a class component, please try below code
this.forceUpdate();
Unfortunately i can not provide my full code here. But i will try to explain what my problem is. Within my main App component i do have ReactiveBase, a DataSearch and ReactiveList as well as several buttons:
const App = () => (
<ReactiveBase
app="Test"
credentials="null"
url="http://localhost:9200"
analytics={false}
searchStateHeader
>
<DataSearch />
<ReactiveList
componentId="result"
dataField="_score"
renderItem={renderItem}
>
<div><Switch defaultChecked onChange={onSpeciesChange} style={{ marginRight: '5px', background: "brown" }} id="cellines"/> <label> Celline </label></div>
<div><Switch defaultChecked onChange={onSpeciesChange} style={{ marginRight: '5px', background: "blue" }} id="chemicals"/> <label> Chemicals </label></div>
So the buttons get rendered within my main App component and i do have a function onSpeciesChange, which basically updates a global object called entityswitchstatus with boolean values:
function onSpeciesChange(checked,event) {
if (event.target.id === "cellines") { entityswitchstatus.cellines=checked; }
else if (event.target.id === "chemicals") { entityswitchstatus.chemicals=checked; }
else if (event.target.id === "diseases") { entityswitchstatus.diseases=checked; }
else if (event.target.id === "genes") { entityswitchstatus.genes=checked; }
else if (event.target.id === "mutations") { entityswitchstatus.mutations=checked;}
else if (event.target.id === "species") { entityswitchstatus.species=checked; }
console.log(entityswitchstatus);
}
Within the renderItem function of the ReactiveList component i am processing the responses from Elasticsearch. And if there is a certain field and the global entityswitchstatus is true i do a highlighting of another field of the elasticsearch response. That all happens within renderItem function of ReactiveList.
function renderItem(res) {
if (res.ptc_species && entityswitchstatus.species) { var species_classname = createHighlighClassObject(res.ptc_species,"species"); } else { res.ptc_species = [] }
}
And basically by clicking the buttons i can change the global object entityswitchstatus of course. But this does not lead to a re-rendering of the ReactiveList component which is also expected. I can not pass any additional props to renderItem or at least i don't know how. So my idea was to simply call re-rendering of ReactiveList component by also clicking the button within the main App component.
Hope this is not too confusing.

Resources