Will React memoize component created during children.map? - reactjs

If I have a setup like the following:
const ChildComponent = React.memo((props) => {
return (<>...</>);
});
const ParentComponent = (props) => {
return (
{React.Children.map(props.children, child => {
return <div>{child}</div>;
)}
);
}
Assuming child.props are identical, will React know to memoize child or will it rerender each time?

Changed the setup to the following:
const ChildComponent = React.memo((props) => {
return (<p>{props.a}</p>);
}, (prev, next) => {
if (prev.a === next.a) { console.log('memo'); return true; }
console.log('no memo'); return false;
});
const ParentComponent = (props) => {
return (
{React.Children.map(props.children, child => {
return <div>{child}</div>;
)}
);
}
const App = () => {
return (
<ParentComponent>
<ChildComponent a='a'/>
<ChildComponent a='b'/>
<ChildComponent a='c'/>
</ParentComponent>
);
}
And I see memo in console. This doesn't seem to be like an exhaustive answer by any means, but I'd assume this means it works.

Related

passing ref from parent functional component to child class component to call a function in child class component

I have a parent functonal component:
const parentFunc = () => {
if (ref.current) {
ref.current.getKinList();
}
};
<TouchableOpacity
onPress={() => {parentFunc()}
>
<Text>click</Text>
</TouchableOpacity>
<ChildComponent
ref={ref}
/>
child class component:
componentDidMount = () => {
this.ref = { current: { function2 : this.function2 } };
};
function2 = () => {
console.log('called from child');
};
function2 is not getting called from parent component.
There are solutions available, but I am not able to figure out where I am going wrong.
When I consoled ref.current in parentFunc it is coming as undefined
You can do something like this:
export default function App() {
const actions = React.useRef({
setMyAction: (f) => {
actions.current.myAction = f;
}
});
return (
<div>
<div onClick={() => actions.current.myAction()}>click</div>
<ChildComponent actions={actions.current} />
</div>
);
}
const ChildComponent = ({ actions }) => {
actions.setMyAction(() => {
console.log("called from child");
});
return null;
};
Working example
Also keep in mind that ref is a special name, not a usual property.

How do I make sure that the parent state is also updated when child state is updated?

I have a architecture of a React application as follows:
<GrandParentComponent>
<ParentComponent>
<ChildComponent>
</ParentComponent>
</GrandParentComponent>
The child component is creating a context within itself to be shared with all its internal components.
I have a ref being passed as a useCallback() from the <ParentComponent> to the <ChildComponent> to keep track of a state which is being passed from the <GrandparentComponent>.
GrandParent
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
{ isPlaying ? "True" : "False" }
<ParentComponent {...{isPlaying, setIsPlaying}}/>
</>
);
Parent
const [button, setButton] = useState();
const handleRef = useCallback( current => {
if(current) {
setButton(current.button);
}
}, []);
useEffect(() => {
if(button) {
const callback = ({type}) => setIsPlaying(type === "play");
button.addEventListener("play", callback);
return () => {
button.removeEventListener("play", callback);
};
}
}, [button]);
return (
{ isPlaying ? "True" : "False" }
<ChildRoot>
<Child ref={handleRef}/>
</ChildRoot>
);
Child
const export ChildRoot = ({children}) => {
return (
<ChildContext.Provider value={{
isOpen: true,
setIsOpen: () => {}
}}
/>
{children}
</ChildContext.Provider>
);
};
const export Child = ({}, forwardRef) => {
const ref = useRef();
useImperativeHandle(forwardRef, () => ({
get button() {
return ref.current();
}
})
});
For some reason the value of isPlaying is only reflected in the <ParentComponent> but not in the <GrandParentComponent>. I've tried using a useContext() and that hasn't solved this either.
How do I make sure that the state is consistent?
I fixed this by combining both the Child components into one component, and exporting it to the Parent component.

React.js - call child function from parent

Is it possible to call a function of a child component on a direct way without creating a 'helper'-function on the top-level?
Can you explain me the right way to pass this problem?
export const AppComponent = () => {
return (
<ParentDiv>
<ChildDiv />
<ParentDiv />
);
}
const ParentDiv = (props) => {
return (<div>Parent{props.children}<button>do something...</button></div>);
}
const ChildDiv = (props) => {
const submitButton = () => {
console.log('do something...');
}
return (<div>Child</div>);
}

How to access refs with react-sortable-hoc, withref

I tried using the "withref" in react-sortable-hoc and I need to have my parent component access the children components for some calls I need to invoke on the parent side. I'm not sure where to even call the getWrappedInstance, which seems to be providing access to the children component.
I'm aware of forwarding but it seems like react-sortable-hoc have a different implementation.
To be more specific, I have something like this:
const SortableItem = SortableElement((props) => (
<div className="sortable">
<MyElement {...props}/>
</div>
), {withRef: true});
const MidasSortableContainer = SortableContainer(({ children }: { children: any }) => {
return <div>{children}</div>;
}, {withRef: true});
<MySortableContainer
axis="xy"
onSortEnd={this.onSortEnd}
useDragHandle
>{chartDivs}</MySortableContainer>
Before I wrapped in HOC, I was able to do the following
const chartDivs = elements.map(({childName}, index) => {
return <MyElement
ref={r => this.refsCollection[childName] = r}
...
Does anyone have any ideas how to achieve the same after wrapping with HOC? Thanks.
The key is from source code: https://github.com/clauderic/react-sortable-hoc/blob/master/src/SortableElement/index.js#L82
getWrappedInstance() function.
I guess, after is your origin code:
// this is my fake MyElement Component
class MyElement extends React.Component {
render () {
return (
<div className='my-element-example'>This is test my element</div>
)
}
}
// this is your origin ListContainer Component
class OriginListContainer extends React.Component {
render () {
const elements = [
{ childName: 'David' },
{ childName: 'Tom' }
]
return (
<div className='origin-list-container'>
{
elements.map(({ childName }, index) => {
return <MyElement key={index} ref={r => this.refsCollection[childName] = r} />
})
}
</div>
)
}
}
Now you import react-sortable-hoc
import { SortableElement, SortableContainer } from 'react-sortable-hoc'
First you create new Container Component:
const MySortableContainer = SortableContainer(({ children }) => {
return <div>{children}</div>;
})
Then make MyElement be sortable
/**
* Now you have new MyElement wrapped by SortableElement
*/
const SortableMyElement = SortableElement(MyElement, {
withRef: true
})
Here is import:
You should use SortableElement(MyElement, ... to SortableElement((props) => <MyElement {...props}/>, second plan will make ref prop be null
{ withRef: true } make your can get ref by getWrappedInstance
OK, now you can get your before ref like after ref={r => this.refsCollection[childName] = r.getWrappedInstance()} />
Here is full code:
const MySortableContainer = SortableContainer(({ children }) => {
return <div>{children}</div>;
})
/**
* Now you have new MyElement wrapped by SortableElement
*/
const SortableMyElement = SortableElement(MyElement, {
withRef: true
})
class ListContainer extends React.Component {
refsCollection = {}
componentDidMount () {
console.log(this.refsCollection)
}
render () {
const elements = [
{ childName: 'David' },
{ childName: 'Tom' }
]
return (
<MySortableContainer
axis="xy"
useDragHandle
>
{
elements.map(({ childName }, index) => {
return (
<SortableMyElement
index={index}
key={index}
ref={r => this.refsCollection[childName] = r.getWrappedInstance()} />
)
})
}
</MySortableContainer>
)
}
}
Append
Ehh...
before I said: You should use SortableElement(MyElement, ... to SortableElement((props) => <MyElement {...props}/>, second plan will make ref prop be null
if you really wanna use callback function, you can use like after:
const SortableMyElement = SortableElement(forwardRef((props, ref) => <MyElement ref={ref} {...props} />), {
withRef: true
})
But here NOT the true use of forwardRef
Ehh... choose your wanna.

Switching between two components in React

rotateRender() {
if(false) {
return(
<TimerPage></TimerPage>
);
} else {
return(
<RepoPage></RepoPage>
);
}
}
I have two components called TimerPage and RepoPage.
I created a simple conditional render function as above, but cannot come up with a condition to make it render iteratively after a certain amount of time.
For example, I first want to render RepoPage and switch to TimerPage after 5 minutes and then stay in TimerPage for 15 mins before I switch again to the RepoPage.
Any way to do this?
Might not be that elegant, but this works
Actually I was thinking that this block might be more elegant than the first one
const FIRST_PAGE = '5_SECONDS';
const SECOND_PAGE = '15_SECONDS';
const FirstComponent = () => (
<div>5 SECONDS</div>
);
const SecondComponent = () => (
<div>15 SECONDS</div>
);
class App extends Component {
state = {
currentPage: FIRST_PAGE
};
componentDidUpdate() {
const {currentPage} = this.state;
const isFirst = currentPage === FIRST_PAGE;
if (isFirst) {
this._showSecondPageDelayed();
} else {
this._showFirstPageDelayed();
}
}
componentDidMount() {
this._showSecondPageDelayed();
};
_showSecondPageDelayed = () => setTimeout(() => {this.setState({currentPage: SECOND_PAGE})}, 5000);
_showFirstPageDelayed = () => setTimeout(() => {this.setState({currentPage: FIRST_PAGE})}, 15000);
render() {
const {currentPage} = this.state;
const isFirst = currentPage === FIRST_PAGE;
const ComponentToRender = isFirst ? FirstComponent : SecondComponent;
return <ComponentToRender/>;
}
}
As stated in the comment section, you can create a higher order component that will cycle through your components based on the state of that component. Use setTimeout to handle the timer logic for the component.
state = {
timer: true
}
componentDidMount = () => {
setInterval(
() => {
this.setState({ timer: !this.state.timer })
}, 30000)
}
render(){
const {timer} = this.state
if(timer){
return <TimerPage />
} else {
return <RepoPage />
}
}
Edit
Changed setTimeout to setInterval so that it will loop every 5 minutes instead of just calling setState once
You could use the new context API to achieve this. The benefit is now I have a configurable, reusable provider to play with throughout my application. Here is a quick demo:
https://codesandbox.io/s/k2vvy54r8o
import React, { Component, createContext } from "react";
import { render } from "react-dom";
const ThemeContext = createContext({ alternativeTheme: false });
class ThemeWrapper extends Component {
state = {
alternativeTheme: false
};
themeInterval = null;
componentDidMount() {
this.themeInterval = setInterval(
() =>
this.setState(({ alternativeTheme }) => ({
alternativeTheme: !alternativeTheme
})),
this.props.intervalLength
);
}
componentWillUnmount() {
if (this.themeInterval) {
clearInterval(this.themeInterval);
}
}
render() {
return (
<ThemeContext.Provider value={this.state}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
const App = () => (
<ThemeWrapper intervalLength={2000}>
<ThemeContext.Consumer>
{({ alternativeTheme }) =>
alternativeTheme ? <p>Alternative Theme</p> : <p>Common Theme</p>
}
</ThemeContext.Consumer>
</ThemeWrapper>
);
render(<App />, document.getElementById("root"));
Whatever you do make sure on componentWillUnmount to clear your interval or timeout to avoid a memory leak.

Resources