I need to listen to following events and do action accordingly
Form submit event - if the form submission is successful, I need to broadcast success event Added Name Record and submit the data along with it to 3rd party service. If not, I need to broadcast a failed event say Name submission failed along with where the error occurred.
Page visit/Route Change - Self explanatory
Click events - Can be from list item click and button click. In case of list item need to pass item details and in case of button just the name maybe
Searches - Any searches made should fire the event sending the keyword.
I am using React - 0.13 and Reflux store.
How can I approach towards it to handle the events at a global level and without writing code at each button click/submit event/search etc ?
What is the best approach ? Body handler won't be enough I feel for this level of customisation required!
P.S - When I say broadcast let's assume that I have a 3rd party function call to make instead.
Purely React ways:
Create base components to globally handle events with extensible event handling methods:
const MyForm = ({ onSubmit, children, ...props }) => (
<form {...props} onSubmit={evt => {
// do global handling
onSubmit(evt);
}}>
{children}
</form>
);
//use case
const CustomForm = () => (
<MyForm onSubmit={evt => /* custom handling */}>
{/* CustomForm inputs and such */}
</MyForm>
)
Alternatively, you could take the HOC approach and wrap each handler you need:
const MyFormHOC = FormComponent => {
return class extends React.Component {
render() {
const { onSubmit, ...props } = this.props;
return (
<FormComponent {...props} onSubmit={evt => {
// do global handling
onSubmit(evt);
}}/>
}
}
};
//use case
let CustomForm = ({ onSubmit, ...props }) => (
<form onSubmit={evt => onSubmit(evt)}>
{/* CustomForm inputs and such */}
</form>
);
CustomForm = MyForm(CustomForm);
Downside to HOC approach is that your components need to pass along the event handler to the DOM element that will ultimately call it.
You could go one step further and create a single base component or HOC that handles all of your global events and then start using that as needed on your other components. It would need to know which props are supported by what components, though (e.g., onSubmit doesn't belong on anything besides a form).
Related
I am having some OOP issues that are probably pretty simple. I have a class that renders some html. However it has an onClick that calls a function that sets a flag inside the class if the image is clicked. Now here is the issue, when I render this class object and click the button from a separate js file, it stays false. I want it to permanently change the flag to true when clicked. here is the class...
class Settings extends React.Component {
handleClick() {
this.flag = true;
console.log(this.flag)
}
render(){
return(
<img src="./img/leaf.png" alt="" onClick={() => this.handleClick()}/>
);
}
}
and here is the code that calls it from a separate file...
const settingsObj = new Settings();
console.log(settingsObj.flag);
I want the flag to be false until the button is clecked and then it permamently changes to true. But it only goes true until my page rerenders as new data comes in and it resets to false. I have tried constructors and a few other techniques with no success.
Normal OOP design principles don't always apply directly to React components. Components don't usually have instance properties, they mostly just have props and state (there are a few exceptions where you do use an instance property, like Animation objects in react-native, but these are rare).
You're kind of mixing the two things in a way that doesn't quite make sense here. Settings is a React component that renders an image, but it's also an object which you instantiate by calling new Settings(). If there are other components which depend on the value of flag, you might want to separate the accessing and storing of the flag from the render component, passing a value and a callback to the renderer.
const Settings = ({setFlag}) => {
return(
<img src="./img/leaf.png" alt="" onClick={() => setFlag(true)}/>
);
}
You've suggested that you like the Context API as a solution for making the flag value globally available. There are a few ways to set this up, but here's one.
Outside of any component, we create a FlagContext object that has two properties: a boolean value flag and callback function setFlag. We need to give it a default fallback value, which is hopefully never used, so our default callback just logs a warning and does nothing.
const FlagContext = createContext<FlagContextState>({
flag: false,
setFlag: () => console.warn("attempted to use FlagContext outside of a valid provider")
});
This FlagContext object gives up Provider and Consumer components, but it's up to us to give a value to the FlagContext.Provider. So we'll create a custom component that handles that part. Our custom FlagProvider uses a local state to create and pass down the value. I've used a function component, but you could use a class component as well.
const FlagProvider = ({children}) => {
const [flag, setFlag] = useState(false);
return (
<FlagContext.Provider value={{
flag,
setFlag
}}>
{children}
</FlagContext.Provider>
)
}
We want to put the entire App inside of the FlagProvider so that the whole app has the potential to access flag and setFlag, and the whole app gets the same flag value.
When you want to use the value from the context in a component, you use either the useContext hook or the Consumer component. Either way, I like to creating an aliased name and export that rather than exporting the FlagContext object directly.
export const FlagConsumer = FlagContext.Consumer;
export const useFlagContext = () => useContext(FlagContext);
With the Consumer, the child of the consumer is a function that takes the value of the context, which in out case is an object with properties flag and setFlag, and returns some JSX.
This is usually a function you define inline:
const SomePage = () => {
return (
<FlagConsumer>
{({flag, setFlag}) => (<div>Flag Value is {flag.toString()}</div>)}
</FlagConsumer>
)
}
But it can also be a function component. Note that when using a function component as the child, you must pass the component itself ({Settings}) rather than an executed version of it (<Settings />).
const Settings = ({ setFlag }) => {
return <img src="./img/leaf.png" alt="" onClick={() => setFlag(true)} />;
};
const SomePage = () => {
return <FlagConsumer>{Settings}</FlagConsumer>;
};
The preferred method nowadays is with hooks. We call useFlagContext() inside the body of the function component and it returns our context object.
const SomePage = () => {
const {flag, setFlag} = useFlagContext();
return <Settings setFlag={setFlag}/>
};
Both the consumer and the hook only work if they are inside of a flag context provider, so that's why we put it around the whole app!
const App = () => {
return (
<FlagProvider>
<SomePage />
</FlagProvider>
);
};
Complete example on CodeSandbox
For this kind of interactions, I highly recommend you to use Redux
Another think I'm sure you will benefit from, is switching to hooks and function components: less boilerplate and much flexible code.
Back to the goal, using Redux your code would look similar to this:
const Settings = (props) => {
const dispatch = useDispatch();
const flag = useSelector(state => state.yourStoreObj.flag);
handleClick() {
dispatch(yourCustomAction("UPDATE_FLAG", true));
}
return(
<img src="./img/leaf.png" alt="" onClick={() => handleClick()}/>
);
}
Explanation:
First of all, spend 15 mins and get used to React Redux. Here's a good practical article to start with. If you're not familiar with hooks, start learning them as that will change a lot, while you don't need to change a single line of what you've done so far.
We suppose there's a property in the store that is the "flag" property of that specific element. In this way, the property can be read by the component itself with the useSelector() operator, or can be read anywhere in your application with the same methodology from any other component.
In the same way, you can change the value by dispatching a change (see dispatch() function) and in the same way, you can do that from any other components.
So, let's say you want to change that property when a click occurs on a completely different component, this is how the other component may looks like
const OtherCoolComp = (props) => {
const dispatch = useDispatch();
handleClick() {
dispatch(yourCustomAction("UPDATE_FLAG", true));
}
return(
<button onClick={() => handleClick()}>
Click me!
</button>
);
}
So you're dispatching the same action, setting it to the value you prefer, from a component that doesn't know who is displaying that value.
I'm having some difficulty passing data that I have mapped from an API, it displays a bunch of Event cards based on how many events there are in the API array.
This my Event Cards component;
export default function EventCard() {
const classes = useStyles();
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios("http://localhost:23455/Event");
setData(result.data);
};
fetchData();
}, []);
const handleClick = (value) => () => {
console.log(value);
};
return (
<div>
<Row>
{" "}
{data.map((item) => (
<Card
className={classes.root}
style={{ marginRight: "25px", marginBottom: "25px" }}
>
<CardHeader
avatar={
<Avatar aria-label="recipe" className={classes.avatar}>
{item.icon}
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={item.name}
subheader="September 14, 2016"
/>
<CardMedia
className={classes.media}
image={LordsLogo}
title="Paella dish"
/>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
<p key={item.id}> {item.description}</p>
<p key={item.id}> {item.startDate}</p>
<p key={item.id}> {item.endDate}</p>
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<Button variant="outlined">Steward</Button>
</IconButton>
<IconButton aria-label="share">
<Button variant="outlined" onClick={handleClick({ item })}>
Tickets
</Button>
</IconButton>
</CardActions>
</Card>
))}
</Row>
</div>
);
}
I have an onclick function which logs what data is being added into "value" onclick, if I click a card, the console log the value of that specific card:
What I'm trying to do now is use this information in another component called ServiceForm. I want to be able to click the button, be linked to ServiceForm, and use the variables within "Item" in the ServiceForm component, Ideally as shown below;
<Form.Group as={Col} controlId="formGridForeName">
<Form.Label>**{item.description}**</Form.Label>
<Form.Control
name="firstName"
placeholder="Enter name"
onChange={this.handleChange}
/>
</Form.Group>
EventCard if a functionalComponent and ServiceForm is a class based component, how could I pass the information from the first to the latter? Thanks
Edit: To show component hierarchy
ServiceForm is rendered in ServiceApplication, as shown:
import * as React from "react";
import { ServiceForm } from "../components/ServiceForm";
class ServiceApplication extends React.Component {
render() {
return (
<React.Fragment>
<h1>Service Application</h1>
<h6>Users can use this page to apply for tickets.</h6>
<ServiceForm />
</React.Fragment>
);
}
}
export default ServiceApplication;
EventCard component is rendered in EventsPage, as shown below;
import EventCard from "../components/EventCards/EventCard";
class EventsPage extends React.Component {
render() {
return (
<React.Fragment>
<h1>Events</h1>
<h6>
Welcome to the Events, here you can apply for seats or tickets at the
events shown below.
</h6>
<Row>
<EventCard />
</Row>
</React.Fragment>
);
}
}
export default EventsPage;
The idea is to pass on the ID when Clicking the 'Tickets' button on the EventCard (the ID is being pulled from an API and mapped).
It's a question of how you want to store state in your app really.
Take a simple example where a component which is a child of another. In that case, the parent can store the state locally e.g. useState() and pass it down as props to the child.
const SomeParent = () => {
const [isTrue, setIsTrue] = React.useState(true)
return (
<Child isTrue={isTrue} />
)
}
There may be cases where you want to share state across multiple components, or across your whole app. In that case, you have a load of different options.
1. Lifting state up to the highest point you need.
It would be easiest to move your state which is shared between multiple components up to the highest point that the two components share.
e.g.
Section component <- store state here
Parent one
child one
child two
Parent two
child one
Here you can again either useState() or alternatively store the state in a useReducer() and pass dispatch down.
The data flow would then look something like this:
initialise state in the section component e.g. const [someState, setSomeState]
pass the setSomeState callback down to the Parent component and in turn child component as a prop e.g.
In the child component set state onClick using the callback e.g. onClick = {() => action(item)}
pass the state value down to your second Parent component and in turn child component e.g.
You have access to the state in your form
This can be an object or multiple state values. As complexity increases, I tend to reach for useReducer().
2. Implementing useContext OR Redux in your application to create an application state accessible from any component (This is a much bigger explanation and there are lots of resources on each...
NOTE: I'm not sure why you're using two sets of brackets here:
const handleClick = (value) => () => {
console.log(value);
};
and when you're calling handleClick you need to switch it for an arrow function if you're passing a value:
onClick={handleClick({ item })
...
onClick={() => handleClick(item)}
Try this:
const handleClick = (value) => {
console.log(value);
};
<Button variant="outlined" onClick={() => handleClick({ item })} />
basically, you want to trigger a class component function from a functional component by passing item prop, if it was a parent child components you can pass a function as a prop and trigger it whenever you want from your parent or child component.
but in your case both components are not connected, what you can do without using redux is to create a public local storage for ex let's call it localStorage.js :
var PublicData = (function() {
var data = null;
var getData = function() {
return data;
};
var setData = function(props) {
data = props;
};
var clearData = function() {
data = null;
};
return {
GET: getData,
SET: setData,
CLEAR: clearData
}
})();
export default PublicData;
then you import it and use like the following :
import PublicData from './PublicData.js';
PublicData.SET(apiData); // you set your data once you fetch it
const data = PublicData.GET(); // get data
PublicData.CLEAR(); // clear your data
Note : this is not an actual localStorage, but it work the same, it will help you to share variables between your components.
Parent
/ \
Service Event
I'm guessing from your question that a simplification of your app tree looks like the above.
The problem you are having is the following:
Parent
/ \
Service Event
Data
In a top down architecture Service does not know about Event and any data over there in the that branch.
So you have 2 options (likely more, but these 2 get you a long way):
Hoist the data
Parent
Data
/ \
Service Event
Store the data somewhere else and provide a mechanism for access
Data
Parent
/ \
Service Event
Option 1 can be achieved by passing props (the data) and functions (passed using props) to manipulate the data (or, indeed, populate it in the first place i.e. fetch it)
The flow would look something like:
Parent is stateful. Parent passes the current state of the data to children that care about it (in your case that looks like Service and Event). Parent passes a function to Event, which can be attached to a click handler within the Event subtree of elements.
Due to passing a function down, when that click handler is invoked it can set the state of the Parent, from which the normal top-down rendering flow will handle setting updates and passing the data changes down the tree, whereby they will (usually) invoke a re-render.
I think Recoil uses this method, and it is becoming increasing popular once more as we look to split applications up and eschew global state management.
Option 2 is where data management libraries like Redux, Cerebral, MobX et al live. If you’re familiar with the publisher/subscriber pattern then you'll know how they work.
In essence, these libraries hold shared state for your application, such as the data you have here. They then (typically) provide a pattern for you to manage changes to the state (via a published event) and ensure that components that subscribe to that data receive the new state and update.
The 'discussion' here differs from option 1:
The click handler publishes an event, which, in essence, asks for some manipulation to the data. Manipulation occurs (or not), and subscribers are updated. In your case, the click would tell the centralised store/s (i.e. anything listening) to fetch the data, when it is done and the data is changed (from empty to filled) it lets all concerned elements (or, quite possibly, the entire application tree and leverage top-down rendering) know about it and they can do what they need to do.
Which option you pick is entirely up to you, they both have pros and cons.
Good luck thinking about your data, how you store it, and how you use it!
I'm glad you asked this question because this is a problem that has been faced and solved a lot of the times. Other contributors have written nice comprehensive answers so I'll keep it short.
1. Move shared stated to a common ancestor.
This one's pretty straight forward but gets very hairy very quickly.
2. Use Context API
You can use react's context API to move code responsible for fetching data to your context and use that data anywhere in your app without a lot of hassle. This approach is getting quite popular nowadays. Take a look at this and here's a sweet library to help you get started.
3. Use a state management library (preferred)
You can choose to use any state management library you like to solve this problem. There are quite a few options, and redux is the most popular one and for a very good reason. It enables you in implementing separation for concern between your data and your UI, but is a bit verbose.
Mobx is also popular and has a good developer experience but I personally don't like that it enforces a certain style to your code.
If you're new to react and state management then these libraries could be really daunting. I'd suggest you start with zustand. It's super simple and gets the job done. ;)
You can simply use Custom Event https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent. It is not the best practice but it work.In EventCard, when user click you dispatch a Custom event with payload is item, then ServiceForm will listener that event and store item as state. Working demo here
https://codesandbox.io/s/distracted-aryabhata-ec6gc
EventCard
const handleClick = () => {
const item = {
description: "This is description"
};
const event = new CustomEvent("customEvent", {
detail: item
});
window.dispatchEvent(event);
};
Service Form
const [item, setItem] = useState(null);
...
useEffect(() => {
const handler = (e) => {
setItem(e.detail);
};
window.addEventListener("customEvent", handler);
return () => window.removeEventListener("customEvent", handler);
}, []);
Edited. People have suggested passing in values to my action creator but I've tried that every way I can think of and it fails to work.
I'm currently getting my first taste of Redux and trying to get my call to mapDispatchToProps to read information in a form on button click but I'm not clear as how to get it to do so. The form component is rendered by React, and so it's a question of being able to bind when it's available but Redux is a monkey wrench I don't know how to compensate for yet. Essentially I have this for my component:
import React from 'react';
import { connect } from 'react-redux';
import { action } from '../actions/actionFile';
const Add = (props) => (
<div className="add">
<input className="field-one" type="text" placeholder="One" />
<input className="field-two" type="number" placeholder="Two" />
<input className="field-three" type="number" placeholder="Three" />
<button onClick={() => props.addItem('Literally anything')}>+</button>
</div>
)
const mapDispatchToProps = (dispatch) => {
return {
action: () => dispatch(action({
// I have three fields I need to update in the store.
}))
}
}
export default connect(null, mapDispatchToProps)(Add);
And this for my actions file:
import { ADD_ITEM } from '../constants/items';
export const addItem = (val) => {
return {
type: ADD_ITEM,
val
}
}
But if I run this and set a breakpoint inside the action creator the val value is undefined. For some reason Redux isn't letting me feed dynamic data to the action creator and I don't understand why.
Obviously I can't just pull the information with querySelector because the form doesn't exist when the callback is loaded. If I fill the object passed to the action with hard-coded dummy values it works, but I'm not able to pull in data from the form fields. I'm not even clear as to where to start with this. Any direction is greatly appreciated.
You can't access any data from Redux state, or from inside the component, in mapDispatch, because it is used as part of a wrapper component that goes around your actual component (and thus doesn't have access to anything in your component's state).
Your main options are:
Pass any necessary values as arguments into the function, like props.action(a, b, c)
Switch to using the React-Redux hooks API (useSelector and useDispatch), which lets you access data from the Redux store inside of your function component. You can then capture these values while defining a click handler.
Also, as a side note: if you are going to use connect, you should use the "object shorthand" form of mapDispatch rather than defining it as a function.
You just need to add onChange event handler to your three fields and store data of each input into your component state.
Then on button click dispatch action using this.props.action with data in your state.
In this way you can get all of your data into redux.
render() {
return <button onClick={() =>this.props.toggleTodo(this.props.todoId)} />
}
const mapDispatchToProps = dispatch => {
return { toggleTodo: todoId =>dispatch(toggleTodo(todoId)) }
}
For reference -Connect: Dispatching Actions with mapDispatchToProps · React Redux
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
I'm trying to query from database when user select a machine from a select input field (which data is also coming from database). I'm using redux-form. My code is like below:
<div className='universal_form-input'>
<label>Mechine Name</label>
<Field
type='number'
name='machine'
component={this.renderSelect}
/>
</div>
And the select input filled with Machine name and value is the id of the corresponding Machine. Now when user will select a machine it state will be updated with the id value of that machine.
On handleChange I'm trying to invoke a method named queryMachineById which should fetch data by specific machine id. And the query should return noOfDispenser of that machine from database. (BTW this query is working on GraphQL playground)
handleChange = (event, input) => {
event.preventDefault();
const { name, value } = event.target;
input.onChange(value);
this.setState({ [name]: value });
setTimeout(() => {
this.queryMachineById();
}, 1000);
};
queryMachineById method is written like below:
queryMachineById = () => {
const machineId = parseInt(this.state.machine);
console.log(typeof machineId, machineId);
return (
<Query query={MACHINE_BY_ID_QUERY} variables={{ machineId }}>
{({ data, loading, error }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
console.log(data);
return data.machineById.map(
machine => (initialValues.dispenserNo = machine.noOfDispensers)
);
}}
</Query>
);
};
machineId is also updating with valid id from state. But the console.log(data) inside the queryMachineById is not showing anything, not even empty {}
My GraphQL query looks like this:
export const MACHINE_BY_ID_QUERY = gql`
query MACHINE_BY_ID_QUERY($machineId: Int!) {
machineById(machineId: $machineId) {
id
noOfDispensers
}
}
`;
onClick is also not working.
Outdated / deprecated
using <Query /> and <Mutation/> components is a sign of using outdated examples/tutorials/knowledge.
These days you should use useLazyQuery hook.
Of course you can still use <Query/> component or even older methods (compose [multiple] graphql HOCs) as long they are supported.
Use old docs
or you can use skip property to manually firing it ...
or even [simpler] render <Query/> component conditionally.
Newer methods are described in docs.
Bads
In React, you can't render components from event handlers. You can use methods [passed as props] derived from components (f.e. from mutation) but never rendered from handler! - like you can't return anything visible (to be rendered) from event handler (update state instead, render something conditionally) ... it's all data driven, update data > render will update a view.
Update
Component works only in render context/flow - event handler flow is separate, you can't use (as not rendering it) or return component to be rendered (rendering was finished earlier) - this way you can't use it's functionality - querying in this case.
[One of options] You have to render <Query /> component in render flow and use skip property to block it's automatic firing at start. Handler can change blocking (skip) condition (use setState) and it will be fired and rerendered with data results. This way you have querying event driven.