Data transfer between components in React - javascript

I'm new to react and struggling with how to transfer data from one component to another.
I referred some tutorials and blogs, but things aren't working for me.
I have two child components, Body-content.jsx and Profile.jsx and 1 parent component parent.jsx
I want to transfer some data from Body-content.jsx toProfile.jsx.
Here's my code
Body-content.jsx
class BodyContent extends React.Component {
componentDidMount() {
this.getUserList()
}
getUserList(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => {
return result.json();
}).then(data =>{
this.setState({
users : data
})
})
}
render() {
const user = this.state.users.map((userData, i) => (
<CardBody>
...some code here
<Button color='primary' onClick={e => this.viewProfile(userData)}>View Profile</Button>
</CardBody>
</Card>
));
return (
<>
<div>{user}</div>
</>
)
}
viewProfile = function (data) {
}
}
export default BodyContent;
profile.jsx
class Profile extends React.Component {
componentDidMount() {
}
render() {
return (
<>
<TopNav />
<main className="profile-page" ref="main">
<section>
//code goes here
</section>
</main>
</>
);
}
}
export default Profile;

Store your data in parent component and send it as props to children.
If you have to change it in one of them, then send (also as prop) the function, which will change data in parent component.
Code sample:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {someData: ''};
}
changeData(newData) {
this.setState({
someData: newData
});
}
render() {
return (
<>
<Child1 setData={this.changeData} data={this.state.someData} />
<Child2 setData={this.changeData} data={this.state.someData} />
</>
)
}
}
Both of them will be able to change data in parent component using this.props.setData('newData')

If you want to share your state across the child component then you may need to move that property in parent's state which you may able to share between two child components.
Sibling to Sibling
Parent Component
Any to Any
Observer Pattern
Global Variables
Context
How to make a shared state between two react components?

You can hoist state to parent component:
class Parent extends Component {
state = {
users
};
handleUsersChange = users => this.setState({ users });
render() {
const { users } = this.state;
return (
<React.Fragment>
<Body-content onUsersChange={ this.handleUsersChange } />
<Profile users={ users } />
</React.Fragment>
);
}
}
...
class BodyContent extends React.Component {
getUserList(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => {
return result.json();
}).then(data =>{
this.props.handleUsersChange(data);
})
}
}

In ReactJs the data flow is uni-directional - Top-to-bottom. The parents passes the data to respective children.
Here, since you want to share the data between two siblings. The data should be first available to their parent.
In order to do so, your getUserList api call should be inside of your parent, i.e
your parent.jsx component.
From there you can pass the data as props to both the siblings.
Let me know if you need and further explanation to this. If needed, please share your parent.jsx code. I can help you further.

Hi and welcome to the world of React,
If you want to share data between siblings components, you should always keep in mind that you should store your data at the highest common component (doc).
In your case that would mean having a parent component that holds your users list and the current profile in its state, and then render accordingly your list and the current profile.
A little example to get you on the "right track" sandbox:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
currentIndex: null
}
}
componentDidMount() {
this.getUserList()
}
getUserList(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
.then(data => {
this.setState(() => ({
users : data
}))
});
}
updateCurrent = (index) => {
this.setState(() => ({ currentIndex: index }));
}
render() {
return (
<div>
<UserList
users={this.state.users}
updateCurrent={this.updateCurrent}
/>
{this.state.currentIndex !== null && (
<Profile user={this.state.users[this.state.currentIndex]} />
)}
</div>
)
}
}
const UserList = (props) => (
<div>
{props.users.map((user, i) => (
<div key={user.id}>
<button color='primary' onClick={() => props.updateCurrent(i)}>View Profile</button>
</div>
))}
</div>
);
const Profile = ({ user }) => (
<div className="profile-page">
{user.name}
</div>
);
Feel free to ask clarification if needed.
Happy coding,

Related

How to call child1(class component) method from child2(class component) via a parent(functional component) in React JS (V 2015)? [duplicate]

Let's say I have a component tree as follows
<App>
</Header>
<Content>
<SelectableGroup>
...items
</SelectableGroup>
</Content>
<Footer />
</App>
Where SelectableGroup is able to select/unselect items it contains by mouse. I'm storing the current selection (an array of selected items) in a redux store so all components within my App can read it.
The Content component has set a ref to the SelectableGroup which enables me to clear the selection programatically (calling clearSelection()). Something like this:
class Content extends React.Component {
constructor(props) {
super(props);
this.selectableGroupRef = React.createRef();
}
clearSelection() {
this.selectableGroupRef.current.clearSelection();
}
render() {
return (
<SelectableGroup ref={this.selectableGroupRef}>
{items}
</SelectableGroup>
);
}
...
}
function mapStateToProps(state) {
...
}
function mapDispatchToProps(dispatch) {
...
}
export default connect(mapStateToProps, mapDispatchToProps)(Content);
I can easily imagine to pass this clearSelection() down to Contents children. But how, and that is my question, can I call clearSelection() from the sibling component Footer?
Should I dispatch an action from Footer and set some kind of "request to call clear selection" state to the Redux store? React to this in the componentDidUpdate() callback in Content and then immediately dispatch another action to reset this "request to call clear selection" state?
Or is there any preferred way to call functions of siblings?
You can use ref to access the whole functions of Content component like so
const { Component } = React;
const { render } = ReactDOM;
class App extends Component {
render() {
return (
<div>
<Content ref={instance => { this.content = instance; }} />
<Footer clear={() => this.content.clearSelection() } />
</div>
);
}
}
class Content extends Component {
clearSelection = () => {
alert('cleared!');
}
render() {
return (
<h1>Content</h1>
);
}
}
class Footer extends Component {
render() {
return (
<div>Footer <button onClick={() => this.props.clear()}>Clear</button>
</div>
);
}
}
render(
<App />,
document.getElementById('app')
);
<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="app"></div>
I think the context API would come handy in this situation. I started using it a lot for cases where using the global state/redux didn't seem right or when you are passing props down through multiple levels in your component tree.
Working sample:
import React, { Component } from 'react'
export const Context = React.createContext()
//***************************//
class Main extends Component {
callback(fn) {
fn()
}
render() {
return (
<div>
<Context.Provider value={{ callback: this.callback }}>
<Content/>
<Footer/>
</Context.Provider>
</div>
);
}
}
export default Main
//***************************//
class Content extends Component {
render() {
return (
<Context.Consumer>
{(value) => (
<div onClick={() => value.callback(() => console.log('Triggered from content'))}>Content: Click Me</div>
)}
</Context.Consumer>
)
}
}
//***************************//
class Footer extends Component {
render() {
return (
<Context.Consumer>
{(value) => (
<div onClick={() => value.callback(() => console.log('Triggered from footer'))}>Footer: Click Me</div>
)}
</Context.Consumer>
)
}
}
//***************************//
Assuming content and footer and in there own files (content.js/footer.js) remember to import Context from main.js
According to the answer of Liam , in function component version:
export default function App() {
const a_comp = useRef(null);
return (
<div>
<B_called_by_a ref={a_comp} />
<A_callB
callB={() => {
a_comp.current.f();
}}
/>
</div>
);
}
const B_called_by_a = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
f() {
alert("cleared!");
}
}));
return <h1>B. my borther, a, call me</h1>;
});
function A_callB(props) {
return (
<div> A I call to my brother B in the button
<button onClick={() => { console.log(props); props.callB();}}>Clear </button>
</div>
);
}
you can check it in codesandbox
One way I use to call the sibling function is to set a new date.
Let me explain more:
In their parent we have a function that set new date in a state (the state's name is something like "refresh date" or "timestamp" or something similar).
And you can pass state to sibling by props and in sibling component you can use useEffect for functional components or componentDidUpdate for class components and check when the date has changed, call your function .
However you can pass the new date in redux and use redux to check the date
const Parent = () => {
const [refreshDate, setRefreshDate] = useState(null);
const componentAClicked = () => setRefreshDate(new Date())
return (
<>
<ComponentA componentAClicked={componentAClicked}/>
<ComponentB refreshDate={refreshDate}/>
</>
}
const ComponentA = ({ componentAClicked}) => {
return (
<button onClick={componentAClicked}>click to call sibling function!!<button/>
)
}
const ComponentB = ({ refreshDate }) => {
useEffect(()=>{
functionCalledFromComponentA()
},[refreshDate]
const functionCalledFromComponentA = () => console.log("function Called")
return null
}
Functional components & TypeScript
Note 1: I've swapped useRef for createRef.
Note 2: You can insert the component's prop type in the second type parameter here: forwardRef<B_fns, MyPropsType>. It's confusing because the props & ref order are reversed.
type B_fns = {
my_fn(): void;
}
export default function App() {
const a_comp = createRef<B_fns>();
return (
<div>
<B_called_by_a ref={a_comp} />
<A_callB
callB={() => {
a_comp.current?.my_fn();
}}
/>
</div>
);
}
const B_called_by_a = forwardRef<B_fns>((props, ref) => {
useImperativeHandle(ref, () => ({
my_fn() {
alert("cleared!");
}
}));
return <h1>B. my borther, a, call me</h1>;
});
function A_callB(props) {
return (
<div> A I call to my brother B in the button
<button onClick={() => { console.log(props); props.callB();}}>Clear </button>
</div>
);
}

Callback function for ref not working while using forwardRef for class components

I was trying to pass a callback function to the ref object of a component which uses forward ref to pass to a class component. I have noticed that the callback function is only triggered once, so I am not able to get the changes in the Element.
I am not sure what the issue is could you please help me with this.
The component to which I am passing the ref
class ElemComponent extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
};
this.ElemComponentRef = props.forwardRef || React.createRef();
}
render() {
return (
<div ref={this.ElemComponentRef}>
Div has a ref
</div>
)
}
}
export default React.forwardRef((props, ref) => <ElemComponent
forwardRef={ref} {...props}
/>);
My parent component
const App = () => {
const [value, setValue] = React.useState("");
return (
<>
<button
onClick={() => {
setValue("white" + value);
}}
>
Click
</button>
<MyForwardElement
value={value}
ref={() => console.log("this is the ref")}
/>
</>
);
};
this.ElemComponentRef = props.forwardRef || React.createRef();
Removing the above code to
<div ref={this.props.forwardRef}>
is solving my issue but I need the ref object even if it is not passed
I can't really updated the child component to a functional component right now, if there is any fix/explanation for this it would be really helpful.
Thank you

i cant update the state in my parent component from my child (class) component

It's a recipe app
My Search (class) component is the child component
The state in my parent component need to to update to the Search result.
My API is in a separate component
I can successfully console.log the result.
class App extends Component {
state = {
recipes: []
};
componentDidMount() {
recipesData()
.then(data => this.setState({ recipes: data.recipes }))
}
render() {
return (
<div className="App">
<Search/>
<RecipeList data={this.state.recipes} />
</div>
class Search extends Component {
state = {
search: ""
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
const key = "KEY";
const url = `https://www.food2fork.com/api/search?key=${key}&q=${
this.state.search
}&page=2`;
fetch(url)
.then(res => res.json())
.then(res => {console.log(res)})
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="search"
value={this.state.search}
onChange={this.handleChange}
/>
<button type="submit">Search</button>
</form>
</div>
Thanks in advance.
the data inside RecipeList is passed from the App component. And When Search component changes, the RecipeList will change too.
Therefore, when the Search component changed, we have to tell App that data had changed.
So the common method is just write handleSubmit in App and pass it to Search:
Here is a working codesandbox:
https://codesandbox.io/s/quirky-architecture-ktj3q?fontsize=14
(I mocked the fetch part)
Update : I added a functional component version of this, if you are interested.
https://codesandbox.io/s/shy-worker-8s3zi?fontsize=14
More about functional component and Hook : https://reactjs.org/docs/hooks-intro.html
You can update parent component state just by passing an update method to the child component and call that method from child component and pass response as a parameter to a method
class App extends Component {
state = {
recipes: []
};
componentDidMount() {
}
updateRecipe = (data) =>{
this.setState({recipes:data.recipes})
}
render() {
return (
<div className="App">
<Search update={(data)=>this.updateRecipe(data)}/>
<RecipeList data={this.state.recipes} />
</div>
)}
}
class Search extends Component {
state = {
search: ""
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
const key = "KEY";
const url = `https://www.food2fork.com/api/search?key=${key}&q=${
this.state.search
}&page=2`;
fetch(url)
.then(res => res.json())
.then(res => {this.props.update(res)})
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="search"
value={this.state.search}
onChange={this.handleChange}
/>
<button type="submit">Search</button>
</form>
</div>
)}
}

dropdown is not open when using context

I am trying to use both context and static approach for more flexible dropdown so i can use any element to show in the dropdown title(may be button or icon or just text or anything that is relevant). However, my dropdown is not toggled. I mean toggleMenu function is not triggered so that it can ensure whether to show the dropdown item or not.
Here is how i have done
class Title extends React.Component {
render() {
return (
<Dropdown.Consumer>
{({ showMenu, toggleMenu }) => {
return (
<React.Fragment>
<DropdownTitle>
{React.cloneElement(this.props.children, {
showMenu,
onClick: () => toggleMenu()
})}
</DropdownTitle>
</React.Fragment>
);
}}
</Dropdown.Consumer>
);
}
}
class Item extends React.Component {
render() {
return (
<Dropdown.Consumer>
{({ showMenu }) => {
return (
<React.Fragment>
{showMenu && <DropdownItem>{this.props.children}</DropdownItem>}
</React.Fragment>
);
}}
</Dropdown.Consumer>
);
}
}
const DropdownContext = React.createContext();
class Dropdown extends Component<Props> {
static Title = Title;
static Item = Item;
static Consumer = DropdownContext.Consumer;
state = {
showMenu: false,
toggleMenu: () => {}
};
toggleMenu = () => {
console.log('did not trigger this function when clicking')
this.setState(
prevState => ({
showMenu: !prevState.showMenu
}),
() => this.props.toggleState(this.state.showMenu)
);
};
render() {
const { children } = this.props;
return (
<React.Fragment>
<DropdownContext.Provider value={{ ...this.state }}>
{children}
</DropdownContext.Provider>
</React.Fragment>
);
}
}
export default Dropdown;
I have created a sandbox either. Here it is
https://codesandbox.io/s/k3lw98jlov
I think there are some issues with your code. First how you use the context value. There is no way of getting to the function because you don't provide it. I'd do it like so:
<DropdownContext.Provider value={{ ...this.state, toggleMenu: this.togglemenu }}>
{children}
</DropdownContext.Provider>
otherwise the toggelmenu function is just an empty function (you need to remove it from the state).
Second I took a quick look at the sandbox and I don't really see where the context is used, i.d. where the function should be called. Make sure you use the context at the right place. I think what you need to do is to connect the index.js to the context, because here you are listening for a button-click. Correct me if I am wrong or if I didn't understand something properly.
Hope that helps.
Regards

React: Cannot update during an existing state transitio

I have a problem with React.
When I press the "+" button, this console message appears and nothing happens:
Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`
I found several questions with similar titles, but common thing among them is that there were calls of functions with setState inside render method.
My render method has no calls, but error appears.
Why?
Thank you for reading.
Code:
import React from 'react';
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input
ref={node => {
input = node;
}}
/>
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
const TodoList = ({todos, remove}) => {
// Map through the todos
const todoNode = todos.map((todo) => {
return (<Todo todo={todo} key={todo.id} remove={remove}/>)
});
return (<ul>{todoNode}</ul>);
};
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
};
window.id = 0;
class TodoApp extends React.Component {
constructor(props) {
// Pass props to parent class
super(props);
// Set initial state
this.state = {
data: []
}
}
// Add todo handler
addTodo(val) {
// Assemble data
const todo = {text: val, id: window.id++}
// Update data
this.state.data.push(todo);
// Update state
console.log('setting state...');
this.setState({data: this.state.data});
}
// Handle remove
handleRemove(id) {
// Filter all todos except the one to be removed
const remainder = this.state.data.filter((todo) => {
if (todo.id !== id) return todo;
});
// Update state with filter
this.setState({data: remainder});
}
render() {
// Render JSX
return (
<div>
<Title />
<TodoForm addTodo={
(val)=>{
this.addTodo(val)
}
}/>
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
</div>
);
}
}
export default TodoApp;
In your render method for Todo you invoke remove, which is where your erroneous state update happens.
To fix this, return a function from the handleRemove method of TodoApp that updates the state. Simplified version:
handleRemove(id) {
return () => {
...
this.setState({ data: remainder });
}
}
Also worth noting here that because you're using the current state, it's best to use the setState callback (which gets prevState as an argument), and not rely on this.state.
setState docs
Andy_D very helped and my answer has two solutions:
First in render function change
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
to
<TodoList
todos={this.state.data}
remove={() => this.handleRemove.bind(this)}
/>
or change code
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
to that:
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={() => remove(todo.id)}>{todo.text}</li>)
};

Categories