why fast-compare not working but own isEqual function works? - reactjs

I use memo in my script. I dont know why my data rerenders when I use fast-compare (isEqual) and when I use my own function (isEq) then its not rerender. Why ?
import isEqual from 'react-fast-compare'
function isEq(prev: IColorItem, next: IColorItem) {
if(prev.activeColor === next.activeColor) {
return true;
}
}
// rerenders
const Item = memo(({ color, index, style, activeColor, onPress }: IColorItem) => {
console.log('render');
return (
<Button style={s.center} onPress={() => onPress(color)}>
<Text>{color}</Text>
</Button>
)
// #ts-ignore
}, isEqual);
// No rerender
const Item = memo(({ color, index, style, activeColor, onPress }: IColorItem) => {
console.log('render');
return (
<Button style={s.center} onPress={() => onPress(color)}>
<Text>{color}</Text>
</Button>
)
// #ts-ignore
}, isEq);
€:
Item:
const renderItem: ListRenderItem<ColorsType> = ({ item, index }) => (
<Item
color={item}
index={index}
style={style}
activeColor={activeColor}
onPress={onPress}
/>
)

Related

Send ref via props in functional component

In my parent component I call hook useRef: const flatListRef = useRef(null); and then I want to use this flatListRef in child component. I tried to do like in documentation but without success. When I call my function toTop I get: null is not an object (evaluating 'flatListRef.current.scrollToOffset')
This is my parent component:
const BeautifulPlacesCards = ({navigation}: HomeNavigationProps<"BeautifulPlacesCards">) => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1)
}
const buttonPressed = () => {
toTop()
}
return(
<Carousel filteredData={filteredData} flatListRef={flatListRef}/>
)
}
This is my child component:
const Carousel = forwardRef((filteredData, flatListRef) => {
return (
<AnimatedFlatList
ref={flatListRef}
/>
)
}
Here is a working example: https://snack.expo.dev/#zvona/forwardref-example
Key takes:
you need to use prop ref when passing it down, not flatListRef
you need to destructure filteredData from props
Here is the relevant code:
const Child = forwardRef(({ filteredData }, ref) => {
return (
<FlatList
ref={ref}
style={styles.flatList}
data={filteredData}
renderItem={({ item }) => (
<Text style={styles.item} key={`foo-${item}`}>
{item}
</Text>
)}
/>
);
});
const App = () => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1);
};
return (
<View style={styles.container}>
<Button title={'Scroll back'} onPress={toTop} />
<Child filteredData={[1,2,3,4,5,6]} ref={flatListRef} />
</View>
);
};

I have error `Cannot update a component (`Ne`) while rendering a different component (`hn`)` with using #brainhubeu/react-carousel library

I use #brainhubeu/react-carousel library for implementing carousel component in my App. Here is example of code:
export const Carousel = ({
options,
open,
onClose,
value,
onChange,
}: Props) => {
const [showSendWindow, setShowSendWindow] = useState<boolean>(false)
const handleCloseCarousel = useCallback(
(e: KeyboardEvent) => {
if (e.keyCode === 27) {
onClose(false)
}
},
[onClose],
)
useEffect(() => {
document.addEventListener('keydown', handleCloseCarousel)
return () => {
document.removeEventListener('keydown', handleCloseCarousel)
}
}, [handleCloseCarousel])
return open ? (
<Backdrop>
{showSendWindow && (
<SendFileModal onClose={() => setShowSendWindow(false)} />
)}
<SliderWrapper>
<Close onClick={() => onClose(false)}>
<Icon color='#FFFFFF' height={25} type='Cross' width={25} />
</Close>
<ImageCarousel
animationSpeed={0}
plugins={[
{
resolve: arrowsPlugin,
options: Arrows(options),
},
]}
value={value}
onChange={onChange}
>
{options.map((e, index) => (
<Wrapper key={index}>
<Image image={e.url}>
<Dropdown
overlay={() =>
FileDropdow(e.url, e.name, () => setShowSendWindow(true))
}
placement='bottomRight'
>
<Dots>•••</Dots>
</Dropdown>
</Image>
<Description>{e.comment}</Description>
</Wrapper>
))}
</ImageCarousel>
</SliderWrapper>
</Backdrop>
) : null
}
I found out that problem is in ImageCarousel component, because if delete it, then warning disappears, but Carousel doesn't work properly. I get next warning in console:
Warning: Cannot update a component (Ne) while rendering a different
component (hn). To locate the bad setState() call inside hn
How can I avoid it?

How do we stop the re-rendering of A and B in React?

import React, { useState, useEffect, useCallback } from "react";
export const Root = () => {
const [items, setItems] = useState(["A", "B"]);
const _onClick = useCallback( item => {
return () =>alert(item);
},[]);
return (
<>
<button onClick={() => setItems(["A", "B", "C"])}>Button</button>
{items.map((item, index) => (
<Item key={index} item={item} onClick={_onClick(item)} />
))}
</>
);
};
const Item = React.memo(({ item, onClick }) => {
useEffect(() => {
console.log("Item: ", item);
});
return <button onClick={onClick}>{item}</button>;
});
How do we stop the re-rendering of A and B?
The result I want is to be a memo on the console when the button is pressed and "Item: C".
Because onClick of <Item/> is new every time it is rendered, it will cause A and B to re-render.
You can use React.memo second parameter to check, for example:
const Item = React.memo(({ item, onClick }) => {
// ...
return <button onClick={onClick}>{item}</button>;
}, (prevProps, nextProps) => {
console.log(Object.is(prevProps.onClick, nextProps.onClick)); // console: false
});
More see doc.
In your code, _onClick(item) will return new callback every render.
<Item key={index} item={item} onClick={_onClick(item)} />
You can change _onClick to this:
const _onClick = useCallback(item => alert(item), []);
Next, pass _onClick to Item, and change how button's onClick is executed.
<Item key={index} item={item} onClick={_onClick} />
//...
<button onClick={() => onClick(item)}>{item}</button>
The full code is as follows:
import React, { useCallback, useState } from 'react';
export const Root = () => {
const [items, setItems] = useState(['A', 'B']);
const _onClick = useCallback(item => alert(item), []);
return (
<>
<button onClick={() => setItems(['A', 'B', 'C'])}>Button</button>
{items.map((item, index) => (
<Item key={index} item={item} onClick={_onClick} />
))}
</>
);
};
const Item = React.memo(({ item, onClick }) => {
useEffect(() => {
console.log("Item: ", item);
});
return <button onClick={() => onClick(item)}>{item}</button>;
});
You were calling _onClick from the wrong place. Rather than calling on the Item component, you should call on the button's onClick event.
Check these working Code Sandbox.

Using React Hooks reference with Class and Function

I've been out of the React game for awhile. Come back and I'm trying to implement the Material UI library which has been rewritten with Hooks.
It seems to be extremely confusing + spagetti code in my eyes.
I simply want to reference a function so I can toggle the drawer, how can I do this?
// Old class
export default class DefaultContainer extends Component<ViewProps, any> {
render() {
return (
<View>
<MainAppBar
onPress={() => this.onMenuPressed()}
/>
{this.props.children}
<MainDrawer
ref={'drawer'}
/>
</View>
);
}
onMenuPressed = () => {
// TODO The bit that isn't working
(this.refs['drawer'] as Drawer).handleToggle()
}
}
Now the new material UI drawer
// New Drawer (3x more code now..)
const useStyles = makeStyles({
list: {
width: 280,
},
fullList: {
width: 'auto',
},
})
type Props = {
}
function MainDrawer(props: Props, ref: any) {
const classes = useStyles();
const [state, setState] = React.useState({
left: false,
});
const toggleDrawer = () => (
event: React.KeyboardEvent | React.MouseEvent,
) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Tab' ||
(event as React.KeyboardEvent).key === 'Shift')
) {
return;
}
setState({ ...state, left: true });
};
const inputRef = useRef();
useImperativeHandle(ref, () => {
toggleDrawer()
});
const sideList = () => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer()}
onKeyDown={toggleDrawer()}
>
<List>
<ListItem button key={'drawer_item'}>
<ListItemIcon><GroupIcon /></ListItemIcon>
<ListItemText primary={'Test Item'} />
</ListItem>
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer()}>Open Left</Button>
<Drawer open={state.left} onClose={toggleDrawer()}>
{sideList()}
</Drawer>
</div>
);
}
export default forwardRef(MainDrawer);
I'm struggling to understand why you need to invoke a function from inside MainDrawer rather than just leveraging the use of props e.g.
Container
export default function DefaultContainer(props: ViewProps) {
const [drawerOpen, setDrawerOpen] = React.useState(false);
// assuming it's a toggle?
const toggleMenu = React.useCallback(() => setDrawerOpen(open => !open));
return (
<View>
<MainAppBar onPress={toggleMenu} />
{this.props.children}
<MainDrawer open={drawerOpen} />
</View>
)
}
MainDrawer
function MainDrawer(props: Props) {
const [open, setOpen] = React.useState(props.open);
...
const toggleDrawer = React.useCallback(() => setOpen(open => !open));
return (
<div>
<Button onClick={toggleDrawer}>Open Left</Button>
// use prop to determine whether drawer is open or closed
<Drawer open={open} onClose={toggleDrawer}>
{sideList()}
</Drawer>
</div>
);
}

Sending data from Child to Parent React

I have subdivided my components and I want to change state of text using deleteName function from child component. However I have used onPress={this.props.delete(i)} in my child component which is not working. The error that occurs for me is:
undefined variable "I"
Here is my code:
App.js
export default class App extends Component {
state = {
placeName: '',
text: [],
}
changeName = (value) => {
this.setState({
placeName: value
})
}
deleteName = (index) => {
this.setState(prevState => {
return {
text: prevState.text.filter((place, i) => {
return i!== index
})
}
}
}
addText = () => {
if (this.state.placeName.trim === "") {
return;
} else {
this.setState(prevState => {
return {
text: prevState.text.concat(prevState.placeName)
};
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Input changeName={this.changeName}
value={this.state.placeName} />
<Button title="Send" style={styles.inputButton}
onPress={this.addText} />
</View>
<ListItems text={this.state.text} delete={this.deleteName}/>
{/* <View style={styles.listContainer}>{Display}</View> */}
</View>
);
}
}
and child component ListItems.js
const ListItems = (props) => (
<View style={styles.listitems}>
<Text>{this.props.text.map((placeOutput, i) => {
return (
<TouchableWithoutFeedback
key={i}
onPress={this.props.delete(i)}>
onPress={this.props.delete}
<ListItems placeName={placeOutput}/>
</TouchableWithoutFeedback>
)
})}
</Text>
</View>
);
You need to bind the index value at the point of passing the props to the child.
delete = index => ev => {
// Delete logic here
}
And in the render function, you can pass it as
items.map((item, index) => {
<ChildComponent key={index} delete={this.delete(index)} />
})
In your child component, you can use this prop as
<button onClick={this.props.delete}>Click me</button>
I have created a Sandbox link for your reference
Instead of onPress={this.props.delete(i)}, use onPress={() => this.props.delete(i)}
In order to have the cleaner code, you can use a renderContent and map with }, this);like below. Also you need to use: ()=>this.props.delete(i) instead of this.props.delete(i) for your onPress.
renderContent=(that)=>{
return props.text.map((placeOutput ,i) => {
return (
<TouchableWithoutFeedback key={i} onPress={()=>this.props.delete(i)}>
onPress={this.props.delete}
</TouchableWithoutFeedback>
);
}, this);
}
}
Then inside your render in JSX use the following code to call it:
{this.renderContent(this)}
Done! I hope I could help :)

Resources