Dispatching actions from presenational components with regards to Container Component pattern? - reactjs

I have just started to explore the Container Component pattern. What I don't quite grasp yet is the concept of presentational component only being concerned with the visuals.
Does that mean that these component can't dispatch action that would change the redux state?
e.g
<MainContainer>
<ListComponent />
<GraphComponent />
</MainContainer>
Say <GraphComponent> shows graphs based on a list in redux state. The <ListComponent> then modify this list in the redux state with buttons. Is this okay in the Container Component pattern?

I think that you're not supposed to dispatch actions in Components. In Container-Component pattern, you're supposed to pass a callback function from the container (MainContainer in your case) as props to ListComponent, that fires when the button is clicked and dispatch action (in container) as result.

Presentation (Dumb) components are like 1st grade students, they have their unique appearance but their behavior and the content they put out is decided or taught by their parents.
Example: <Button />
export const Button = props => {
<button type="button" onClick={props.onClick} />{props.text}</button>
}
Unique appearance: it's a button
Behavior: onClick
Content: text
Both onClick and text are provided by parent.
When they grow to 5th or 7th grade, they may start to have their own state and decide few of the things on their own.
Example: <Input />
class Input extends Component {
constructor(props) {
super(props);
this.state = {
value: ''
}
}
handleChange = (e) => {
this.setState({value: e.target.value});
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
onFocus={this.props.onFocus}
/>
);
}
}
Unique appearance: it's an input
State: decides it own value.
Behavior: onFocus
onFocus provided by parent.
And when they become adults they may start to behave on their own and may not
need their parents guidance. They start to talk to the outer world (redux store)
on their own (now they are new Container components).
Example
const mapStateToProps = (state, [ownProps]) => {
...
}
const mapDispatchToProps = (state, [ownProps]) => {
...
}
const MyComponent = (props) => {
return (
<ChildComponent {...props} />
);
}
connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Decides its own behavior and content, may or may not need parent (read ownProps)
Presentational components may or may not need any behavior of their own,
they rely on their parents for that. But to talk (dispatch) to the outer
world(store) they need to be big enough (Container components).
So the main thing to remember here is start small and decide as your component
grows whether it needs to be a presentational or container component.

Related

Retrieving latest state from functional child component using callbacks

Coming from Vue and diving into React I seem to struggle with the concept of the hooks / component lifecycle and data flow. Where in Vue I could solve my issues using v-model in React I struggle to do so. Basically:
What I intend to do is : have a parent component which is a form. This component will have several child components where each are fragments of the form data. Each child component manages its own state. The parent component has a submit button that should be able to retrieve the values from all child components.
In a nutshell, my approach is: have a functional component to manage part of said form using state hooks. This "form fragment"-component can broadcast a change event broadcastChange() containing the updated values of its inputs. In the parent I have a submit button which invokes this broadcastChange() event from the child using a ref.
The problem I am running into is that I am always getting the default values of the child state. In the below example, I'd always be getting foo for property inputValue. If I were to add a submit button inside the child component to invoke broadcastChange() directly, I do get the latest values.
What am I overlooking here ? Also, if this is not the React way to manage this two-way communication, I'd gladly hear about alternatives.
Parent code:
function App() {
const getChildChangesFn = useRef( null );
const submitForm = e => {
e.nativeEvent.preventDefault();
getChildChangesFn.current(); // request changes from child component
};
const handleChange = data => {
console.log( data ); // will always list { inputValue: "foo" }
};
return (
<form>
<child getChangeFn={ getChildChangesFn } onChange={ handleChange } />
<button type="submit" onClick={ () => submitForm() }>Save</button>
</form>
);
}
Child code:
export default function Child( props ) {
const [ inputValue, setInputValue ] = useState( "foo" );
useEffect(() => {
// invoke inner broadcastChange function when getChangeFn is triggered by parent
props.getChangeFn.current = broadcastChange;
}, []);
const broadcastChange = () => {
props.onChange({ inputValue });
};
render (
<fieldset>
<input
type="text"
value={ inputValue }
onChange={ e => setInputValue( e.target.value ) }
/>
</fieldset>
);
}
You need to leave Vue behind and make your thinking more React-y. Rather than trying to manage your state changes imperitavely by 'broadcasting' them up, you need to 'lift your state' (as they say) in to your parent and pass it down to your children along with change handlers.
A simple example:
export default function App() {
const [childState, setChildState] = useState(false);
const onChildClick = () => setChildState((s) => !s);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ChildComponent childState={childState} onClickHandler={onChildClick} />
</div>
);
}
const ChildComponent = ({ childState, onClickHandler }) => {
return (
<button onClick={onClickHandler}>
State is {childState ? "true" : "false"}
</button>
);
};
Sandbox here
You have to uplift the state to parent component, or use a state manager like redux. In your case you are already passing down an onChange function, which should do what you need. But on parent component you need to manage the state, and store changes in the state, and pass it down to components as props.
An alternative to redux is mobx, which is a reactive library, in your case, sounds like you are familiar with reactive components, might fit you better. Other alternatives are using the Context that comes with react, and react-query is also a solid alternative, as it also handles async api calls.

Is it possible to display props on a parent component?

Is there a way to DISPLAY props on parent component in React?
Is this similar to passing props from child component to parent component?
React and many other modern frameworks (like Vue) have what they call a top-down data flow:
This is commonly called a “top-down” or “unidirectional” data flow.
Any state is always owned by some specific component, and any data or
UI derived from that state can only affect components “below” them in
the tree.
If you imagine a component tree as a waterfall of props, each
component’s state is like an additional water source that joins it at
an arbitrary point but also flows down.
What you can do instead is supply the child component with an event listener and update the parent component's state accordingly.
const ParentComponent = () => {
const [parentState, setParentState] = useState(null)
return (
<ChildComponent
onTextChange={(newState) => setParentState(newState)}
value={parentState}
/>
)
}
const ChildComponent = ({ onTextChange, value }) => {
return <input type="text" value={value} onChange={onTextChange} />
}

React design pattern dilemma - steppers in numeric form components

So we all know uncontrolled components are usually a bad thing, which is why we usually want to manage the state of an input (or group of inputs) at a higher-level component, usually some kind of container. For example, a <Form /> component manages state and passes down state as values to its <Input /> components. It also passes down functions such as handleChange() that allow the Input to update the state.
But while implementing my own <NumericInput /> component, it got me thinking that fundamentally this component is not self-reliant. It's reusable but requires a lot of repetition (opposite of DRY mentality) because everywhere in my app that I want to use this component, I have to implement these state values, a handleChange function, and in the case of my <NumericInput />, two additional functions to control the stepper arrows.
If I (or someone who took over my code) wanted to use this <NumericInput />, but they forget to run to a different container component and copy the stepUp() and stepDown() functions to pass down as props, then there will just be two non-functional arrows. I understand that this model allows our components to be flexible, but they also seem to be more error-prone and dependent on other components elsewhere. Again, it's also repetitive. Am I thinking about this incorrectly, or is there a better way of managing this?
I recognize this is more of a theory/design question, but I'm including my code below for reference:
NumericInput:
const NumericInput = ({label, stepUp, stepDown, ...props}) => (
<>
{label && <Label>{label}</Label>}
<InputContainer>
<Input type={props.type || "number"} {...props} />
<StepUp onClick={stepUp}>
//icon will go here
</StepUp>
<StepDown onClick={stepDown}>
//icon will go here
</StepDown>
</InputContainer>
</>
);
Form.js
const Form = (props) => {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
}
const stepUp = () => {
setValue(value++);
}
const stepDown = () => {
setValue(value--);
}
return (
<NumericInput
stepUp={stepUp}
stepDown={stepDown}
handleChange={handleChange}
label="Numeric"
)
}
Let's try to boil down your questions a bit:
[NumericInput] is reusable but requires a lot of repetition (opposite of DRY mentality) because everywhere in my app that I want to use this component, I have to implement these state values, a handleChange function, and in the case of my , two additional functions to control the stepper arrows.
The only repetition you have is to define a value and a handleChange callback property for <NumericInput />. The value prop contains your numeric input value and is passed in from the parent container component owning that state. You need the callback to trigger a change request from the child (controlled component). That is the ususal approach, when you need to lift state up or divide your components in container and presentational components.
stepUp/stepDown methods are an implementation detail of NumericInput, so you can remove them from Form. Form just wants to know, if a value change has been triggered and what the new changed numeric value is.
Concerning where to store the state: There is probably a reason, why you want to save the state in the Form component. The user enters some input in (possibly) multiple fields before pressing the submit button. In this moment your form needs all the state of its child fields to trigger further processing based on the given input. And the idiomatic React way is to lift state up to the form to have the information.
I understand that this model allows our components to be flexible, but they also seem to be more error-prone and dependent on other components elsewhere. Again, it's also repetitive.
A presentational component is rather less error prone and is independent, because it doesn't care about state! For sure, you write a bit more boilerplate code by exposing callbacks and value props in child. The big advantage is, that it makes the stateless components far more predictable and testable and you can shift focus on complex components with state, e.g. when debugging.
In order to ease up props passing from Form to multiple child components, you also could consolidate all event handlers to a single one and pass state for all inputs. Here is a simple example, how you could handle the components:
NumericInput.js:
const NumericInput = ({ label, value, onValueChanged }) => {
const handleStepUp = () => {
onValueChanged(value + 1);
};
const handleStepDown = () => {
onValueChanged(value - 1);
};
return (
<>
...
<Input value={this.state.value} />
<StepUp onClick={handleStepUp}></StepUp>
<StepDown onClick={handleStepDown}></StepDown>
</>
);
};
Form.js:
const Form = (props) => {
const [value, setValue] = useState(0);
const handleValueChanged = (value) => {
setValue(value);
}
return (
<NumericInput
onValueChanged={handleValueChanged}
value={value}
label="Numeric"
)
}
Move the setValue function to the NumericInput component
Manage your state through the component.
return (
<NumericInput
setValue={setValue}
label="Numeric"
/>
)
I would recommend you use hooks

Understanding state and props changes with rerendering in react components

I have a component which isn't re-rendering as I'd expect. I'm less concerned about this specific example than having a better understanding about how state, props, updates, re-rendering and more happens in the react-redux lifecycle.
My current code is about creating a delivery with a list of locations. The main issue is that reordering the location's itinerary doesn't seem to work - the state updates in the reducer correctly, but the components are not rerendering.
This is the relevant snippet from delivery.js, a component which uses the LocationSearch custom component to display each location in the list of locations:
{console.log("Rendering...")}
{console.log(delivery.locations)}
{delivery.locations.map((location, index) => (
<div key={index}>
<LocationSearch
{...location}
total={delivery.locations.length+1}
index={index}
/>
</div>
))}
The console.logs print out the correct data where and when expected. When an action to reorder the locations is triggered (from within LocationSearch), the console log prints out the list of locations with the data updated correctly. However the component is not displaying anything updated.
Here is some relevant parts of the LocationSearch component:
export class LocationSearch extends Component {
constructor(props) {
super(props)
this.state = {
searchText: this.props.address
}
this.handleUpdateInput = this.handleUpdateInput.bind(this)
}
handleUpdateInput (searchText) {
this.setState({
searchText: searchText
})
this.props.handleUpdateInput(searchText)
}
render(){
const { type, itineraryOrder, floors, elevator, accessDistance, index} = this.props
return (
...display all the stuff
)
}
}
...map dispatch, connect, etc...
function mapStateToProps(state, ownProps) {
return {
suggestions: state.delivery.suggestions,
data: ownProps.data
};
}
This is where I get confused - I figure I'm meant to do something along the lines of componentWillUpdate, but can't find any good explanations for what happens at each step. Do I just set this.props = nextProps in there? Shouldn't this.props have updated already from the parent component passing them in? How come most components seem to rerender by themselves?
Any help you can give or links to good resources I'd be grateful for. Thanks in advance!
I kept running into this kind of issues right before I discovered redux. Considering you mentioned already using react-redux, maybe what you should do is switch to a container/component structure and forget about componentWillUpdate()
Basically, what this allows for is just passing fresh props to the components that renders the actual HTML, so you don't have to replace the props by hand.
Your container could be something like this
import React from 'react'
import { connect } from 'react-redux'
import PresentationalComponent from 'components/PresentationalComponent'
const Container = props => <PresentationalComponent {...props} />
export default connect( state => ({
searchText: state.UI.searchText,
locations: [...],
}), dispatch => ({
handleUpdateInput: e => dispatch( {
type: "CHANGE_SEARCH_TEXT",
text: e.target.value,
} ),
}))(Container)
And your presentational component
import React from 'react'
const PresentationalComponent = ({ searchText, locations, handleUpdateInput }) =>
<div>
{locations.map(location => <p>{location}</p>)}
<input defaultValue={searchText} type="text" onChange={handleUpdateInput} />
</div>
export default PresentationalComponent

In a React Component, where should I bind arguments to my onClick functions?

So in an example component
<Button doStuff={doStuff} />
I know this is bad, it creates a new function on every render:
class Button extends Component {
const value = getClickValue(this.props); // do something with props
render() {
<button onClick={() => this.props.doStuff(value)}>Click me</button>
}
}
But not sure which is better for performance from the following:
1) Bind click value in a class property function
This is a trivial example, but in my current component the code to process the props is cluttering my component up and makes the component more smart and less dumb, when you want to strive to have DUMB components and smart containers.
class Button extends Component {
handleClick = () => {
const value = getClickValue(this.props); // do something with props to get value
this.props.doStuff(value);
}
render() {
<button onClick={this.handleClick}>Click me</button>
}
}
2) Send bound functions as props from a container to a stateless functional component.
This method seems better from an organizational perspective. The component is as dumb as possible and is only concerned with rendering. But does it impact performance? As in, is a new function constructed every time the ownProps changes?
const Button = (onClick) => (
<button onClick={this.props.onClick}>Click me!</button>
)
import { connect } from 'react-redux';
const mapDispatchToProps = (dispatch, ownProps) => {
const value = getClickValue(ownProps);
return {
onClick: () => doStuff(value)
};
};
connect(null, mapDispatchToprops)(Button);
In your second approach, every time your props change, mapDispatchToProps is called. Since you're returning an arrow function, it will create that function again.
That makes your first suggestion the best option.

Resources