Request a React component to take action other than render - javascript

I wish to send a message to a React component to request it do something other than re-render.
Specifically, I wish to request a component containing a grid to save the grid data.
I know that I can send data into a component via props and that change of state will trigger re-rendering.
However, how can I pass an event rather than data into the component?
The solution I am currently using is to use events: https://github.com/primus/EventEmitter3. My concern with this approach is that it is not linked to the React lifecycle and as such, events might reach the component at an inappropriate stage in the component lifecycle.
Is there an idiomatic way that I can do this just using React?

"However, how can I pass an event rather than data into the component?"
Not exactly sure what you mean. If I think I understand what you're trying to do, you can pass a function down to the lower component which gets fired off on the parent level:
const Child = ({ handleChildClick }) => {
const someData = { name: "John", age: 69 };
// Where someData can either be from state or a variable (just using functional component as it's easier to read imo)
return (
<div>
<button onClick={handleChildClick(someData)}>Click me</button>
</div>
);
};
const Parent = () => {
const childClicked = data => {
console.log("Data from child: ", data);
};
return (
<div>
<Child handleChildClick={childClicked} />
</div>
);
};
So when the child's event is fired off

Related

Update list display in Next.js when passing data from children

I have a Page component in Next.js that looks somewhat like this:
export default function Index({ containers }) {
const [containerListState, setContainerListState] = useState(containers);
const updateContainerList = (container) => {
containers.push(container);
setContainerListState(containers);
};
return (
<>
<FormCreateContainer updateContainerList={updateContainerList} />
<ContainerList containers={containerListState} />
</>
);
}
FormCreateContainer allows for the creation of a container. When that API call resolves, I call updateContainerList and pass the newly created container to the parent. This works.
However, I also want to pass this container to ContainerList (which is a simple dynamic list using containers.map()) as part of the containers prop. While pushing to containers works as intended, Next does not live-update my list of containers; the newly created container only shows up when I reload the page.
I thought that including useEffect and changing updateContainerList as follows might work, but alas it did not.
const updateContainerList = (container) => {
containers.push(container);
};
useEffect(() => {
setContainerListState(containers);
}, [containers]);
How do I correctly pass data from a child to a parent component, therethrough passing it to a different child component and updating a dynamic list without reloading the page myself?
I really do appreciate any help as I have done extensive research that did not help me achieve my goal.
First and foremost, you should never mutate the value of a prop.
I think you should just use setContainerListState to update the data without mutating the prop like this:
const updateContainerList = (container) => {
setContainerListState(containers => [...containers, container]);
};
This will re-render your component and all its children automatically.

Pass data from grandchild to parent in React

Hello i have an array called info[] in a grandchild component and i want my parent component when a button is clicked to access the array. I also want a sibling component to have access to it. How is this possible .. i am a bit confused.
Should I use use-context ?
Thank you!
If I have understand what you are asking it could be something like this.
const GrandChild = ({ setParentInfo }) => {
const info = [1, 2, 3];
const handleClick = () => {
setParentInfo(info);
};
return <button onClick={handleClick}>Set parent info</button>;
};
const Sibling = ({ parentInfo }) => {
return <div>{parentInfo.length}</div>; // Do whatever you need with parentInfo
};
const Parent = () => {
const [parentInfo, setParentInfo] = useState([]);
return (
<div>
<GrandChild setParentInfo={setParentInfo} />
<Sibling parentInfo={parentInfo} />
</div>
);
};
Here you don't need context because you don't have that much layers but if you need to drill down the props than use a context.
If you want to share state among many different components in your application, and you believe that passing State as a prop is "a very long journey" to move around I'm probably you should consider something like use context hook.
Either way what you just described seems like a simple use case witch will not need context.
What you should do is:
On the parent you have [state, setState]
On the current component pass setStat as a prop to child component and then from child component pass setState as a prop to grandchild component.
Then on grandchild component you can do something like:
props.setState(array).
So now on the parent component the variable state will have been updated with the value array from the grandchild component.
If you want to pass state to siblings component, and by sibling I assume you mean sibling of the parent,
Then you should move the state from parent one level up let's say the parent of the parent.. and do what I've described above.
So create useState high in your component tree,
And pass State and setState as well to children as props, setState will be passed as function, so you can call it on the grandchild component or any other component

React setState of Parent component without rerendering the Child

I have a parent Component with a state variable that gets changed by one of its child components upon interaction. The parent then also contains some more components based on the data in the state variable.
The problem is that the child component rerenders when the state of its parent changes because the reference to the setState function changes. But when I use useCallback (as suggested here), the state of my parent just does not update at all.
This is my current setup:
function ArtistGraphContainer() {
const [artistPopUps, setArtistPopUps] = useState([])
const addArtistPopUp = useCallback(
(artistGeniusId, xPos, yPos) => {
setArtistPopUps([{artistGeniusId, xPos, yPos}].concat(artistPopUps))
},
[],
)
return (
<div className='artist-graph-container'>
<ArtistGraph addArtistPopUp={addArtistPopUp} key={1}></ArtistGraph>
{artistPopUps.map((popUp) => {
<ArtistPopUp
artistGeniusId={popUp.artistGeniusId}
xPos={popUp.xPos}
yPos={popUp.yPos}
></ArtistPopUp>
})}
</div>
)
}
And the Child Component:
function ArtistGraph({addArtistPopUp}) {
// querying data
if(records) {
// wrangling data
const events = {
doubleClick: function(event) {
handleNodeClick(event)
}
}
return (
<div className='artist-graph'>
<Graph
graph={graph}
options={options}
events={events}
key={uniqueId()}
>
</Graph>
</div>
)
}
else{
return(<CircularProgress></CircularProgress>)
}
}
function areEqual(prevProps, nextProps) {
return true
}
export default React.memo(ArtistGraph, areEqual)
In any other case the rerendering of the Child component wouldn't be such a problem but sadly it causes the Graph to redraw.
So how do I manage to update the state of my parent Component without the Graph being redrawn?
Thanks in advance!
A few things, the child may be rerendering, but it's not for your stated reason. setState functions are guaranteed in their identity, they don't change just because of a rerender. That's why it's safe to exclude them from dependency arrays in useEffect, useMemo, and useCallback. If you want further evidence of this, you can check out this sandbox I set up: https://codesandbox.io/s/funny-carson-sip5x
In my example, you'll see that the parent components state is changed when you click the child's button, but that the console log that would fire if the child was rerendering is not logging.
Given the above, I'd back away from the usCallback approach you are using now. I'd say it's anti-pattern. As a word of warning though, your useCallback was missing a required dependency, artistPopUp.
From there it is hard to say what is causing your component to rerender because your examples are missing key information like where the graphs, options, or records values are coming from. One thing that could lead to unexpected rerenders is if you are causing full mounts and dismounts of the parent or child component at some point.
A last note, you definitely do not need to pass that second argument to React.memo.

React Redux when the child component button is pressed how to have the websocket client send data if the websocket client is in the parent?

In my parent component I have
componentWillReceiveProps(nextProps) {
if(nextProps.message) {
websocket.send(JSON.stringfy(nextProps.message));
}
}
My child component has this button when clicked it runs this method:
onClickButton = () => {
// My action dispatcher for reducer
this.props.sendMessage(this.props.data);
}
My thought process was that to pass something from child to parent component - I will utilize the store. So whenever the button in a child component of parent component (which is a list) is clicked then the data that comes from child component will be accessible to the parent. The reason why I did this is because the websocket client is situated at the parent component.
The problem is that when the button in the child component is clicked once it runs the componentWillReceiveProps(nextProps) method in the parent component;
however, when I click it again it doesn't run. I understand that the payload doesn't change but gets set to the same thing again, so I thought when the dispatch action is called again after clicking on the button it will at least have the componentWillReceiveProps(nextProps) method in parent run again?
Is there a way to have to have it so that, when I click on a button of a child component - the websocket that is in my parent component send the data of the individual child component?
Essentially, I wanted to have the websocket client send messages to the server when the button in the child component is clicked
perhaps a different way to think about your question is to simply pass a function sendMessageToServer and data as props to the child component. as a functional component, it would look something like this:
export default function Parent() {
// this child component doesnt have to be here, can be imported or declared outside of this Parent functional component. it's here for demo purposes.
const Child = ({ data, sendMessageToServer }) => {
return <button onClick={() => sendMessageToServer(data)}>Send Data</button>;
};
const websocket = {
send(msg) {
console.log(msg);
}
};
const sendMessageToServer = msg => {
websocket.send(JSON.stringify(msg));
};
const data = { msg: "message from parent" };
return (
<div className="App">
<Child {...{ data, sendMessageToServer }} />
</div>
);
}
in doing so, the function executes in the parent's scope whenever the button is clicked in the child. this can of course be written as a class component as well. for illustration purposes, i thought a functional component is easier to understand.

How to render results of search in another component in React?

I am a beginner in React and using Webpack to build into a bundle.js and display.
My need is to provide some search forms and accordingly display result below search form. So, for modularizing it, I have create a parent component containing both search and result view components.
Now that I have designed a form and written form onSubmit event handler, how should i proceed to render API results (dummy json for now) in the result component. I am attaching a brief pic of my intention for your reference.
Here is my solution based on my comments above: https://codesandbox.io/s/q85oq0w10q
Create an HOC that will hold the state of your app, then your two children are merely used for rendering purpose and can be made pure functions
import React from 'react';
import { render } from 'react-dom';
const Result = ({results}) => {
return results.map((r, i) => <div key={i}>{r}</div>);
}
const Search = (props) => {
const {
searchQuery,
onChange,
search
} = props;
return <div>
<input
type="text"
value={searchQuery}
onChange={onChange}
/>
<button onClick={search}>Search</button>
</div>;
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
searchQuery: '',
results: []
}
this.onSearchQueryChange = this.onSearchQueryChange.bind(this);
this.onSearch = this.onSearch.bind(this);
}
onSearchQueryChange(e) {
this.setState({searchQuery: e.target.value});
}
onSearch() {
// Simulate AJAX call
setTimeout(() => {
this.setState({results: [0, 1, 2, 3, 4]});
}, 1000)
}
render() {
const {results, searchQuery} = this.state;
return <div>
<Search
searchQuery={searchQuery}
onChange={this.onSearchQueryChange}
search={this.onSearch}
/>
<Result results={results} />
</div>;
}
}
I believe this is what you are looking for. Worked example fiddle
So the idea is to keep your result in Container component and pass it down to Result component. Also Container component should pass a callback function to your Search component and it will be triggered with a API result from the Search component.
Also you may want to take a look at Redux. This is a library for managing your app state. With Redux it can be achieved in more easiest way.
Hope it will help you.
In my opinion if you are new in React. You should learn first using React. Because I see that a lot of people use Redux(or any other app state handler) as a storage for any data.
Your case is actually very good example to learn two of the basic ideas: if children need similar thing, parents should handle it and params go down and callbacks go up.
So all your logic has to be in Container Component, make callback of http request function, with update of state(setState) after resolving response and send to Search Component. onSubmit call that callback, and send data to Result Component.
So you no need of additional library(maybe for http request).
1 Class component(Container Component). 2 Probably stateless functional components(Search Component & Result Component).

Categories