React pass props when child component is a variable - reactjs

How can I pass props to a child component when the component is a variable. In the following code, I need to pass the prev function as a prop to the step. Thanks.
import React, {useState} from 'react';
const Wizard = (props)=>{
const [step] = useState(0);
const CurrStep = props.steps[step];
const prev = ()=>{
console.log('prev called')
}
return (
<div>
{// need to pass prev as a prop to the CurrStep component}
{CurrStep }
</div>)
}
export default Wizard
Wizard.propTypes = {
header: PropTypes.func.isRequired,
steps: PropTypes.array.isRequired,//array of functional components
wizardContext: PropTypes.object.isRequired,
onComplete: PropTypes.func.isRequired
};

you can spread props inside the CurrStep component like this
return <CurrStep {...props} />;
here's a codesandbox demo of the code below
import React, { useState } from "react";
const Wizard = props => {
const [step] = useState(0);
const Comp1 = props => <div>{props.a}</div>;
const Comp2 = props => <div>{props.a}</div>;
const comps = [Comp1, Comp2];
const CurrStep = comps[step];
// this is just for demo, you can just pass props straight from Wizard
props = { a: "a", ...props };
return <CurrStep {...props} />;
};
export default Wizard;

This was my mistake, I was passing in an array like this:
[, ] instead of
[Step1, Step2]
Sorry to waste your time.

// adding (props) =>
const CurrStep = (...props) => props.steps[step](...props);
const prev = () => {
console.log('prev called')
}
return (
<div>
{CurrStep(prev)}
</div>
)

Related

React HOC can't pass props?

I tried to pass description=Button props to Button Component using HOC.
so that, I expected to render like, `Button
But, Empty Button Elements is Rendered!
My codeSandBoxLink:enter link description here
Button.jsx
import React from 'react'
import withLoading from './withLoading'
function Button() {
return <button></button>
}
export default withLoading(Button)
withLoading.jsx
export default function withLoading(Component) {
const WithLoadingComponent = (props) => {
return <Component>{props.description}</Component>
);
};
return WithLoadingComponent;
App.jsx
return(
<div>
<Button description="button"><Button>
</div>
)
Thanks for any help.
At Button compnent, you need to use props and follow your code so that is props.description.
function Button(props) {
return <button>{props.description}</button>;
}
At withLoading HOC, you need to pass all props for Component.
//HOC Example
export default function withLoading(Component) {
const WithLoadingComponent = (props) => {
const [loading, setLoading] = React.useState(true);
console.log("props:", props.description);
//delay 3sec...
React.useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
return loading ? <p>loading...</p> : <Component {...props} />;
};
return WithLoadingComponent;
}
I have been fork and then fixed it. You can refer by this link: https://codesandbox.io/s/strange-cerf-glxquu?file=/src/withLoading.jsx

passing object using context and doing iteration with map

This is a simple question but I couldn't reach the final result after a lot of attempts. The problem is that I want to pass an object in context and use it in another file. And then do an iteration and create a specific element for each value.
App.jsx
const [activities, setActivity] = useState([
{
key: Math.random() * Math.random(),
name: 'Hello',
}
]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: Math.random() * Math.random(),
name: inputValue.current.value,
};
setActivity(activities.concat(activity));
};
const value = {
// I want to pass this parameter - only activities has problem (Activity.jsx <h1>)
// I can't achieve activities.name in Activity.jsx
activities: [...activities],
functions: {
addActivity: addActivity
},
ref: {
inputValue: inputValue
}
};
<Context.Provider
value={value}
>
Context.js
export const Context = createContext();
Activity.jsx
const { activities, functions, ref } = useContext(Context);
return (
<section className="activity-container">
<input type="text" ref={ref.inputValue} />
<button onClick={functions.addActivity}>add!</button>
{
activities.map(activity => (
<h1>activity.name</h1>
))
}
</section>
);
I believe this is what you want:
// Sharing data through context
Context file:
// Context.js
import React, { useState, useRef, createContext } from "react";
export const DataContext = createContext();
const getRandom = () => Math.random() * Math.random();
const defaultValue = {
key: getRandom(),
name: "Hello"
};
const ContextProvider = ({ children }) => {
const [activities, setActivity] = useState([defaultValue]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: getRandom(),
name: inputValue.current.value
};
setActivity([...activities, activity]);
};
const value = {
activities: [...activities],
functions: { addActivity },
ref: { inputValue }
};
return <DataContext.Provider value={value}>{children}</DataContext.Provider>;
};
export default ContextProvider;
Hook to read from context:
// useDataContext
import { useContext } from "react";
import { DataContext } from "./Context";
const useDataContext = () => {
const contextValue = useContext(DataContext);
return contextValue;
};
export default useDataContext;
Child Element where you want to receive the value from context:
// Child.js
import React from "react";
import useDataContext from "./useDataContext";
const Child = () => {
const data = useDataContext();
return (
<>
{data.activities.map((val, idx) => (
<div key={idx}>Name is {val.name}</div>
))}
</>
);
};
export default Child;
And the App container:
// App.js
import Child from "./Child";
import ContextProvider from "./Context";
export default function App() {
return (
<div className="App">
<ContextProvider>
<Child />
</ContextProvider>
</div>
);
}
I've created a sandbox for you to test.
You should make sure that the Activity.jsx component is wrapped with context provider, to get the proper value from the context.
I tried in this codesandbox, and it's working properly. You can refer to this and check what you are missing.

React: Is there a way to access component state from function in another file?

I've a react component which includes a large function that updates the component state, the function is large so I want to move it to a separate file and export it in the react component. But I don't find anyway to access the component state if I move the function to its own file.
Is there anyway to do this ?
example:
component.tsx
import { myFunction } from './function.ts'
const [toggle, setToggle] = useState(false)
const my_component = () => {
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = () => {
// do something that updates `toggle`
}
you can do the logic apart from the component and return the result to the component. have a look at the code below.
https://codesandbox.io/s/hopeful-dubinsky-930p7?file=/src/App.js
This is just a raw example of what you can do with custom state hooks (reference: https://dev.to/spukas/react-hooks-creating-custom-state-hook-300c)
import React from 'react';
export function useMyFunction(value) {
const [toggle, setToggle] = React.useState(value || false);
const myFunction = () => {
// do something that updates `toggle` with setToggle(...)
}
return { toggle, myFunction };
}
import { useMyFunction } from './function.ts'
const my_component = () => {
const [toggle, myFunction] = useMyFunction(false)
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
This can be achieved by 2 different ways one using HOC components and another just by using functions.
Approach 1: Using HOC
handler.js
const withHandlers = (WrappedComponent) => {
class HandlerComponent extends Component {
state = {toggle:false};
myFunction = () => {
//Do your update here
}
render() {
return <WrappedComponent
toggle={this.state.toggle
myFunction={this.myFunction}
/>
}
};
my_component.js
const my_component = (props) => {
return (
<div>
<button onClick={props.myFunction}>Run function</button>
</div>
}
export default withHandlers(my_component);
Approach 2: Using Functions
handler.js
export const myFunction(toggle) => {
return !toggle; //return the changed value
}
my_component.js
const my_component = () => {
const [toggle, setToggle] = useState(false);
const myFunction = () => {
setToggle(handler.myFunction); //the state will be passed as a parameter by default
};
return(
<div>
<button onClick={myFunction}>Run function</button>
</div>
);
};
For the toggle to work, it must be passed to the function as a props then for update it used state management (redux or react context).
The best solution is to define the toggle in the function itself and pass it a Boolean props to control it.
import { myFunction } from './function.ts'
const my_component = () => {
return (
<div>
<button onClick={myFunction(false)}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = (props) => {
const [toggle, setToggle] = useState(props || false);
// your codes
};

How to pass props if it already has some value passed?

function UpdatePngf(index, id) {
const [Cards, setCards] = props.value.Cards
let CardsData = Cards
var CardsObj = {
link: Cards[index].link,
icon: Cards[index].icon,
name: Cards[index].name,
png: Cards[index].png,
id: id,
}
CardsData[index] = CardsObj
setCards(CardsData)
}
export const UpdatePng = withUserData(UpdatePngf)
this is my function I want to pass props..but how I am supposed to do so??
should I do this way function UpdatePngf(index, id,props) {}? or other way
/** #format */
import React, { createContext } from 'react'
const UserData = createContext(null)
export const withUserData = (Component) => (props) => {
return (
<UserData.Consumer>
{(value) => <Component {...props} value={value}></Component>}
</UserData.Consumer>
)
}
export default UserData
This is my userData hoc..
I saw index, id also is props. You just update UpdatePngf:
function UpdatePngf({index, id, ...props}) { ... }
And pass props to UpdatePng wwhen using it: <UpdatePng id="...." index="..." ...yourProps>

On child component state change, update siblings

Partially working example: https://codesandbox.io/s/jolly-smoke-ryb2d
Problem:
When a user expands/opens a component row, all other rows inside the rows parent component need to be collapsed. Unfortunately, I can't seem to get the other sibling rows to collapse.
I tried passing down a handler from the parent to the child that updates the state of the parent which would then in turn propagate down to the children.
Expected Result
On expand/open of a row, collapse any other rows that are open inside the parent component that isn't the one clicked
Code:
App.tsx
import React from "react";
import ReactDOM from "react-dom";
import Rows from "./Rows";
import Row from "./Row";
import "./styles.css";
export interface AppProps {}
const App: React.FC<AppProps> = props => {
return (
<Rows>
<Row>
<p>Click me</p>
<p>Collapse</p>
</Row>
<Row>
<p>Click me</p>
<p>Collapse</p>
</Row>
<Row>
<p>Click me</p>
<p>Collapse</p>
</Row>
</Rows>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Rows.tsx
Rows.tsx
import React, { useState, useEffect } from "react";
import Row, { RowProps } from "./Row";
export interface RowsProps {}
const Rows: React.FC<RowsProps> = props => {
const [areRowsHidden, setAreRowsHidden] = useState<boolean>(false);
useEffect(() => {});
const handleOnShow = (): void => {};
const handleOnCollapse = (): void => {};
const renderChildren = (): React.ReactElement[] => {
return React.Children.map(props.children, child => {
const props = Object.assign(
{},
(child as React.ReactElement<RowsProps>).props,
{
onShow: handleOnShow,
onCollapse: handleOnCollapse,
isCollapsed: areRowsHidden
}
);
return React.createElement(Row, props);
});
};
return <>{renderChildren()}</>;
};
export default Rows;
Row.tsx
import React, { useState, useEffect } from "react";
export interface RowProps {
onCollapse?: Function;
onShow?: Function;
isCollapsed?: boolean;
}
const Row: React.FC<RowProps> = props => {
const [isCollapsed, setIsCollapsed] = useState(props.isCollapsed || true);
useEffect(() => {}, [props.isCollapsed]);
const handleClick = (): void => {
if (isCollapsed) {
props.onShow();
setIsCollapsed(false);
} else {
props.onCollapse();
setIsCollapsed(true);
}
};
return (
<>
{React.cloneElement(props.children[0], {
onClick: handleClick
})}
{isCollapsed ? null : React.cloneElement(props.children[1])}
</>
);
};
export default Row;
I would store which row is open inside of Rows.tsx and send that value down to its children rather than having the child control that state. You may see this being referred to as lifting state up. You can read more about it here.
Rows.tsx
import React, { useState } from 'react'
import Row from './Row'
export interface RowsProps {}
const Rows: React.FC<RowsProps> = props => {
const [visibleRowIndex, setVisibleRowIndex] = useState<number>(null)
const renderChildren = (): React.ReactElement[] => {
return React.Children.map(props.children, (child, index) => {
const props = Object.assign({}, (child as React.ReactElement<RowsProps>).props, {
onShow: () => setVisibleRowIndex(index),
onCollapse: () => setVisibleRowIndex(null),
isCollapsed: index !== visibleRowIndex
})
return React.createElement(Row, props)
})
}
return <>{renderChildren()}</>
}
export default Rows
Row.tsx
import React from 'react'
export interface RowProps {
onCollapse?: Function
onShow?: Function
isCollapsed?: boolean
}
const Row: React.FC<RowProps> = props => {
const handleClick = (): void => {
if (props.isCollapsed) {
props.onShow()
} else {
props.onCollapse()
}
}
return (
<>
{React.cloneElement(props.children[0], {
onClick: handleClick
})}
{props.isCollapsed ? null : React.cloneElement(props.children[1])}
</>
)
}
export default Row
Example: https://codesandbox.io/s/gifted-hermann-oz2zw
Just a side note: I noticed you're cloning elements and doing something commonly referred to as prop drilling. You can avoid this by using context if you're interested although not necessary.

Resources