How does react component pass value to the previous page component - javascript

I have two component, FormComponent and OrderListComponent. There is one field on the FormComponent to ask users to select a value from a list which is from OrderListComponent. So I put a button on the field in FormComponent, when users click on that button, I use router.push('XXXX') to navigate the page to OrderListComponent to show all the options for user. When users click one of the options on OrderListComponent, I use router.goBack() to go back to FormComponent. Here is the problem, how does FormComponent get the selected value from OrderListComponent? I know that I can save the selection as a state in redux store, but I wander whether there is a more direct way to solve this issue.

The best way is to have state manager. Redux state will solve it elegantly. Though as Random User mentioned in his answer; localStorage/sessionStorage can be used to do this.
But passing the values by URL will be tricky when there are too many items selected for Order.
The state can be changed so that having anything in state will give consistent output at OrderListCompoennt.
You can have order state and push the items to that state. And then redirect the user to OrderListComponent rather than doing router.push('XXXX').
OrderListComponent will watch the state and show the items accordingly.
The FormComponent will also watch the order state for marking the currently added items. With this way, there will not be any router.goBack(); just the redirects by router.push
And when your app will be at FormComponent, order state will automatically be responsible to mark the items which are currently in order.

Storing the selected value in redux store or component state itself would be better choice for this, but you can also
store it in localStorage
store it in sessionStorage
when redirecting the user back, pass it as a url parameter
If you're using any one of these methods, In FormComponent you can check if the value exists and perform operations accordingly.
Example code
OrderListComponent
let handleChange = (value) => {
localStorage.orderListValue = value
}
This will replace any existing value as well.., Use your brain here.
FormComponent
if ( localStorage.orderListValue ) {
console.log('Yay.. we have data');
} else {
// ask the user to select the List first.
}

Related

Resetting redux state without breaking react components

Been working with redux in a complex react application.
I put some data in redux state and use this data to render a component.
Then, you want to change the page and I do need to reset some fields in the redux state. Those fields were used to render the previous component.
So, if I reset the state before going to the next page, the previous page rerenders and throws errors because data is missing (because of the reset). But I can't reset in the next page, because that page is reached in many flows so it can be difficult to manage when to reset and when not.
How problems like this are managed in react applications?
All the example are simplified to show the problem. in the actual application, there are many fields to reset.
so, page 1
redux state
{field: someValue}
component uses the field value
function myComponent(props) => {
return (props.field.someMappingOperation());
}
Now, when going to page 2, field should be null, so we reset it
redux state
{field: null}
the component above rerenders, throwing an error because of
props.field.someMappingOperation()
and field is null.
What can be done is to load the next page, so this component is not in the page and then reset the state. yet, this becomes very hard to manage, because you the are in page B (suppose clients list with the list saved in redux) and you want to see the details of a client you go to page C, when you press back you don't want to reset again the state. So you add conditions on the reset. But because there are many flows in the application, that page can be reached in many ways. Have conditions for each way is not the best solution I suppose.
Edit:
I would like to add that state reset was not required initially and components aren't designed for that. As the application grew and became enough complex it became necessary. I'm looking for a solution that does not require me to add props value checking in every and each component, like
{this.props.field && <ComponentThatUsesField />}
This is really a lot of work and it's bug-prone as I may miss some fields.
As the application has a lot of pages and manages a lot of different data, the store results to be big enough to not want to have clones of it.
You have to design your components in a more resilient way so they don't crash if their inputs are empty.
For example:
render() {
return (
{ this.props.field && <ComponentUsingField field={this.props.field} />}
)
}
That's a dummy example, but you get the point.
you can change your code like below:
function myComponent(props) => {
const {fields} = props;
return (fields && fields.someMappingOperation());
}
This will not throw any error and is a safe check
You don't need to use conditions or reset your state, your reducer can update it and serve it to your component. You can call an action in componentDidMount() and override the previous values so they will be available in your component. I assume this should be implied for each of your component since you are using a different field values for each.

ReactJS - ReactiveSearch - Custom event on suggestion selection

I am building an autocomplete component to search current users and add them to a team. The search works fine except I need to customize a couple things that I can't figure out how to do.
First, this component is modeled after GitHub's invite user modal. You can start typing to search for a current user and it populates any results it finds. However, if it doesn't find any results it only shows one item to invite that user via email. How can I edit the full state of the result list for a DataSearch component? All I can see is to edit the content of each suggestion via the onSuggestion prop. I need to be able to say, "If there are zero results, display this."
Second, when a suggestion from the autocomplete result list is selected, I need to be able to reset the search box because I am populating a list that the user can see. Right now, the search box is populated with the value of the suggestion. So, I am populating the list below of selected results just fine; but, I still need to be able to reset the search box when that result is selected.
Help????
CodeSandbox link
For the first part of the problem, you can use the prop onNoResults on any of the results components to render custom JSX when no results are found. From the docs:
onNoResults String or JSX [optional]
show custom message or component when no results founds.
<ResultList
...
// custom JSX when no results are found
onNoResults={
<section>
<input />
<button>Add</button>
</section>
}
/>
For the second part of the problem, there are two ways IMO you may approach this.
Using a ReactiveComponent which lets you create a custom ReactiveSearch component.
Using customQuery
I'll explain how to approach this using customQuery but it might be better to create a custom component depending on which approach suits your needs best.
In the example I've shared my DataSearch looks like this:
<DataSearch
title="DataSearch"
dataField={["original_title", "original_title.search"]}
categoryField="authors.raw"
componentId="BookSensor"
customQuery={this.customQuery}
defaultSelected={this.state.value}
onValueSelected={this.updateState}
/>
The reason for using a customQuery is to get full control of which query gets applied to retrieve the results. ReactiveSearch is designed to work reactively. When a value is set into the DataSearch, the ResultList would react to this change. Having a customQuery lets us control which query is fired for this change. Also I'm keeping the value of DataSearch in the state so I can clear it up when the query gets fired. Here's what I'm using in the example:
customQuery = (value, props) => {
// find the current query using defaultQuery from DataSearch
const currentQuery = DataSearch.defaultQuery(value, props);
// clear the value in component's state
this.setState({
value: ""
});
if (value.length) {
// store this query
this.prevQuery = currentQuery;
return currentQuery;
}
// if the value is empty, that is it was cleared, return the previous query instead
return this.prevQuery;
};
So whenever the value is cleared, I just return the previous query so the result list shows the correct results for the previous query (before the value was cleared).
Also in order to control the value of DataSearch from my component I'm using the defaultSelected and onValueSelected props. They work quite similar to how you would create a controlled component in react. Docs
Again, it might be better to create a custom component using ReactiveComponent if this approach sounds complicated to customize this flow.
Demo for this answer using customQuery

Why is ugly hack required for react-redux to re-render redux-form?

I have a react app that is using a redux store and this component also uses redux-form. I am seeing a strange behavior on one particular type of update where I want to trigger a follow-on update.
Basically a user is updating permissions for something on the form, then saves. The save submits the form and in the processing of the action, it triggers another action (getting a list of all the somethings). When the list is returned from the server, this dispatches an action to update the list UI. At this point I am updating a property in the redux store for the form to say trigger an update of the permissions for the currently editing thing. This is the only point where I can do this because we now have the information to trigger this request.
The problem is that a boolean flag is being set as the trigger, and the mapStateToProps is getting called with the updated flag, but the component is not subsequently getting called (shouldComponentUpdate and componentWillUpdate never get called). It is like it ignores the state of the boolean variable in the props.
I found that if, in mapStateToProps I set another property based on the
boolean trigger, then the component is updated (re-rendered).
function mapStateToProps(store, ownProps) {
...
let triggerFetchSomethingOwnershipHash = 0;
if (store.triggerFetchSomethingOwnership) {
triggerFetchSomethingOwnershipHash = 100000 * Math.random();
}
return {
...
triggerFetchSomethingOwnership,
triggerFetchSomethingOwnershipHash
}
}
...
const ConnectedSomethingDetail = connect(mapStateToProps)(SomethingForm);
export default ConnectedSomethingDetail;
Why do I need to do this and how can I avoid it?
Yes I have checked a few relevant SO questions like react-redux-component-does-not-rerender-on-store-state-change and react-redux-update-item-in-array-doesnt-re-render

React: update a field value based on another field in dynamically generated form

I'm trying to find out the "proper" way to do the following in React:
I have a form with two fields, url and title.
Whenever url's value changes, I make an API call to Embedly to retrieve metadata about the link.
Once the metadata has been retrieved, I want to update the title field with the result.
The difficulty here is that the url and title fields are not in the same component. Here's the basic structure of the form (I'm using Formsy):
<Formsy.Form onSubmit={this.submitForm} onChange={this.updateState} ref="form">
{fields.map(field => <FormComponent
name={field.name}
type={field.type}
value={field.value}
/>)}
<Button type="submit">Submit</Button>
</Formsy.Form>
As you can see, the form loops over an array of fields and calls a generic <FormComponent/> component for each of them, which is basically a big switch that then calls the appropriate Formsy Component based on the field's type.
The logic for querying Embedly is already working inside the component for the url field, but I'm not sure if there's a way to accomplish what I want while still using the default Input component for title?
[Edit: thanks to Dan I was able to come up with a better solution for step 4 and after]
So here's the solution I came up with. I have no clue if it's the best pattern or not, but at least so far it seems to work.
Step 1: add a addToPrefilledValues method to the <Form/> component that takes a property and adds it to the prefilledValues object on that component's state.
Step 2: add addToPrefilledValues to the form component's context so that the method is passed on to all child components (note: I could also pass it as a prop but context seems easier to pass it on to grandchild components).
Step 3: make my <URLField/> component call addToPrefilledValues whenever it receives new metadata from Embedly:
this.context.addToPrefilledValues({title: result.title, body: result.description});
Step 4 [Wrong, see below]: in the <TitleField/> component's shouldComponentUpdate method, watch for changes to the context and update the field's value (if it's empty):
shouldComponentUpdate(nextProps, nextState, nextContext) {
const nextTitle = nextContext.prefilledValues && nextContext.prefilledValues.title;
const currentTitle = this.context.prefilledValues && this.context.prefilledValues.title;
if (!!nextTitle && nextTitle != currentTitle && !this.input.getValue()) {
this.input.setValue(nextTitle);
}
return true;
}
Step 4 (better): whenever the form changes, store all of the form's values in a currentValue object on the form's state.
Step 5: Look at:
the original value of the field (i.e. in the database), as passed through this.props).
the current value being typed in the field, as found in this.state.currentValue.
the prefilled value generated by the Embedly API call, as found in this.state.prefilledValue.
Figure out which one is correct (i.e. if the user hasn't entered anything in the field prefill it, if not don't) and pass that on to the form field child component.
This achieves the desired result:
<URLField/> and <TitleField/> don't have to know about or communicate with each other.
<Form/> doesn't have to know about <URLField/> or <TitleField/> either.
As #ffxsam suggested though, I should probably just use Redux Form…

Flux store dependency with async actions

I'm having problems understanding what is the best way to do this using the Flux pattern. Say for example that I have a userStore and I listen to it. Once it changed, I need get the user.name and access colors[user.name] - but the colors object comes from another colorsStore store I have. Here's the essence of it:
var self = {};
userStore.addListener(function(user) {
// dependency with the colors store
var color = self.colors[user.name]
})
colorsStore.addListener(function(colors) {
self.colors = colors;
})
actions.getUser() // modifies userStore
actions.getColors() // modifies colorsStore
The problem is that the two actions are async (they get the data from an AJAX call for instance). With this in mind, the userStore might change before the self.colors variable is populated from the other store.
How is this handled using the Flux pattern? Does the Dispatcher help with this somewhat? Sorry but I'm new to the Flux pattern. Intuitively I would simply call the async actions in the appropriate order such as:
actions.getColors() // need to populate self.colors before running getUser()
.then(actions.getUser())
But was wondering if there was a more Flux-way of doing this.
Your setup is fine from flux perspective.
Your component needs to be able to handle different possible (stores) states generated by your actions, which could possibly include:
user store has old/no data, colors store already has newest data
user store has newest user data, colors store still has old data
If you want any of these states to be visible to the user in some way (eg show loading indicator, show old color/ default color while waiting for newest color), then the react-flux way is to deal with these states inside your component.
If you do not want to show anything about these states to user, you have two options:
inside your component, fire the actions.getUser() from inside the colorStore listener function (quick and dirty solution)
change the setup to prevent the unwanted store state to trigger component update
For the second solution, you could typically do:
have you component fire both actions
both listeners trigger the same function getStateFromStores()
this function fetches state from both stores, and only does component update (setState()) if user and colors match
That way, your async calls can come back in any order.
If I understand your problem correctly, you can use waitFor for this case. Also there's the discussion about "waitFor vs combining stores into one", so a combined store can solve your problem as well.

Categories

Resources