I've have an component "A" with a button. When the user press the button I'm showing a modal(react-responsive-modal) with bunch of filed and an update button. When the user presses the update button on the modal I want to reload the component "A" with the updated data.
I tried redirecting using this.props.history.push('dashboard/componentA') but it didn't work. Then I tried redirecting to the dashboard and again redirecting to the component like this
this.props.history.push('/dashboard');
this.props.history.push('/dashboard/componentA');
It worked but I'm not seeing any loader that I've used on 'componentWillMount' and the component just freezes up. I couldn't scroll up or down.
Try not to use the browser history as a way to update react (as much as you can). React is designed to re-render components when the props or state for that component change. As an example, this should trigger an update in ComponentA without needing to update the browser's history:
class ComponentA extends Component {
handleModalClick = (event) => {
this.setState({
componentData: event.data,
});
}
render() {
return (
<ReactModal onClick={this.handleClick} />
)
}
}
EDIT: Updated to show a data fetching parent component:
class DataFetcher extends Component {
saveAndFetchData = (saveData) => {
FetchDataPromise(saveData).then((updatedData) => {
this.setState({ data: updatedData });
}
}
render() {
const { data } = this.state;
return (
<div>
<ComponentA data={data} />
<ReactModalComponent handleClick={saveAndFetchData} />
</div>
);
}
}
class ComponentA extends Component {
render() {
const { data } = this.props;
return (
<div>
...render data...
</div>
)
}
}
Related
I have an app with redux and router where on the first load, all users are loaded. To this end, I've implemented a main component that loads the user when the component is mounted:
class Content extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.load();
}
render() {
return this.props.children;
}
}
The afterwards, if the user chooses to load the details of one user, the details are also obtained through the same lifehook:
class Details extends Component {
componentDidMount() {
this.props.getByUrl(this.props.match.params.url);
}
render() {
const { user: userObject } = this.props;
const { user } = userObject;
if (user) {
return (
<>
<Link to="/">Go back</Link>
<h1>{user.name}</h1>
</>
);
}
return (
<>
<Link to="/">Go back</Link>
<div>Fetching...</div>
</>
);
}
Now this works well if the user lands on the main page. However, if you get directly to the link (i.e. https://43r1592l0.codesandbox.io/gal-gadot) it doesn't because the users aren't loaded yet.
I made a simple example to demonstrate my issues. https://codesandbox.io/s/43r1592l0 if you click a link, it works. If you get directly to the link (https://43r1592l0.codesandbox.io/gal-gadot) it doesn't.
How would I solve this issue?
Summary of our chat on reactiflux:
To answer your question: how would you solve this? -> High Order Components
your question comes down to "re-using the fetching all users before loading a component" part.
Let's say you want to show a Component after your users are loaded, otherwise you show the loading div: (Simple version)
import {connect} from 'react-redux'
const withUser = connect(
state => ({
users: state.users // <-- change this to get the users from the state
}),
dispatch => ({
loadUsers: () => dispatch({type: 'LOAD_USERS'}) // <-- change this to the proper dispatch
})
)
now you can re-use withUsers for both your components, which will look like:
class Content extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
// ... etc
}
const ContentWithUsers = withUsers(Content) // <-- you will use that class
class Details extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
}
const DetailsWithUsers = withUsers(Details) // <-- same thing applies
we now created a re-usable HOC from connect. you can wrap your components with withUsers and you can then re-use it but as you can see, you are also re-writing the componentDidMount() part twice
let's take the actual load if we haven't loaded it part out of your Component and put it in a wrapper
const withUsers = WrappedComponent => { // notice the WrappedComponent
class WithUsersHOC extends Component {
componentDidMount () {
if (!this.props.users || !this.props.users.length) {
this.props.loadUsers()
}
}
render () {
if (! this.props.users) { // let's show a simple loading div while we haven't loaded yet
return (<div>Loading...</div>)
}
return (<WrappedComponent {...this.props} />) // We render the actual component here
}
}
// the connect from the "simple version" re-used
return connect(
state => ({
users: state.users
}),
dispatch => ({
loadUsers: () => dispatch({ type: 'LOAD_USERS' })
})
)(WithUsersHOC)
}
Now you can just do:
class Content extends Component {
render() {
// ......
}
}
const ContentWithUsers = withUsers(Content)
No need to implement loading the users anymore, since WithUsersHOC takes care of that
You can now wrap both Content and Details with the same HOC (High Order Component)
Until the Users are loaded, it won't show the actual component yet.
Once the users are loaded, your components render correctly.
Need another page where you need to load the users before displaying? Wrap it in your HOC as well
now, one more thing to inspire a bit more re-usability
What if you don't want your withLoading component to just be able to handle the users?
const withLoading = compareFunction = Component =>
class extends React.Component {
render() {
if (! compareFunction(this.props)) {
return <Component {...this.props} />;
}
else return <div>Loading...</div>;
}
};
now you can re-use it:
const withUsersLoading = withLoading(props => !props.users || ! props.users.length)
const ContentWithUsersAndLoading = withUsers(withUsersLoading(Content)) // sorry for the long name
or, written as a bit more clean compose:
export default compose(
withUsers,
withLoading(props => !props.users || !props.users.length)
)(Content)
now you have both withUsers and withLoading reusable throughout your app
I am building a component that will be used for step-through processes such as :
This Workflow component takes an array of 'steps' as a prop and then it does the rest. Here is how it is being called in the image above :
let steps = [
{
display: "Sign Up Form",
component: SignupForm
},
{
display: "Verify Phone",
component: VerifyPhone
},
{
display: "Use Case Survey",
component: UseCase
},
{
display: "User Profile",
component: UserProfile
},
];
return (
<Workflow
steps={steps}
/>
);
The component field points to the component to be rendered in that step. For example the SignupForm component looks like this :
export default class SignupForm extends React.Component {
...
render() {
return (
<div>
<div className="page-header">
<h1>New User Sign Up Form</h1>
<p>Something here...</p>
</div>
<div className="form-group">
<input type="email" className="form-control" placeholder="Email address..." />
<small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
</div>
);
}
}
The issue I'm facing is that in each step there needs to be a Next button to validate the information in that step and move to the next. I was going to just put that button inside the component of each step, but that makes it less user-friendly. When a user clicks 'Next', and everything is valid, that step should be collapsed and the next step should open up. However this means that my Workflow component needs to render this button.
So, I need my Workflow component to call the method of each step component to validate the information in the step and return a promise letting it know if it passed or failed (with any error message). How do I need to call this method? Here is where the Workflow component renders all the steps
as <step.component {...this.props} />:
{
this.state.steps.map((step, key) => {
return (
...
<Collapse isOpen={!step.collapsed}>
<step.component {...this.props} />
<Button color="primary"
onClick={() => this.validate(key)}>Next</Button>
<div className="invalid-value">
{step.error}
</div>
</Collapse>
...
);
})
}
That renders the next button, as well as the onClick handler validate():
validate(i) {
let steps = _.cloneDeep(this.state.steps);
let step = steps[i];
step.component.handleNext().then(function () {
...
}).catch((err) => {
...
});
}
Ideally, step.component.validate() would call the validate method inside that component that has already been rendered:
export default class SignupForm extends React.Component {
....
validate() {
return new Promise((resolve, reject) => {
resolve();
})
}
render() {
...
}
}
.. which would have access to the state of that component. But, thats not how it works. How can I get this to work? I read a little about forwarding refs, but not exactly sure how that works. Any help is greatly appreciated!
One approach is to apply the Observer pattern by making your form a Context Provider and making it provide a "register" function for registering Consumers. Your consumers would be each of the XXXForm components. They would all implement the same validate API, so the wrapping form could assume it could call validate on any of its registered components.
It could look something like the following:
const WorkflowContext = React.createContext({
deregisterForm() {},
registerForm() {},
});
export default class Workflow extends React.Component {
constructor(props) {
super(props);
this.state = {
forms: [],
};
}
deregisterForm = (form) => {
this.setState({
forms: this.state.forms.slice().splice(
this.state.forms.indexOf(forms), 1)
});
};
registerForm = (form) => {
this.setState({ forms: [ ...this.state.forms, form ] })
};
validate = () => {
const validationPromises = this.state.forms.reduce(
(promises, formComponent) => [...promises, formComponent.validate()]);
Promise.all(validationPromises)
.then(() => {
// All validation Promises resolved, now do some work.
})
.catch(() => {
// Some validation Promises rejected. Handle error.
});
};
render() {
return (
<WorkflowContext.Provider
value={{
deregisterForm: this.deregisterForm,
registerForm: this.registerForm,
}}>
{/* Render all of the steps like in your pasted code */}
<button onClick={this.validate}>Next</button
</WorkflowContext.Provider>
);
}
}
// Higher-order component for giving access to the Workflow's context
export function withWorkflow(Component) {
return function ManagedForm(props) {
return (
<WorkflowContext.Consumer>
{options =>
<Component
{...props}
deregisterForm={options.deregisterForm}
registerForm={options.registerForm}
/>
}
</WorkflowContext.Consumer>
);
}
}
SignupForm and any other form that needs to implement validation:
import { withWorkflow } from './Workflow';
class SignupForm extends React.Component {
componentDidMount() {
this.props.registerForm(this);
}
componentWillUnmount() {
this.props.deregisterForm(this);
}
validate() {
return new Promise((resolve, reject) => {
resolve();
})
}
render() {
...
}
}
// Register each of your forms with the Workflow by using the
// higher-order component created above.
export default withWorkflow(SignupForm);
This pattern I originally found applied to React when reading react-form's source, and it works nicely.
I stuck in bit weird situation, I am using ReactJS. I have header container, title bar, title container. Header container has navigation bar. On click of that it effects title bar. I am using react router for that navigation. I am using componentDidMount lifecycle method for that.
Problem with that it triggers only once when title container loads. So I used componentDidUpdate. But in that problem occured when I added title bar component to title container. So now my componentDidUpdate runing in infinite loop. I tried to use shouldComponentUpdate(nextProps, nextState) but I don't know what condition put to return it false.
export class TitleContainer extends React.Component {
componentDidMount() {
this.props.dispatch(fetchDetail(this.props.match.params.program_id))
}
componentDidUpdate(prevProps, prevState) {
this.props.dispatch(fetchDetail(this.props.match.params.id))
}
shouldComponentUpdate(nextProps, nextState){
console.log("current props",this.props)
console.log("next props",nextProps)
// if(this.props.name == nextProps.name)
// return false;
return true;
}
render() {
console.log("data in contaner", this.props)
return (
<div>
<Title name = { this.props.name }
/>
</div>
)
}
}
const mapStateToProps = (state) => {
console.log("update state", state)
return {
programProfileData: state.DetailReducer.Details,
name: state.DetailReducer.name
}
}
export default connect(mapStateToProps)(TitleContainer)
If I understand your problem you would like to fetch other data if you change the params?
If so I would just remount the whole component.
class TitleContainer extends React.Component {
componentDidMount() {
this.props.dispatch(fetchDetail(this.props.match.params.program_id))
}
render() {
console.log("data in contaner", this.props)
return (
<div>
<Title name = { this.props.name }
/>
</div>
)
}
}
const mapStateToProps = (state) => {
console.log("update state", state)
return {
programProfileData: state.DetailReducer.Details,
name: state.DetailReducer.name
}
}
export default connect(mapStateToProps)(TitleContainer)
I think you don't need to use componentUpdate. You navigate to your that address, react router will creates that component, and you can extract the match props.
In your header you can have other Links from the react router dom lib which will replace your existing component. If you click on a link, react router pushes that to the browser history and creates a new component and therefore the params are updated.
Having a weird problem with React props in Firefox. Using Redux and Babel as well.
I'm trying to hide a form, once it has submitted. This works fine on Chrome, but for some reason doesn't work on FF and IE.
So here I have a simple component, a div which houses a form. display class comes from parent component:
class MyForm extends Component {
handleFormSubmit(e) {
// fires an action that sets submitInfo to true
}
render() {
const { display } = this.props;
return (
<div className={display}>
<form onSubmit={ (e) => this.handleFormSubmit(e) }>
// some inputs here
</form>
</div>
);
}
}
When the form submits, an action is fired that sets submitInfo (Redux state) is set to true.
The parent component looks like this:
import { submitInfo, hideForm } from '../actions/main.js'
class App extends Component {
render() {
const {submitInfo, hideForm} = this.props;
var showForm;
if ((submitInfo == true) || (hideForm == true)) {
console.log('evaluating showForm');
showForm = 'hide';
} else {
console.log('evaluating showForm');
showForm = '';
}
return (
<div>
<MyForm display={ 'main-form' + ' ' + showForm } />
</div>
);
}
}
function mapStateToProps(state) {
const { submitInfo, hideForm } = state;
return { submitInfo, hideForm }
}
The parent component checks Redux state for submitInfo = true or hideForm = true. If true then pass value of 'hide' to child component.
Can seem to figure out what is wrong. In Chrome, my console.logs within the parent component seem to be firing every time the state object is re-rendered (ie. whenever an action is fired), but this doesn't happen in Firefox.
State object is being updated correctly, so I can see submitInfo: true and hideForm: true when they're supposed appropriate.
You should use a conditional instead of a class to determine whether to show a component.
The parent component's render method would look something like this:
class App extends Component {
render() {
return (
<div>
{!(this.props.submitInfo && this.props.hideForm) ? <MyForm /> : null}
</div>
);
}
}
Now we can also clean up the child component:
class MyForm extends Component {
handleFormSubmit(e) {
// fires an action that sets submitInfo to true
}
render() {
return (
<div className="main-form">
<form onSubmit={(e) => this.handleFormSubmit(e)}>
...
</form>
</div>
);
}
}
I've just started using flummox and I'm a little bit flummoxed :)
Here is my use case.
App Layout
<section id="layout">
<Header {...this.props} />
<RouteHandler {...this.props} />
<Footer />
<Alert {...this.props} />
</section>
In my App I have Alert Component. When something happens I trigger an AlertAction from some component, it dispatches alert payload to AlertStore, which gets updated, and AlertComponent shows alert ( + hides it after some time).
For example I have a PostEdit Component. After form submit, I send request to API from PostActions and receive response, which is dispatched to PostStore. Store gets updated and PostEdit Component gets notified. In PostEdit's componentWillReceiveProps I check props, received from the store, and trigger AlertAction to show the alert.
2 problems:
I have to use setTimeout to trigger AlertAction from the Post Component to make alert things happen (code below).
And the main problem is that Alert Component stops listening AlertStore after the first react-router transition.
Here is console.log, illustrating the problem:
One more strange thing is that changed-store-notification in console.log printed before dispatch-payload-from-action-notification (which causes this store change).
Here are code snippets:
AlertHandler.jsx
export default class AlertHandler extends React.Component {
// constructor()
render() {
return (
<FluxComponent connectToStores={'alerts'}>
<Alert {...this.props} />
</FluxComponent>
);
}
}
Alert.jsx
export default class Alert extends React.Component {
// constructor()
// _handleShow()
// _handleHide()
componentDidMount() {
this.props.flux.getStore('alerts').addListener('change', function() {
console.log('Changed!', this.state);
});
}
componentWillUnmount() {
console.log('Not gonna happen');
}
// render()
}
PostEdit.jsx
export default class PostEdit extends React.Component {
// constructor()
componentWillReceiveProps(newProps) {
this.setState({
isLoading: false
}, () => {
if (newProps.errors) {
// without `setTimeout` nothing happens
setTimeout(() => {
newProps.flux
.getActions('alerts')
.showErrorAlert(newProps.errors);
}, 1);
} else {
setTimeout(() => {
newProps.flux
.getActions('alerts')
.showSuccessAlert();
}, 1);
}
});
}
_submitPost(e) {
// doing stuff...
// triggering updatePost action
this.props.flux
.getActions('posts')
.updatePost(post);
}
// render()
}
Not sure are these bugs or I missed smth in flux/flummox patterns and do things wrong. Thanks for feedback!