I'm trying to pass extra props to this.props.children, I saw this answer how to pass props to children with React.cloneElement?
and from some reason although I'm not getting any error I can't see the prop
So I have this state
this.state = {
open: true
}
and I want to pass it down to this.props.children, and this is what I've done so far:
{
React.Children.map(this.props.children, child =>
React.cloneElement(child, {sidebarState: this.state.open}))
}
and when I'm console.logging this.props on the children I can't see my new props.
--- EDIT ---
In the children it looks like this:
render() {
console.log(this.props)
// other code
}
BTW I'm using react 16.0
Here is an example.
Before (without passing props down to the children):
<div className="layout">
{children}
</div>
After (passing extra props1 and props2 props to every child):
<div className="layout">
{
React.Children.map(children, (child) => {
return React.cloneElement(child, {
props1: 1,
props2: 2,
});
})
}
</div>
props1 and props2 get merged with the existing props for every child.
Regarding TypeScript types, you have to use React.ReactElement instead of React.ReactNode or the TS compiler will complain at React.Children.map (or ts-ignore it):
type Props = {
children: React.ReactElement;
};
See https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356 for more explanation, that helped me a lot to understand how to do it! (#mediumPaywall)
There are two ways to pass props to children:
Children as function
Instead of being a React element, children can be a function.
Call the children function:
const List = ({ children, sidebarState }) => (
<ul>
{
children(sidebarState)
}
</ul>
);
Passing the children a function:
<List sidebarState={sidebarState}>
{
(sidebarState) => (
<Item sidebarState={sidebarState} />
)
}
</List>
Working example:
const { Component } = React;
const Item = ({ sidebarState }) => (
<li>{sidebarState ? 'open' : 'close'}</li>
);
const List = ({ children, sidebarState }) => (
<ul>
{
children(sidebarState)
}
</ul>
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
sidebarState: true
}
}
toggleOpen = () => this.setState((prevState) => ({
sidebarState: !prevState.sidebarState
}));
render() {
const { sidebarState } = this.state;
return (
<div>
<button onClick={this.toggleOpen}>Toggle</button>
<List sidebarState={sidebarState}>
{
(sidebarState) => (
<Item sidebarState={sidebarState} />
)
}
</List>
</div>
);
}
}
ReactDOM.render(
<App />,
demo
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
React.cloneElement
Working example:
const { Component } = React;
const Item1 = ({ sidebarState }) => (
<li>{sidebarState ? 'open' : 'close'}</li>
);
const Item2 = ({ sidebarState }) => (
<li>{sidebarState ? 'open' : 'close'}</li>
);
const List = ({ children, sidebarState }) => (
<ul>
{
React.Children.map(children, (child) => React.cloneElement(child, { sidebarState }))
}
</ul>
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
sidebarState: true
}
}
toggleOpen = () => this.setState((prevState) => ({
sidebarState: !prevState.sidebarState
}));
render() {
const { sidebarState } = this.state;
return (
<div>
<button onClick={this.toggleOpen}>Toggle</button>
<List sidebarState={sidebarState}>
<Item1 />
<Item2 />
</List>
</div>
);
}
}
ReactDOM.render(
<App />,
demo
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
Related
I have 3 components, one parent component which renders 2 child components. I need to access the function of the 1st child component from the 2nd child component.
Sample Code:
Parent Component:
class Main extends React.Component {
myRef: React.RefObject<any>;
constructor(props: any) {
super(props);
this.myRef = React.createRef();
}
return (
<div>
{/* some code */}
<ChildOne />
<ChildTwo />
</div>
);
ChildOne:
function ChildOne() {
const childOneFunction = () => {
console.log("Hello from child one");
};
return <div>{/* some code */}</div>;
}
}
ChildTwo:
function ChildTwo() {
useEffect(() => {
childOneFunction();
});
return <div>{/* some code */}</div>;
}
I need call the childOneFunction() from childTwo component. Is it possible without any 3rd party library like redux?
Maybe you can try with forwarding refs and useImperativeHandle.
Demo:
let ChildOne = React.forwardRef((props, ref) => {
React.useImperativeHandle(ref, () => ({
childOneFunction: () => {
console.log('Hello from child one');
},
}));
return <div>1</div>;
});
let ChildTwo = React.forwardRef((props, ref) => {
return (
<div
onClick={() => {
ref.current.childOneFunction();
}}
>
2
</div>
);
});
export default function App() {
const test = React.useRef();
return (
<div>
<ChildOne ref={test} />
<ChildTwo ref={test} />
</div>
);
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
todos: 5
};
}
handleClick = () => {
this.setState({ todos: this.state.todos + 1 });
};
render() {
return (
<div className="App">
<h1>ToDo List</h1>
<p>Just keep giving me things to do</p>
<p>I still have {this.state.todos} things to do</p>
<AddTodo todos={this.state.todos} handleClick={this.handleClick} />
</div>
);
}
}
I am trying to update <p>I still have {this.state.todos} things to do</p> in the Parent to increase by 1 for every button click in the Child Component. What am I missing? I am not getting any errors but it is not functional.
import React from "react";
export default function AddTodo(handleClick) {
return (
<div className="AddTodo">
<button onClick={() => handleClick}>Add Another</button>
</div>
);
}
Props is the first value passed to a functional component, it's an object and you would need to destructure handleClick from it.
export default function AddTodo({ handleClick }) {
return (
<div className="AddTodo">
<button onClick={handleClick}>Add Another</button>
</div>
);
}
Also change your handle click function to this
handleClick = () => {
this.setState(({ todos }) => ({ todos: todos + 1 }));
};
a working example
const AddTodo = ({ onClick }) => (
<div className="AddTodo">
<button onClick={onClick}>Add Another</button>
</div>
);
const App = () => {
const [todos, setTodos] = React.useState(5);
const onClick = () => {
setTodos((oldTodos) => oldTodos + 1);
};
return (
<div className="App">
<h1>ToDo List</h1>
<p>Just keep giving me things to do</p>
<p>I still have {todos} things to do</p>
<AddTodo todos={todos} onClick={onClick} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://unpkg.com/react#17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js" crossorigin></script>
<div id="root"></div>
I have implemented an app which uses react-router to handle the routes in my web-app. I want to trigger the function logintoggle which is on the Header.js component from a function from the Hompage.js component. The App.js has all the routes in one file.
Can anyone explain to me how this can be achieved with small code snippet?
App.js
render() {
const { location } = this.props;
return (
<IntlProvider
locale="a"
messages="s"
>
<Fragment>
<div>
<Headers />
<Switch>
<Route exact path="/women" component={HomePage} />
</Switch>
</div>
</Fragment>
</IntlProvider>
);
}
}
export default App;
Header
class Header extends React.Component {
constructor(props) {
super(props);
}
logintoggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
}
Homepage.js
class CheckOut extends Component {
constructor(props) {
super(props);
}
}
When you need to have a shared state among the components React.Context API is what you need. It allows you to create a separate context provider, which will provide the state and the methods to manipulate this state to all the components you need. In the example below I have a LoginContextProvider with activeTab state variable. I provide activeTab and setActiveTab to all the components inside LoginContextProvider's children. Header changes activeTab to 1, Homepage changes to 2 and LoginContextDebug represents the actual activeTab value.
const LoginContext = React.createContext(null);
const LoginContextProvider = ({ children }) => {
const [activeTab, setActiveTab] = React.useState(0);
return (
<LoginContext.Provider value={{ setActiveTab, activeTab }}>
{children}
</LoginContext.Provider>
);
};
const Header = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am header</h1>
<button onClick={() => setActiveTab(1)}>Set activeTab to 1</button>
</div>
);
};
const Homepage = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am homepage</h1>
<button onClick={() => setActiveTab(2)}>Set activeTab to 2</button>
</div>
);
};
const LoginContextDebug = () => {
const { activeTab } = React.useContext(LoginContext);
return (
<pre style={{ padding: 10, background: "lightgray" }}>
activeTab={activeTab}
</pre>
);
};
const App = () => (
<LoginContextProvider value={null}>
<Header />
<Homepage />
<LoginContextDebug />
</LoginContextProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Im tryng to test a class component that needs some props to render the html, not sure why is not working. Im starting tests with jest and react now, so im not really experienced. What im missing?
Test component
const mockStore = configureMockStore();
const store = mockStore({});
describe("Pokemon detail", () => {
const mockPokemon = {
sprites: {
back_default:
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/132.png"
},
name: "ditto",
abilities: [
{
ability: {
name: "imposter",
url: 'https://pokeapi.co/api/v2/ability/150/"'
}
},
{
ability: {
name: "imposter",
url: "https://pokeapi.co/api/v2/ability/150/"
}
}
],
types: [
{
type: {
name: "normal"
}
}
]
}
const wrapper = mount(
<Provider store={store}>
<PokemonDetail pokemon={mockPokemon} />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
error message is this
TypeError: Cannot read property 'sprites' of undefined
render() {
<h1>oi</h1>
if (this.props.pokemon.sprites) {
^
const habilidades = this.props.pokemon.abilities.map(element => {
return <li key={element.ability.url}>{element.ability.name}</li>;
});
tested component
class PokemonDetail extends React.Component {
render() {
if (this.props.pokemon.sprites) {
const habilidades = this.props.pokemon.abilities.map(element => {
return <li key={element.ability.url}>{element.ability.name}</li>;
});
const tipos = this.props.pokemon.types.map(element => {
return <li key={element.type.url}>{element.type.name}</li>;
});
return (
<div className="ui card">
<div className="image">
<img src={this.props.pokemon.sprites.back_default} />
</div>
<div className="content">
<a className="header">{this.props.pokemon.name}</a>
</div>
<button
onClick={() => this.props.favoritePokemon(this.props.pokemon.name)}
className="ui button primary"
>
Add
</button>
</div>
);
}
return <div />;
}
}
const mapStateToProps = state => {
return { pokemon: state.pokemon };
};
export default connect(mapStateToProps,{favoritePokemon})(PokemonDetail);
Not sure why the mockPokemon with fake data is not taking place on the props object....seems right to me
Since PokemonDetail is a Redux connected component, and pokemon prop is handled by Redux, the prop in <PokemonDetail pokemon={mockPokemon} /> will be overridden by Redux.
Instead, the object should be passed as a part of Redux store:
const store = mockStore({ pokemon: mockPokemon });
const wrapper = mount(
<Provider store={store}>
<PokemonDetail />
</Provider>
);
How do I update an adjacent (or child) component after a scroll event without re-rendering the parent component?
Adjacent scenario
<div>
<Scrollable onScroll={ ... } />
<DisplayPosition scrollEvent={ ... } />
</div>
Child scenario
return (
<div onScroll={ ... }>
<span> Don’t re-render me! </span>
<DisplayPosition scrollEvent={ ... } />
</div>
)
I am reluctant to reach for Redux for this problem as I would like to be able to solve it in a lightweight fashion
Without Redux
ParentComponent
export default class ParentComponent extends Component {
render() {
let parentCallback = () => {};
const onScroll = event => parentCallback(event);
const didScroll = callback => parentCallback = callback;
return (<div onScroll={onScroll}>
<ChildrenComponent whenParent={didScroll} />
...
ChildrenComponent
It will setup the callback to be executed by the parent
componentDidMount() {
this.props.whenParent(event => this.setState({ event }));
}
By updating the state your ChildrenComponent will re-render. The ParentComponent will not re-render as nothing on its state or properties changed.
You'll have access to your event on this.state.event. Check the snippet for an example with click event.
class ParentComponent extends React.Component {
render() {
console.log('hello ParentComponent');
let parentCallback = () => {};
const onClick = () => parentCallback(1);
const didClick = callback => parentCallback = callback;
return (
<div>
<button onClick={onClick}>Click To Add</button>
<ChildrenComponent whenParent={didClick} />
</div>
);
}
}
class ChildrenComponent extends React.Component {
state = { sum: 0 };
componentDidMount() {
const { whenParent } = this.props;
whenParent((adds) => {
const { sum } = this.state;
this.setState({ sum: sum + adds });
});
}
render() {
console.log('hello ChildrenComponent');
const { sum } = this.state;
return <div>{sum}</div>;
}
}
ReactDOM.render(
<ParentComponent />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>