Passing state up from child component and call a function with React - reactjs

So this is my parent component - it's getting a state set from it's child component when what component is updated which works great
const [filteredCases, setFilteredCases] = useState([]);
<ChildComponent
setFilteredCases={setFilteredCases}
/>
What I would like to do is also call a function whenever the setFilteredCases state is updated .. so something like this
const [filteredCases, setFilteredCases] = useState([]);
function foo() {
// do things
}
<ChildComponent
setFilteredCases={setFilteredCases, foo}
/>
But doing so seems to break the state update and never calls the function .. have I got the syntax wrong here or is there a better way to implement what I'm doing here?
Any advice / guidance / links to documentation welcome!

Try using useEffect instead
const [filteredCases, setFilteredCases] = useState([]);
const foo = () => {
// some changes
}
useEffect(() => {
// filteredCases has been updated
foo();
}, [filteredCases]);
<
ChildComponent setFilteredCases = {
setFilteredCases
}
/>
const ChildComponent = ({
setFilteredCases
}) => {
// Call the function this way:
setFilteredCases([]);
return (
// Send out something.
);
};
export default ChildComponent;

You can pass a state or method as a props from parent to child component.
Example:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Another Example :
const [filteredCases, setFilteredCases] = useState([]);
function foo() {
// do things
}
<ChildComponent
setFilteredCases={setFilteredCases}
foo={foo}
/>
const ChildComponent = ({ foo, setFilteredCases }) => {
const onHandleClick = () => {
// Call the functions
setFilteredCases('value');
foo();
}
return (
<button onClick={onHandleClick} >Click me</button>
);
};
export default ChildComponent;

Related

How to pass component constructor in React props?

How to pass component constructor in React props?
I'm a beginner for React. So below example may make it clear what I am trying to achieve.
Example:
PorkComponent:
const PorkComponent = ({ children, variant }: PorkComponentProps) => {
return (
<motion.div>
...
</motion.div>
);
};
DuckComponent:
const DuckComponent = ({ children, variant }: DuckComponentProps) => {
return (
<motion.div>
...
</motion.div>
);
};
The Lunch component will contains PorkComponent or DuckComponent Based on LunchProps.meatType.
type LunchProps = {
meatType: React.ComponentType;
};
const Lunch = (props: LunchProps) => {
return (
<props.meatType> // Here it will return PorkComponent or DuckComponent based on what props.meatType.
);
}
Sure I can use something like:
const Lunch = (props: LunchProps) => {
return (
if (props.isPork) {
< PorkComponent >
} else(props.isDuck) {
< DuckComponent >
}
....
);
}
But I don't want to have multiple IFELSE check in Lunch component. Instead, I want the caller to specify the "meat type", like <Lunch meatType= PorkComponent>.
I recently saw this someone shared on Twitter,
const MeatComponents = {
'pork': <PorkComponent />
'duck': <DuckComponent />
}
const Lunch = (props) => {
return MeatComponent[props.meatType]
}
// usage
<Lunch meatType='pork' />
<Lunch meatType='duck' />
Or you could just use,
const Lunch = ({ children }) => {
return <>{children}</>
}
// usage
<Lunch>
<PorkComponent />
</Lunch>
You can simply write :
const Lunch = (props: LunchProps) => <props.meatType />;
<Lunch meatType={DuckComponent} />
Here is an example on stackblitz
You can pass the rendered component directly instead:
// in Lunch component
return (<div>{props.meatType}</div>);
<Lunch meatType={<PorkComponent />} />
Playground
If you just want to return the given component, simply use:
return props.meatType;
you can pass component functions as props. You almost got the answer.
const Lunch = (props) => {
return (
// React.createElement(props.meatType, {}, null)
<props.meatType /> // this is valid, I have tested before replying.
);
}
// then
<Lunch meatType={PorkComponent} />
// or
<Lunch meatType={DuckComponent} />
// even this works and renders a div
<Lunch meatType="div" />

React: save ref to state in a custom hook

I want to create a ref to an element, save it in state and use it somewhere else, down the line. Here is what I have so far:
const Header = () => {
const topElement = useRef();
const { setRootElement } = useScrollToTop();
useEffect(() => {
setRootElement(topElement);
}, []);
return (
<div ref={topElement}>
...
</div>
)
}
The useScrollToTop hook:
export const useScrollToTop = () => {
const [rootElement, setRootElement] = useState();
const scrollToTop = () => {
rootElement.current.scrollIntoView();
};
return {
scrollToTop: scrollToTop,
setRootElement: setRootElement
};
};
And in a different component:
const LongList = () => {
const { scrollToTop } = useScrollToTop();
return (
<div>
....
<button onClick={() => scrollToTop()} />
</div>
);
}
The setRootElemet works okay, it saves the element that I pass to it but when I call scrollToTop() the element is undefined. What am I missing here?
As hooks are essentially just functions, there is no state shared between calls. Each time you call useScrollToTop you are getting a new object with its own scrollToTop and setRootElement. When you call useScrollToTop in LongList, the returned setRootElement is never used and therefore that instance rootElement will never have a value.
What you need to do is have one call to useScrollToTop and pass the returned items to their respective components. Also, instead of using a state in the hook for the element, you can use a ref directly and return it.
Putting these together, assuming you have an App structure something like:
App
Header
LongList
Hook:
export const useScrollToTop = () => {
const rootElement = useRef();
const scrollToTop = () => {
rootElement.current.scrollIntoView();
};
return {
scrollToTop,
rootElement,
};
};
App:
...
const { scrollToTop, rootElement } = useScrollToTop();
return (
...
<Header rootElementRef={rootElement} />
<LongList scrollToTop={scrollToTop} />
...
);
Header:
const Header = ({ rootElementRef }) => {
return (
<div ref={rootElementRef}>
...
</div>
);
}
LongList:
const LongList = ({ scrollToTop }) => {
return (
<div>
...
<button onClick={() => scrollToTop()} />
</div>
);
}
The issue probably is topElement would be null initially and useEffect would trigger setRootElement with null. You would need to keep topElement in state variable and check when it changes and set the value inside your JSX as
const [topElement, setTopElement] = useState(null);
useEffect(() => {topElement && setRootElement(topElement);}, [topElement])
return (
<div ref={(ref) => setTopElement(ref)}>
...
</div>
);

React useContext value is not getting passed from the provider?

I'm trying to pass a value down using useContext as below
This is Context.js
export const selectedContext = React.createContext();
export const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
<Pies />
</selectedContext.Provider>
);
};
I'm calling the context in one of the components like so
This is in Card.js (a child in the provider)
const value = React.useContext(selectedContext);
console.log(value);
When I initialize the value from React.createContext, the value is passed down to my component but when I try using the provider it doesn't work.
What am I doing wrong?
When you are using React.useContext like this it's not wire into the <Context.Provider>
Please see the docs on who to use React.useContext here.
It's seems that the React.useContext will not work with in the Provider direct component children, so you need to make one more component in between. (like in the docs example)
const selectedContext = React.createContext();
const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
</selectedContext.Provider>
);
};
const Cards = () => {
const value = React.useContext(selectedContext);
console.log(value); // will not work
return (
<Card />
);
};
const Card = () => {
const value = React.useContext(selectedContext);
console.log(value); // will work
return (
<div>My Card</div>
);
};
If you need it to work on the first layer of component you can use <Context.Consumer> and it will work within.
const selectedContext = React.createContext();
const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
</selectedContext.Provider>
);
};
const Cards = () => {
const value = React.useContext(selectedContext);
console.log(value); // will not work
return (
<div>
<selectedContext.Consumer>
{({value}) => (
<h1>{value}</h1> // will work
)}
</selectedContext.Consumer>
</div>
);
};
Your code is fine, but you should "call the context" in the child component of the provider, as the value is available in Provider's children:
export const SelectedContext = React.createContext();
export const SelectProvider = ({ children }) => {
return (
<SelectedContext.Provider value={'Team One'}>
{children}
</SelectedContext.Provider>
);
};
const ProviderChecker = () => {
const value = React.useContext(SelectedContext);
return <div>{value}</div>;
};
const App = () => {
return (
<SelectProvider>
<ProviderChecker />
</SelectProvider>
);
};

How to declare default props in react functional component

I want to declare default props in a functional component
normally we would do this
function Body() {
static defaultProps = {
counter: 0
}
return (
<div>
body
</div>
);
}
this obviously doesn't work
This is usually called a functional component, not a hook component.
For the defaultProps you can do it like this:
const Body = ({ counter }) => {
return (
<div>
{counter}
</div>
);
}
Body.defaultProps = {
counter: 0
}
function Body({counter=0}) {
return (
<div>
body
</div>
);
}
counter now has initial value of 0
You can do that simply like this
const Body = (props) => {
const {foo = 'defaultValue'} = props;
return <div>{foo}</div> // It will show defaultValue if props foo is undefined
}
Hooks are just regular functions, you can define default values the same way as you define them in regular functions
function useNameHook(initialName = "Asaf") {
const [name, setName] = useState(initialName);
// you can return here whatever you want, just the name,
// just the setter or both
return name;
}
function Comp({ name }) {
const myName = useNameHook(name);
return <h1>Hello {myName}</h1>;
}
function App() {
return (
<div className="App">
{/* without name prop */}
<Comp />
{/* with name prop */}
<Comp name="angry kiwi" />
</div>
);
}
You can declare defaultProps outside the functional component. In your case it would be something like this -
function Body(props) {
return (
<div>
{props.counter}
</div>
);
}
Body.defaultProps = {
counter: 0
}
This is for hooks but is not intended for functional components.
const defaultProps = {
position: "end"
};
const Dropdown = (props) => {
// The order of arguments is important.
props = Object.assign({}, defaultProps, props);
...
}

Any better way to pass data from a child component to a function in a parent component without using bind()?

I'm using React.Context to pass a function down to child component which is inside another component. See figure down below.
I'm quite not comfortable using .bind() in order to pass the name variable to the function in the parent component.
Is there a better way to pass data from a child component to a function in a parent component without using .bind()?
See my code below:
import React, { Component, createContext } from 'react'
const AppContext = createContext()
class ComponentOne extends Component {
const handleClick = (name)=> {
alert(`Hello ${name}`)
}
render(){
return(
<AppContext.Provider
value={{
handleClick: this.handleClick
}}>
<p>Hello from ComponentOne</p>
</AppContext.Provider>
)
}
}
const ComponentThree = props=> {
const name = "lomse"
return(
<AppContext.Consumer>
(context=>(
<button onClick={context.handleClick.bind(null, name)}>Click me!</button>
))
</AppContext.Consumer>
)
}
You can always do:
const ComponentThree = props=> {
const name = "lomse"
return(
<AppContext.Consumer>
(context=> {
const handleClick = () => context.handleClick(name)
return <button onClick={handleClick}>Click me!</button>
})
</AppContext.Consumer>
)
}
What you're doing is about binding arguments so when a function is called later it will be called with those arguments. It has nothing to do with React Context.
and onClick needs to be a function, that will be called - well - on click.
Or you could do:
const handleClick = (name) => () => {
alert(`Hello ${name}`)
}
and then:
const ComponentThree = props=> {
const name = "lomse"
return(
<AppContext.Consumer>
(context=> (
<button onClick={context.handleClick(name)}>Click me!</button>
))
</AppContext.Consumer>
)
}

Resources