CODE IN CODEOPEN.IO
JS
import React, { useEffect, useCallback, useMemo, useState } from "https://cdn.skypack.dev/react#17.0.1";
import { render, createPortal } from "https://cdn.skypack.dev/react-dom#17.0.1";
const calcu=()=>{
return(
<>
<button onClick={close}>Close</button>
<div>HOLAAAA</div>
</>
)
}
const App = () => {
const [isOpen, setOpenState] = useState(false);
const open = useCallback(() => setOpenState(true));
const close = useCallback(() => setOpenState(false));
return (
<div>
<h1>Portals in React</h1>
<button onClick={open}>Open</button>
<button onClick={close}>Close</button>
{isOpen && (
<NewWindow close={close}>
<calcu></calcu>
</NewWindow>
)}
</div>
);
};
const NewWindow = ({ children, close }) => {
const newWindow = useMemo(() =>
window.open(
"about:blank",
"newWin",
`width=400,height=300,left=${window.screen.availWidth / 2 -
200},top=${window.screen.availHeight / 2 - 150}`
)
);
newWindow.onbeforeunload = () => {
close();
};
useEffect(() => () => newWindow.close());
return createPortal(children, newWindow.document.body);
};
render(<App />, document.getElementById("root"));
HTML
<div id="root"></div>
I dont know how to invoke another component. Is there a way to display the content of the calcu component?
Actually I need to send props to that child component with which I will render another component
Related
I have two components, the parent and child. Currently I have these codes below. But unfortunately it returns an error:
TypeError: Cannot read property 'click' of null
For some reasons I want when button is click the Item component also will be click. But these codes below produces an error above. Anyone does know how to achieve it?
import React, { useRef } from 'react';
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
{dynamicBoolean ? (
<button onClick={() => itemRef.current.click()}>
click item
</button>
) : (
//more codes here
<Item ref={itemRef} />
)}
</div>
);
};
export default App;
Child component would look like below (demonstration purposes, the code is very lengthly)
import React from 'react';
const Item = (props) => {
return (
<div>
//some design here
</div>
);
};
export default Item;
You need useRef and you have to forward this ref to the Item component.
import React, { forwardRef, useRef } from 'react';
const Item = forwardRef((props, ref) => {
return <li {...props}
onClick={() => alert('clicked on Item')}
ref={ref} >MyItem</li>
})
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
<button onClick={() => itemRef.current.click()}>
click item
</button>
<Item ref={itemRef} />
</div>
);
};
export default App;
import React, { createRef } from "react";
const Hello = (props) => {
const itemRef = createRef();
const hello = () => {
itemRef.current.click();
};
return (
<div>
<button onClick={() => hello()}>click item</button>
<Item ref={itemRef} />
</div>
);
};
const Item = React.forwardRef((props, ref) => {
const myClick = () => {
console.log("this is clicked");
};
return (
<button ref={ref} className="FancyButton" onClick={myClick}>
{props.children}
</button>
);
});
export default Hello;
like this i hava a array of components need ref to trigger the comment component collapse, so i need to create some refs to reference each commentListItem, but it doesn't work, how do i do this work?
import React, { useRef, createRef } from "react";
import PropTypes from "prop-types";
import { map, isArray } from "lodash/fp";
import Divider from "#material-ui/core/Divider";
import CommentListItem from "./CommentListItem";
import CommentCollapse from "./CommentCollapse";
function CommentList({ list = [], ...props }) {
const { count = 0 } = props;
const refList = map((o) => {
/* o.ref = createRef(null); */
return o;
})(list);
const onShow = () => {
console.log(refList);
};
return (
<div className="ke-comment-list">
{map.convert({ cap: false })((o, i) => (
<div key={i} className="ke-comment-list-item">
<CommentListItem listItem={o} onShow={onShow} />
{isArray(o.child) && o.child.length ? (
<CommentCollapse {...o}>
<CommentList list={o.child} count={count + 1} />
</CommentCollapse>
) : null}
{count > 0 && list.length - 1 === i ? null : <Divider />}
</div>
))(refList)}
</div>
);
}
CommentList.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export default CommentList;
there is CommentCollapse component for show or hide subcomment.
import React, { useState, forwardRef, useImperativeHandle } from "react";
import ButtonBase from "#material-ui/core/ButtonBase";
import Collapse from "#material-ui/core/Collapse";
const CommentCollapse = ({ children }, ref) => {
const [show, setShow] = useState(false);
const showMore = () => {
setShow((prev) => !prev);
};
const collapseText = () => (show ? "收起" : "展开");
useImperativeHandle(ref, () => ({
showMore: showMore()
}));
return (
<div className="ke-comment-list-children">
<Collapse in={show}>{children}</Collapse>
<ButtonBase size="small" onClick={showMore}>
{collapseText()}
</ButtonBase>
</div>
);
};
export default forwardRef(CommentCollapse);
catch errors
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
have any idear for this situation?
is fixed, just not trigger showMore function in ref.
import React, { useState, forwardRef, useImperativeHandle } from "react";
import ButtonBase from "#material-ui/core/ButtonBase";
import Collapse from "#material-ui/core/Collapse";
const CommentCollapse = ({ children }, ref) => {
const [show, setShow] = useState(false);
const showMore = () => {
setShow((prev) => !prev);
};
const collapseText = () => (show ? "收起" : "展开");
useImperativeHandle(ref, () => ({
showMore
}));
return (
<div className="ke-comment-list-children">
<Collapse in={show}>{children}</Collapse>
<ButtonBase size="small" onClick={showMore}>
{collapseText()}
</ButtonBase>
</div>
);
};
export default forwardRef(CommentCollapse);
Creating a simple app using React and Redux.
The point is to get photos from the server, show them and if you click on the photo show modal window with bigger photo and comments.
The code for App component
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import './App.scss'
import List from './components/list/List'
import Header from './components/header/Header'
import Footer from './components/footer/Footer'
import ModalContainer from './containers/ModalContainer'
import { getPhotos, openModal } from './redux/actions/actions'
const App = () => {
const { isFetching, error } = useSelector(({ photos }) => photos)
const photos = useSelector(({ photos }) => photos.photos)
const { isOpen } = useSelector(({ modal }) => modal)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getPhotos())
}, [])
const getBigPhoto = (id) => {
dispatch(openModal(id))
}
return (
<div className="container">
<Header>Test App</Header>
<div className="list__content">
{isFetching
? <p>Loading...</p>
: error
? <p>{error}</p>
: photos.map(({ id, url }) => (
<List
key={id}
src={url}
onClick={() => getBigPhoto(id)}
/>
))
}
</div>
<Footer>© 2019-2020</Footer>
{isOpen && <ModalContainer />}
</div>
)
}
export default App
In this line I get photos only once to stop rerender if I refresh the page
useEffect(() => {
dispatch(getPhotos())
}, [])
When I click on the photo my modal opens and I want to stop rerendering all the components. For example for my header I use React.memo HOC like this
import React, { memo } from 'react'
import './Header.scss'
import PropTypes from 'prop-types'
const Header = memo(({ children }) => {
return <div className="header">{children}</div>
})
Header.propTypes = {
children: PropTypes.string,
}
Header.defaultProps = {
children: '',
}
export default Header
It works perfectly when I open and close my modal. Header and Footer are not rerendered. But List component is rerendered every time I open and close a modal window. It's happening because that prop onClick={() => getBigPhoto(id)} in List component creates a new anonymous function every time I click. As you know if your props changed, component is rerendered.
My question is how to avoid rerender of List component in my situation?
You can create a container for List that receives getBigPhoto and an id, create getBigPhoto with useCallback so the function doesn't change:
const ListContainer = React.memo(function ListContainer({
id,
src,
getBigPhoto,
}) {
return (
<List
key={id}
src={scr}
onClick={() => getBigPhoto(id)}
/>
);
});
const App = () => {
const { isFetching, error, photos } = useSelector(
({ photos }) => photos
);
const { isOpen } = useSelector(({ modal }) => modal);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPhotos());
}, []);
//use callback so getBigPhoto doesn't change
const getBigPhoto = React.useCallback((id) => {
dispatch(openModal(id));
}, []);
return (
<div className="container">
<Header>Test App</Header>
<div className="list__content">
{isFetching ? (
<p>Loading...</p>
) : error ? (
<p>{error}</p>
) : (
photos.map(({ id, url }) => (
// render pure component ListContainer
<ListContainer
key={id}
src={url}
id={id}
getBigPhoto={getBigPhoto}
/>
))
)}
</div>
<Footer>© 2019-2020</Footer>
{isOpen && <ModalContainer />}
</div>
);
};
See codesandbox here
I want to use a callback in a child react component so that I can invoke a function from my child component in my parent component. However, when I handle the callback in my parent component, and try to set this child's function so that I can invoke this function later on when a button gets clicked, this function ends up getting invoked unexpectedly.
What I want to happen is that the sampleFunction to be invoked when the 'Invoke Sample Function' button is clicked, but instead, sampleFunction is invoked when the parent component is mounted (and the console is thus logged with 'foo'). How can I properly pass this callback function from the child to the parent? Thanks.
index.js:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
useEffect(() => {
callback({
toggleStatus,
sampleFunction: () => console.log("foo")
});
}, [callback, toggleStatus]);
const handleClick = () => {
setToggleStatus(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(null);
const handleCallback = props => {
setFn(props.sampleFunction);
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Based on what this question covers and the usage you want, you should use useCallback (docs - more info) instead of useEffect. something like the following is kind of better implementation:
import React from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
const handleClick = React.useCallback(() => {
setToggleStatus(prevState => !prevState);
callback({
toggleStatus,
sampleFunction: () => () => console.log("foo")
})
}, [callback, toggleStatus])
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(() => null);
const handleCallback = props => {
console.log(props)
setFn(props.sampleFunction);
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You need to update your code as you are calling the callback in the initialization of your child component.
useEffect(() => {
// remove below call
callback({
toggleStatus,
sampleFunction: () => console.log("foo")
});
}, [callback, toggleStatus]);
See codesandbox here
Thanks to #StackedQ and #Anthony for the help. As Anthony mentioned, in order to avoid an endless loop, I had to add an extra condition to handleCallback:
index.js:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
useEffect(() => {
callback({
toggleStatus,
sampleFunction: () => () => setToggleStatus(prevState => !prevState)
});
}, [callback, toggleStatus]);
const handleClick = () => {
setToggleStatus(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(null);
const handleCallback = props => {
if (!fn) {
setFn(props.sampleFunction);
}
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
See codesandbox here
I am trying to pass the state value of a child component up to its parent by using React's useImperativeHandle. However, it appears that my parent component is not receiving the updated state value of the child component when it console logs the child's component value; console.log(componentRef.current.state) always is logged as false.
Why is this not working and how can I accurately receive the mutated state value of my child component in my parent component by passing the necessary ref? Thanks!
index.tsx:
import React from "react";
import ReactDOM from "react-dom";
const Component = React.forwardRef((props, ref) => {
const [state, set] = React.useState(false);
React.useImperativeHandle(ref, () => ({
state
}));
const handleClick = () => {
set(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{state ? "On" : "Off"}</h1>
</>
);
});
const App = () => {
const componentRef = React.useRef(null);
React.useEffect(() => {
console.log(componentRef.current.state);
}, [componentRef]);
return <Component ref={componentRef} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If you just want that functionality you can use something like:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = props => {
const [state, set] = React.useState(false);
useEffect(() => props.callback(state), [state])
const handleClick = () => {
set(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1 >{state ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
return <Component callback={val => console.log(val)} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
But if you really need to use ref, just comment it so I'll remove this answer.