Can I share data between sibling components in this case? - javascript

Is there a way to share data between sibling components without the need to return the component from which I need the data? here is an example to explain it better:
import React from "react";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<div>
<h1>Home page</h1>
<Component1 />
<Component2 />
</div>
</header>
</div>
);
}
const Component1 = (props) => {
const importantInfo = "secret-info";
if (props.handleInfo) {
props.handleInfo(importantInfo);
}
return (
<div>
<p>I am component number 1</p>
</div>
);
};
const Component2 = () => {
const handleInfo = (info) => {
console.log(info);
};
return (
<div>
<p>I am component number 2</p>
<Component1 handleInfo={(info) => handleInfo(info)} />
</div>
);
};
export default App;
I want to use some data from Component1 in Component2. The only way I found to do this, was to return the component1 inside the component2 and put the props in there.
I mean this:
return (
<div>
<p>I am component number 2</p>
<Component1 handleInfo={(info) => handleInfo(info)} />
</div>
Is there a way not to return the component and still receive the data throw props? I know I can do it with UseContext and with other methods, but I want to know if I can do it with props. Thanks!

What you can do is creating a new state in App.js as the following:
function App() {
const [commonInfo, setCommonInfo] = useState('information')
return // rest of the code
}
Then pass that down through props in both components as:
return (
<div className="App">
<header className="App-header">
<div>
<h1>Home page</h1>
<Component1 commonInfo={commonInfo} setCommonInfo={setCommonInfo} />
<Component2 commonInfo={commonInfo} setCommonInfo={setCommonInfo} />
</div>
</header>
</div>
);
Thus you can destructure from props in the components as the following:
// and also in Component2
const Component1 = (props) => {
const { commonInfo, setCommonInfo } = props
// rest
}

Related

Optimal way to set state of another component from

there's a few posts explaining how to update state from another component but I'm still unable to parse the solution for my particular problem. I want the submit button in my SideBar.js component to trigger nextProblem() in my tableA.js component when it is clicked. This will cause the state of tableA.js to change as problemNum goes from 1 to 2, generating a different form. I can't import SideBar.js component into my tableA.js due to my design layout though! SideBar.js also contains images. Any ideas?
I'm using a CSS grid for the layout of my page:
grid-template-areas:
"navbar navbar navbar navbar"
"sidebar tableA ... ..."
"sidebar tableA ... ..."
"sidebar tableA ... ..."
I'm using App.js to route to the pages:
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path={"/"} exact element={<MainPage/>}/>
<Route path={"/gamePage"} exact element={<GamePage/>}/>
</Routes>
</div>
</Router>
);
}
export default App;
I created a SideBar.js component that contains a logo at the top and a submit button at the bottom:
class SideBar extends Component {
componentDidMount() {
}
render() {
return (
<div className={"sidebar"}>
<div className={"logo_group"}>
<img className={"some_logo"} src={logo}/>
</div>
<div className={"nav_group"}>
<button className={"home"}/>
<button className={"about"}/>
</div>
<img className={"an_image"} src={someImage}/>
<div className={"button_group"}>
<button className={"submit_button"} onClick={() => nextProblem()}>Submit</button>
</div>
</div>
)
}
}
export default SideBar
and a tableA.js component with a function nextProblem() that changes the state of the component:
class tableA extends Component {
state = {
problemNum: 1,
problemStatus: 'unsolved',
userAnswer: []
};
nextProblem = () => {
this.setState(state => ({
problemNum: state.problemNum + 1,
}))
}
componentDidMount() {}
render() {
return (
<div>
<form>
...depends on the state of the problem number
</form>
</div>
)
}
export default tableA
I then consolidate everything in GamePage.js:
const GamePage= () => {
return (
<div className={"gamepage-container"}>
<div className={"navbar"}>
<NavBar></NavBar>
</div>
<div className={"sidebar"}>
<SideBar></SideBar>
</div>
<div className={"tableA"}>
<TableA></TableA>
</div>
...
</div>
)
}
export default GamePage
i'd propably go the approach of letting the gamepage handle the state and such and send down the problem and the update function down the components as props. Depending on how big this thing will grow you could also use a centralized store.
const GamePage= () => {
const [problem, setPropblem] = useState(0)
return (
<div className={"gamepage-container"}>
<div className={"navbar"}>
<NavBar></NavBar>
</div>
<div className={"sidebar"}>
<SideBar
onSetNext={() => setProblem(problem + 1)}
></SideBar>
</div>
<div className={"tableA"}>
<TableA
problem={problem}
></TableA>
</div>
...
</div>
)
}
export default GamePage
and in your sidebar the button becomes
<button className={"submit_button"} onClick={() => this.props.onSetNext()}>Submit</button>
similarly you access the props.propblem instead of state in your table.

Child component rerendered on every state changes

Live on codesandbox
I have a simple example
import "./styles.css";
import {useState} from "react";
const Child=({text,idx})=>{
console.log(`Child - ${idx} rendered`);
return <li>{text}</li>
}
const ShouldNotRender=()=>{
console.log("Should not render")
return <p>Should not render</p>
}
export default function App() {
const [items,setItems]=useState([]);
return (
<div className="App">
<button onClick={(e)=>{
setItems([...items,parseInt(Math.random()*500,10)]);
}}>Add list</button>
<ul>
{items.map((item,idx)=>{
return <Child text={item} key={idx} idx={idx}/>
})}
</ul>
<ShouldNotRender/>
</div>
);
}
You can see, whenever we add an item to the list, then ShouldNotRender is also rerendering. ShouldNotRender is not dependent on any of the states, then it should not rerender. It is a static and heavy component(canvas).
I thought of splitting the component into two other components, one is static and the other is dynamic. Well, it worked fine but i don't understand that why react rerender ShouldNotRender component.
You can also use React.memo()
const ShouldNotRender = React.memo(() => {
console.log("Should not render");
return <p>Should not render</p>;
});
To prevent re-rendering, you can memoize the component.
export default function App() {
const [items,setItems]=useState([]);
const memoizedShouldNotRender = useMemo(() => <ShouldNotRender/>, []);
return (
<div className="App">
<button onClick={(e)=>{
setItems([...items,parseInt(Math.random()*500,10)]);
}}>Add list</button>
<ul>
{items.map((item,idx)=>{
return <Child text={item} key={idx} idx={idx}/>
})}
</ul>
{memoizedShouldNotRender}
</div>
);
}
See documentation: https://reactjs.org/docs/hooks-reference.html#usememo

Pass two children as props to a React component

I have a component like this
const Component = () => {
return (
<div>
<div className="data1">
{children}
</div>
<div className="data1">
{children2}
</div>
</div>
)
}
I would like to have Item1 and Item2 inside the "data1" div, and some other components inside the "data2" div. Writing the next code I only have Item1 and Item2 as children but I don't know how to pass the second children (for example Item3 and Item4)
<Component>
<Item1/>
<Item2/>
</Component>
I have to reuse the Component multiple times into the app so a function called children2 that returns the elements is not a good idea because they are different depending on where I use the component.
The recommended way is to create custom props for each child:
const App = () => <Component child1={<Item1 />} child2={<Item2 />} />
const Component = ({child1, child2}) => {
return (
<div>
<div className="data1">
{child1}
</div>
<div className="data1">
{child2}
</div>
</div>
)
}
const Item1 = () => <p>Item 1</p>
const Item2 = () => <p>Item 2</p>
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I've made an StackBlitz in order for you to check a simple solution to render multiple Children been pass to a component. But to resume my solution, if you do this:
<Component>
<Item1/>
<Item2/>
</Component>
You could in Component.js (or wherever the Component is defined) map (this.props.children.map({})) the prop children in order to get the details of every child past to the <Component />.
You can pass multiple components as follows:
import React from 'react'
function Child (props) {
return (<>
<div>{props.child1}</div>
<div>{props.child2}</div>
</>
)
}
export default Child
import React from 'react'
function Parent {
return (
<>
<Child
children1={<Item1/>}
children2={<Item2/>}
/>
</>
)
}
export default Parent
till me if it work
const Component = (item1,item2) => {
return (
<div>
<div className="data1">
{item1}
</div>
<div className="data1">
{item2}
</div>
</div>
)
}
how to used it
which item one what you want to add as item like this <item1/>
<Component item1={item1} item2={item2}/>

Wrong pass prop in react component

I have simple Reactjs app that includes the Card and Modal components. every Card must have a Modal that when clicking on "Show More" button, open it.
Modal should only show the title on its Card and my problem is passed props to Modal, just send the title of the last Card And not about itself!
In summary, the prop of title received properly in Card component but Card component can't pass title to Modal correctly.
Here is my app in CodeSandBox: Demo
Card Components:
const Card = props => {
const { children, title } = props;
const { isShowModal, setIsShowModal } = useContext(MainContext);
const showModalHandler = () => {
setIsShowModal(true);
};
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-content">{children}</div>
<div className="card-footer">
<button onClick={showModalHandler}>Show More</button>
</div>
{isShowModal && <Modal title={title} />}
</div>
);
};
Modal Component:
const Modal = props => {
const { setIsShowModal } = useContext(MainContext);
const closeModalHandler = () => {
setIsShowModal(false);
};
const { title } = props;
return (
<div className="modal">
<h2>Modal: {title}</h2>
<p>
You cliked on <b>{title}</b> Card
</p>
<hr />
<button onClick={closeModalHandler}>Close</button>
</div>
);
};
Note: I use Context for control open/close modal in isShowModal state and maybe that's the problem?
Just as you thought the problem seems to be the useContext that you are using. So I made a couple of changes to the code, most importantly using useState. I recommend you read the documentation about useContext and when to use it. Here is the updated code:
Card.js
import React, { useState } from "react";
import Modal from "./Modal";
import "./Card.scss";
const Card = props => {
const { children, title } = props;
const [ isShowModal, setIsShowModal ] = useState(false);
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-content">{children}</div>
<div className="card-footer">
<button onClick={() => setIsShowModal(true)}>Show More</button>
</div>
{isShowModal && <Modal setIsShowModal={setIsShowModal} title={title} />}
</div>
);
};
export default Card;
Modal.js
import React from "react";
import "./Modal.scss";
const Modal = props => {
const { title } = props;
return (
<div className="modal">
<h2>Modal: {title}</h2>
<p>
You cliked on <b>{title}</b> Card
</p>
<hr />
<button onClick={() => props.setIsShowModal(false)}>Close</button>
</div>
);
};
export default Modal;
As you can see, Modal.js component doesn't have to be a stateful component. You can pass as a prop the setIsShowModal function from Card.js component. That way you can make the modal a reusable component.

Why I get props is undefined?

import React from "react";
import styles from "../articles.css";
const TeamInfo = props => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{props.team.city} {props.team.name}
</span>
</div>
<div>
<strong>
W{props.team.stats[0].wins}-L{props.team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
export default TeamInfo;
the code that render this
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => {
const teaminfofunc = (team) => {
return team ? (
<TeamInfo team={team}/>
) : null
}
return (
<div>
{teaminfofunc(props.teamdata)}
</div>
)
}
export default header;
and I am getting error TypeError: props is undefined in line 8 why is that ?
Line 8 is
background: url('/images/teams/${props.team.logo}')
Update:
I found that in index.js the componentWillMount bring the data correctly but in the render() those data (article and team) was not passed to render, any idea why ?
import React, {Component} from 'react';
import axios from 'axios';
import {URL} from "../../../../config";
import styles from '../../articles.css';
import Header from './header';
import Body from './body';
class NewsArticles extends Component {
state = {
article:[],
team: []
}
componentWillMount() {
axios.get(`${URL}/articles?id=${this.props.match.params.id}`)
.then(response => {
let article = response.data[0];
axios.get(`${URL}/teams?id=${article.team}`)
.then(response => {
this.props.setState({
article,
team:response.data
})
})
})
}
render() {
const article = this.state.article;
const team = this.state.team;
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
}
export default NewsArticles;
You render your component immediately, long before your AJAX call finishes, and pass it the first element of an empty array:
<Header teamdata={team[0]}
componentWillMount does not block rendering. In your render function, short circuit if there's no team to render.
render() {
const { article, team, } = this.state;
if(!team || !team.length) {
// You can return a loading indicator, or null here to show nothing
return (<div>loading</div>);
}
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
You're also calling this.props.setState, which is probably erroring, and you should never call setState on a different component in React. You probably want this.setState
You should always gate any object traversal in case the component renders without the data.
{props && props.team && props.team.logo ? <div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div> : null}
This may not be you exact issue, but without knowing how the prop is rendered that is all we can do from this side of the code.
Update based on your edit. You can't be sure that props.teamdata exists, and therefore your component will be rendered without this data. You'll need to gate this side also, and you don't need to seperate it as a function, also. Here is an example of what it could look like:
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => (
<div>
{props.teamdata ? <TeamInfo team={props.teamdata}/> : null}
</div>
)
export default header;
First -- while this is stylistic -- it's not good practice to pass props directly to your functional component. Do this instead.
const TeamInfo = ({team}) => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{team.city} {team.name}
</span>
</div>
<div>
<strong>
W{team.stats[0].wins}-L{team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
Second, you might just want to do some kind of null check. If team is undefined the first time the component tries to render, you might just want to render null so you're not wasting cycles.
In case this isn't the issue, you'd learn a lot by console.log-ing your props so you know what everything is each time your component tries to render. It's okay if data is undefined if you're in a state that will soon resolve.

Categories

Resources