Rendering multiple times? - javascript

I have a form component, and the reference of input fields are linked to the useForm reducer with references. I have to set a initial form state after setting the input field references? I have done as below. But it is rendering thrice. How to solve this rendering issue?
import React, { useState } from 'react';
const useForm = () => {
const [ formState, setFormState ] = useState({});
const refs = useRef({});
const register = useCallback(( fieldArgs ) => ref => {
if(fieldArgs) {
const { name, validations, initialValue } = fieldArgs;
refs.current[name] = ref;
}
console.log('Register rendered');
}, []);
useEffect(() => {
console.log('Effect Rendered');
const refsKeys = Object.keys(refs.current);
refsKeys.forEach(refKey => {
if(!formState[refKey]) {
setFormState(prevState => {
return {
...prevState,
[refKey]: {
value: '',
touched: false,
untouched: true,
pristine: true,
dirty: false
}
}
});
}
});
}, [ refs ]);
return [ register ];
}
export { useForm };
And the app component as below
const App = () => {
const [ register ] = useFormsio();
return(
<form>
<input
type = 'email'
placeholder = 'Enter your email'
name = 'userEmail'
ref = { register({ name: 'userEmail' }) } />
<button
type = 'submit'>
Submit
</button>
</form>
)
}
How to solve this multiple rendering issue?

I think the issue in the code above is whenever refs changes you need to loop through all the fields in form and set the state.
Why don't you set the state in register method?
const register = useCallback(( fieldArgs ) => ref => {
if(fieldArgs) {
const { name, validations, initialValue } = fieldArgs;
if(!refs.current[name] ) {
refs.current[name] = ref;
setFormState(prevState => {
return {
...prevState,
[refKey]: {
value: '',
touched: false,
untouched: true,
pristine: true,
dirty: false
}
}
});
}
}
console.log('Register rendered');
}, []);

Related

"Objects are not valid as a React child" in Object destructuring

I'm having a React Error #31 which describes as "Objects are not valid as a React child."
The problem is I'm trying to do Object Destructuring for Form Validations as follows, so I'm trying to figure out the best way to solve this (unless refactoring everything to arrays?).
import { useReducer } from 'react';
const initialInputState = {
value: '',
isTouched: false,
};
const inputStateReducer = (state, action) => {
if (action.type === 'INPUT') {
return { value: action.value, isTouched: state.isTouched };
}
if (action.type === 'BLUR') {
return { isTouched: true, value: state.value };
}
if (action.type === 'RESET') {
return { isTouched: false, value: '' };
}
return inputStateReducer;
};
const useInput = (validateValue) => {
const [inputState, dispatch] = useReducer(inputStateReducer, initialInputState);
const valueIsValid = true;
const hasError = !valueIsValid && inputState.isTouched;
const valueChangeHandler = (event) => {
dispatch({ type: 'INPUT', value: event.target.value });
}
const inputBlurHandler = (event) => {
dispatch({ type: 'BLUR' });
}
const reset = () => {
dispatch({ type: 'RESET' });
}
return {
value: inputState.value,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
reset,
}
}
export default useInput;
import useInput from "../hooks/use-input";
import { useState } from 'react';
export default function InterestForms({ photoSet, onInterestLightboxChange, interestLightboxContent }) {
const alphabetOnly = (value) => /^[a-zA-Z() ]+$/.test(value);
const [isFormSubmitted, setFormSubmitted] = useState(false);
// Calling useInput and expect values in object destructuring.
// However, it seems this is causing React Error #31 since it expect values to be in different format
const {
value: nameValue,
isValid: nameIsValid,
hasError: nameHasError,
valueChangeHandler: nameChangeHandler,
inputBlurHandler: nameBlurHandler,
reset: resetName,
} = useInput(alphabetOnly);
...
Refactoring this to arrays might solve the problem. But I want to see if there's a better solution (or if I just missed something simple).

Why is onInput not a function? A question about useEffect()

I am pretty new to react and hooks, and I'm struggling with useEffect(). I've watched all the vids and read all the docs and still can't quite wrap my head around the error I'm getting. ("onInput is not a function" when my New Article route loads). onInput points to a callback function in my form-hook.js. Why isn't it registering?
In my input.js component:
import React, { useReducer, useEffect } from 'react';
import { validate } from '../../util/validators';
import './Input.css';
const inputReducer = (state, action) => {
switch (action.type) {
case 'CHANGE':
return {
...state,
value: action.val,
isValid: validate(action.val, action.validators)
};
case 'TOUCH': {
return {
...state,
isTouched: true
}
}
default:
return state;
}
};
const Input = props => {
const [inputState, dispatch] = useReducer(inputReducer, {
value: props.initialValue || '',
isTouched: false,
isValid: props.initialValid || false
});
const { id, onInput } = props;
const { value, isValid } = inputState;
useEffect(() => {
console.log(id);
onInput(id, value, isValid)
}, [id, value, isValid, onInput]);
const changeHandler = event => {
dispatch({
type: 'CHANGE',
val: event.target.value,
validators: props.validators
});
};
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
//if statement to handle if you are updating an article and touch the category.... but it's valid
const element =
props.element === 'input' ? (
<input
id={props.id}
type={props.type}
placeholder={props.placeholder}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
/>
) : (
<textarea
id={props.id}
rows={props.rows || 3}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
/>
);
return (
<div
className={`form-control ${!inputState.isValid && inputState.isTouched &&
'form-control--invalid'}`}
>
<label htmlFor={props.id}>{props.label}</label>
{element}
{!inputState.isValid && inputState.isTouched && <p>{props.errorText}</p>}
</div>
);
};
export default Input;
useEffect(() => {onInput points to the onInput prop in NewArticle.js component where users can enter a new article.
import Input from '../../shared/components/FormElements/Input';
import { useForm } from '../../shared/hooks/form-hook';
const NewArticle = () => {
const [formState, inputHandler] = useForm({
title: {
value: '',
isValid: false
}
}, false );
return (
<Input
id="title"
element="input"
type="text"
label="Title"
onInput={inputHandler}
/> );
};
export default NewArticle;
...and then in my form-hook.js inputHandler is a callback. So, onInput points to a callback function through a prop. It was working, registering onInput as a function and then, all of a sudden it was throwing an error. What gives?
import { useCallback, useReducer } from 'react';
const formReducer = (state, action) => {
switch (action.type) {
case 'INPUT_CHANGE':
let formIsValid = true;
for (const inputId in state.inputs) {
if (!state.inputs[inputId]) {
continue;
}
if (inputId === action.inputId) {
formIsValid = formIsValid && action.isValid;
} else {
formIsValid = formIsValid && state.inputs[inputId].isValid;
}
}
return {
...state,
inputs: {
...state.inputs,
[action.inputId]: { value: action.value, isValid: action.isValid }
},
isValid: formIsValid
};
case 'SET_DATA':
return {
inputs: action.inputs,
isValid: action.formIsValid
};
default:
return state;
}
};
export const useForm = (initialInputs, initialFormValidity) => {
const [formState, dispatch] = useReducer(formReducer, {
inputs: initialInputs,
isValid: initialFormValidity
});
const inputHandler = useCallback((id, value, isValid) => {
dispatch({
type: 'INPUT_CHANGE',
value: value,
isValid: isValid,
inputId: id
});
}, []);
const setFormData = useCallback((inputData, formValidity) => {
dispatch({
type: 'SET_DATA',
inputs: inputData,
formIsValid: formValidity
});
}, []);
return [formState, inputHandler, setFormData];
};
Thanks, ya'll.
I can give you some advice on how to restructure your code. This will ultimately solve your problem.
Maintain a single source of truth
The current state of your UI should be stored in a single location.
If the state is shared by multiple components, your best options are to use a reducer passed down by the Context API (redux), or pass down the container component's state as props to the Input component (your current strategy).
This means you should remove the Input component's inputReducer.
The onInput prop should update state in the container component, and then pass down a new inputValue to the Input component.
The DOM input element should call onInput directly instead of as a side effect.
Remove the useEffect call.
Separation of Concerns
Actions should be defined separately from the hook. Traditionally, actions are a function that returns an object which is passed to dispatch.
I am fairly certain that the useCallback calls here are hurting performance more than helping. For example inputHandler can be restructured like so:
const inputChange = (inputId, value, isValid) => ({
type: 'INPUT_CHANGE',
value,
isValid,
inputId
})
export const useForm = (initialInputs, initialFormValidity) => {
const [formState, dispatch] = useReducer(formReducer, {
inputs: initialInputs,
isValid: initialFormValidity,
})
const inputHandler = (id, value, isValid) => dispatch(
inputChange(id, value, isValid)
)
}
Learn how to use debugger or breakpoints in the browser. You would quickly be able to diagnose your issue if you put a breakpoint inside your useEffect call.

How to update state array?

I am making a App in Reactjs. In it I have an API that gets data from a AWS database table and populates a state array which then populates a tables.
I cant figure out how to let a user in turn update the values of the table and then save those changes and let the API upload it back into the data base.
I have a update method but it gives an error saying that I cant update the state Array or would complain that the column array is undefined.
I have the following code:
export default function Complaints(props) {
const [complaint, setComplaint] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isInEditMode, setIsEditMode] = useState(false);
const [defaultValue, setDefaultValue] = useState("");
var columsArr = [
{ title: 'Customer ID', field: 'id' },
{ title: 'Issue', field: 'complaintName' },
{ title: 'Description', field: 'complaintDescription'},
{ title: 'Order ID', field: 'complaintOrderId'},
{ title: 'Submitted', field: 'createdAt'},
{ title: 'Updated', field: 'updatedAt'},
{ title: 'Admin Comment', field: 'adminComment'},
];
useEffect(() => {
async function onLoad() {
if (!props.isAuthenticated) {
return;
}
try {
const complaint = await loadComplaint();
setComplaint(complaint);
setState({
columns: [state.columns, ...columsArr],
complaint: [...state.complaint, ...complaint]
});
console.log(complaint)
} catch (e) {
alert(e);
}
setIsLoading(false);
}
onLoad();
}, [props.isAuthenticated]);
function loadComplaint() {
return API.get("kleen", "/Complaint");
}
// function edit(adminComment) {
// setIsEditMode(true);
// setDefaultValue(adminComment);
// console.log("value is"+ adminComment);
// }
// function updateComplaint() {
// return API.put("kleen", `/Complaint/${props.}`);
// }
const [state, setState] = React.useState({
columns: [],
complaint: []
});
return (
<MaterialTable style={{
marginTop: "8rem",
marginLeft: "auto",
marginRight: "auto",
position: "sticky",
}}
title="Complaints"
columns={state.columns}
data={state.complaint}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
let key = 1;
setState(prevState => {
complaint: prevState.complaint.map(el => el.key === key ? {...el, adminComment: "testing"}: el)
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
});
}
}, 600);
}),
}}
/>
);
}
I am very new to Reactjs, any help would be greatly appreciated.
One way that you can achieve is by:
First store the state in a variable
Update the array variable
Restore the variable to the state.
If you want to use state then better to use component class
export default class Complaints extends Component {
//assign initial value to state
state={
propsObj : this.props
}
...
}
If you want to use component you have to import it
import React, { Component } from "react";
inside component class you have to use this.props instead of props
later if you want to change state just use this.setState({ propsObj: 'some thing new'});

State does not get updated with React hooks

I thought I got my head around closures in hooks. However, I'm struggling with this one. The code below is a very simplified version of my actual code for demonstration. In reality, clickHandler() and upload() are far more complex. That's why it's not an option to combine them into one function. The issue is that I can't access the updated files array from the state in the upload function. If I use a ref for the array it works, however, I think it's an antipattern. I also tried to declare the state outside the component, which didn't work. Thanks for your help.
import React, { useCallback, useState } from 'react';
const HomeScreen = () => {
const initialState = {
error: false,
files: [],
totalSize: 0,
finished: false
};
const [state, setState] = useState(initialState);
const upload = useCallback(() => {
// At this point state.files is an empty array
console.log(state.files);
}, [state]);
const clickHandler = useCallback(() => {
const queue = [
{ id: 1, bool: false },
{ id: 2, bool: false }
];
setState((state) => {
return { ...state, files: queue }
});
upload()
}, [upload]);
return <button onClick={clickHandler}>Click me</button>;
};
export default HomeScreen;
state updates using hooks state updater is asynchronous and hence when you call upload from within clickHandler the updated state isn't available to you. The simplest solution to pass the state to the upload method
import React, { useCallback, useState } from 'react';
const HomeScreen = () => {
const initialState = {
error: false,
files: [],
totalSize: 0,
finished: false
};
const [state, setState] = useState(initialState);
const upload = useCallback((updatedState) => {
// At this point state.files is an empty array
console.log((updatedState || state).files);
}, [state]);
const clickHandler = useCallback(() => {
const queue = [
{ id: 1, bool: false },
{ id: 2, bool: false }
];
setState((state) => {
const updatedState = { ...state, files: queue }
upload(updatedState )
return updatedState;
});
upload()
}, [upload]);
return <button onClick={clickHandler}>Click me</button>;
};
export default HomeScreen;
For your upload() function to have access to the current state it must be called from a useEffect(). See if that works for you?
const HomeScreen = () => {
const initialState = {
error: false,
files: [],
totalSize: 0,
finished: false
};
const [state, setState] = React.useState(initialState);
const upload = React.useCallback(() => {
// At this point state.files is an empty array
console.log(state.files);
}, [state]);
React.useEffect(()=>{
state.files.length ? upload() : null;
},[state]);
const clickHandler = React.useCallback(() => {
const queue = [
{ id: 1, bool: false },
{ id: 2, bool: false }
];
setState((state) => {
return { ...state, files: queue }
});
//upload()
}, [upload]);
return <button onClick={clickHandler}>Click me</button>;
};
ReactDOM.render(<HomeScreen/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React-Redux Refactoring Container Logic

I got one container connected to one component. Its a select-suggestion component. The problem is that both my container and component are getting too much repeated logic and i want to solve this maybe creating a configuration file or receiving from props one config.
This is the code:
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { goToPageRequest as goToPageRequestCompetitions } from '../ducks/competitions/index';
import { getSearchParam as getSearchCompetitionsParam, getCompetitionsList } from '../ducks/competitions/selectors';
import { goToPageRequest as goToPageRequestIntermediaries } from '../ducks/intermediaries/index';
import { getSearchParam as getSearchIntermediariesParam, getIntermediariesList } from '../ducks/intermediaries/selectors';
import SelectBox2 from '../components/SelectBox2';
export const COMPETITIONS_CONFIGURATION = {
goToPageRequest: goToPageRequestCompetitions(),
getSearchParam: getSearchCompetitionsParam(),
suggestions: getCompetitionsList()
};
export const INTERMEDIARIES_CONFIGURATION = {
goToPageRequest: goToPageRequestIntermediaries(),
getSearchParam: getSearchIntermediariesParam(),
suggestions: getIntermediariesList()
};
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(SelectBox2));
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Flex, Box } from 'reflexbox';
import classname from 'classnames';
import styles from './index.scss';
import Input from '../Input';
import { AppButtonRoundSquareGray } from '../AppButton';
import RemovableList from '../RemovableList';
const MIN_VALUE_TO_SEARCH = 5;
const NO_SUGGESTIONS_RESULTS = 'No results found';
class SelectBox extends Component {
/**
* Component setup
* -------------------------------------------------------------------------*/
constructor(props) {
super(props);
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
}
/**
* Component lifecycle
* -------------------------------------------------------------------------*/
componentWillMount() {
console.log(this.props);
document.addEventListener('mousedown', this.onClickOutside, false);
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
}
componentWillUnmount() {
console.log(this.props);
document.removeEventListener('mousedown', this.onClickOutside, false);
}
componentWillReceiveProps(nextProps){
console.log(this.props);
if (this.props.suggestionsType === 'competition') {
this.state.suggestions = nextProps.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries') {
this.state.suggestions = nextProps.intermediariesSuggestions;
}
}
/**
* DOM event handlers
* -------------------------------------------------------------------------*/
onButtonClick = (ev) => {
ev.preventDefault();
const itemIncluded = this.state.items.find(item => item.id === this.state.selection);
if (this.state.selection && !itemIncluded) {
const item =
this.state.suggestions.find(suggestion => suggestion.id === this.state.selection);
this.setState({ items: [...this.state.items, item] });
}
};
onChangeList = (items) => {
const adaptedItems = items
.map(item => ({ label: item.name, id: item.itemName }));
this.setState({ items: adaptedItems });
};
onClickOutside = (ev) => {
if (this.wrapperRef && !this.wrapperRef.contains(ev.target)) {
this.setState({ displayBox: false });
}
};
onSuggestionSelected = (ev) => {
this.setState({
displayBox: false,
value: ev.target.textContent,
selection: ev.target.id });
};
onInputChange = (ev) => {
this.generateSuggestions(ev.target.value);
};
onInputFocus = () => {
this.generateSuggestions(this.state.value);
};
/**
* Helper functions
* -------------------------------------------------------------------------*/
setWrapperRef = (node) => {
this.wrapperRef = node;
};
executeSearch = (value) => {
if (this.props.suggestionsType === 'competition'){
this.props.searchCompetitions(value);
}
if (this.props.suggestionsType === 'intermediaries'){
this.props.searchIntermediaries(value);
}
};
generateSuggestions = (value) => {
if (value.length > MIN_VALUE_TO_SEARCH) {
this.executeSearch(value);
this.setState({ displayBox: true, value, selection: '' });
} else {
this.setState({ displayBox: false, value, selection: '' });
}
};
renderDataSuggestions = () => {
const { listId } = this.props;
const displayClass = this.state.displayBox ? 'suggestions-enabled' : 'suggestions-disabled';
return (
<ul
id={listId}
className={classname(styles['custom-box'], styles[displayClass], styles['select-search-box__select'])}
>
{ this.state.suggestions.length !== 0 ?
this.state.suggestions.map(suggestion => (<li
className={classname(styles['select-search-box__suggestion'])}
onClick={this.onSuggestionSelected}
id={suggestion.get(this.props.suggestionsOptions.id)}
key={suggestion.get(this.props.suggestionsOptions.id)}
>
<span>{suggestion.get(this.props.suggestionsOptions.label)}</span>
</li>))
:
<li className={(styles['select-search-box__no-result'])}>
<span>{NO_SUGGESTIONS_RESULTS}</span>
</li>
}
</ul>
);
};
renderRemovableList = () => {
if (this.state.items.length > 0) {
const adaptedList = this.state.items
.map(item => ({ name: item.name, itemName: item.id }));
return (<RemovableList
value={adaptedList}
className={classname(styles['list-box'])}
onChange={this.onChangeList}
uniqueIdentifier="itemName"
/>);
}
return '';
};
render() {
const input = {
onChange: this.onInputChange,
onFocus: this.onInputFocus,
value: this.state.value
};
return (
<Flex className={styles['form-selectBox']}>
<Box w={1}>
<div
ref={this.setWrapperRef}
className={styles['div-container']}
>
<Input
{...this.props}
input={input}
list={this.props.listId}
inputStyle={classname('form-input--bordered', 'form-input--rounded', styles.placeholder)}
/>
{ this.renderDataSuggestions() }
</div>
</Box>
<Box>
<AppButtonRoundSquareGray type="submit" className={styles['add-button']} onClick={this.onButtonClick}>
Add
</AppButtonRoundSquareGray>
</Box>
<Box>
{ this.renderRemovableList() }
</Box>
</Flex>
);
}
}
SelectBox.propTypes = {
items: PropTypes.instanceOf(Array),
placeholder: PropTypes.string,
listId: PropTypes.string,
className: PropTypes.string
};
SelectBox.defaultProps = {
items: [],
placeholder: 'Choose an option...',
listId: null,
className: ''
};
export default SelectBox;
As you see, in many places i am validating the type of suggestions and do something with that. Its suppose to be a reusable component, and this component could accept any kind of type of suggestions. If this grows, if will have very big validations and i don't want that. So i think that i want something similar to this:
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
How can i make something similar to that?
Here are a few things to consider:
The purpose of using Redux is to remove state logic from your components.
What you've currently got has Redux providing some state and your component providing some state. This is an anti-pattern (bad):
// State from Redux: (line 22 - 24)
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
// State from your component: (line 65 - 71)
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
If you take another look at your SelectBox component - a lot of what it is doing is selecting state:
// The component is parsing the state and choosing what to render (line 79 - 86)
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
Turns out, this is precisely what mapStateToProps() is for. You should move this selection logic to mapStateToProps(). Something like this:
const mapStateToProps = (state) => {
let suggestions = null;
switch (state.suggestionType) {
case 'competition':
suggestions = state.suggestions.competition;
break;
case 'intermediaries':
suggestions = state.suggestions.intermediaries;
break;
default:
break;
}
return {
suggestions
};
};
Every time the state updates (in Redux) it will pass new props to your component. Your component should only be concerned with how to render its part of the state. And this leads me to my next point: When your application state is all being managed by Redux and you don't have state logic in your components, your components can simply be functions (functional components).
const SelectBox3 = ({ suggestions }) => {
const onClick = evt => { console.log('CLICK!'); };
const list = suggestions.map((suggestion, index) => {
return (
<li key={index} onClick={onClick}>suggestion</li>
);
});
return (
<ul>
{list}
</ul>
);
};
Applying these patterns, you get components that are very easy to reason about, and that is a big deal if you want to maintain this code into the future.
Also, by the way, you don't need to use mergeProps() in your example. mapDispatchToProps can just return your search function since connect() will automatically assemble the final props object for you.:
const mapDispatchToProps = (dispatch, ownProps) => ({
// 'search' will be a key on the props object passed to the component!
search: searchParam => {
dispatch(ownProps.reduxConfiguration.goToPageRequest({ searchParam });
// (also, your 'reduxConfiguration' is probably something that belongs in
// the Redux state.)
}
});
I highly recommend giving the Redux docs a good read-through. Dan Abramov (and crew) have done a great job of laying it all out in there and explaining why the patterns are the way they are.
Here's the link: Redux.
Also, look into async actions and redux-thunk for dealing with asynchronous calls (for performing a search on a server, for example).
Finally let me say: you're on the right track. Keep working on it, and soon you will know the joy of writing elegant functional components for your web apps. Good luck!

Categories