I wonder what I am doing wrong here. The dispatch methods are dispatching correct values but the state object is showing wrong values.
{ name: "name", room: "room" } is what I am dispatching separately. But the state is showing { name: "room": room: "" }
Google chrome logs :
NOTE: please checkout the code here from the github repo incase needed.
Reducer:
export const initialState = {
name: '',
room: ''
}
export const reducer = (state, action) => {
console.log("Calling action", action);
switch (action.type) {
case types.SET_NAME:
return { ...state, name: action.name }
case types.SET_ROOM:
return { ...state, name: action.room }
default:
return state;
}
}
_app component:
import DataProvider from "../context/DataProvider";
import { initialState } from '../reducers/index';
import { reducer } from '../reducers';
const AppComponent = ({ Component, pageProps }) => {
return (
<DataProvider intialState={initialState} reducer={reducer}>
<Component {...pageProps} />
</DataProvider>
)
}
AppComponent.getInitialProps = async (appContext) => {
let pageProps = {};
if (appContext.Component.getInitialProps) {
pageProps = await appContext.Component.getInitialProps(appContext.ctx);
}
return { pageProps }
}
export default AppComponent;
Component:
const Join = () => {
const [name, setName] = input('');
const [room, setRoom] = input('');
const [state, dispatch] = useContext(DataContext);
const submit = (e) => {
if (name === '' || room === '') {
e.preventDefault();
return;
}
dispatch({
type: types.SET_NAME,
name
});
dispatch({
type: types.SET_ROOM,
room
});
}
return (
<div>
<h1>Join</h1>
<input onChange={(e) => setName(e)} placeholder="name" />
<input onChange={(e) => setRoom(e)} placeholder="room" />
<Link href="/chat">
<button type="submit" onClick={(e) => submit(e)}>Submit</button>
</Link>
</div>
)
}
Chat component (where I am consuming state):
const Chat = () => {
// const backendEndpoint = 'http://localhost:5000';
const [state, dispatch] = useContext(DataContext);
console.log('STATE', state)
return <h1>Chat</h1>
}
Chat.getInitialProps = async (ctx) => {
return {}
}
export default Chat;
I think the problem is in your reducer
case types.SET_ROOM:
return { ...state, name: action.room }
Here you change the name in rooms action
maybe you need to update like this
return { ...state, room: action.room }
actually u make a mistake in your Reducer.js
export const reducer = (state, action) => {
console.log("Calling action", action);
switch (action.type) {
case types.SET_NAME:
// equals state.name = action.name
// state = { name: 'name', room: '' }
return { ...state, name: action.name }
case types.SET_ROOM:
// equal state.name = action.room
// state = { name: 'room', room: '' }
return { ...state, name: action.room }
default:
return state;
}
}
// u can change your code style to reduce mistakes
export const reducer = (state, action) => {
const {name, room} = action
switch (action.type) {
case types.SET_NAME:
return { ...state, name }
case types.SET_ROOM:
return { ...state, room }
default:
return state;
}
}
Related
currently i have one problem in my forms i cant clear inputs after form is submitted..
I have two files for input and form:
form-hook.js
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];
};
And here i got one Input.js file:
import React, { useReducer, useEffect } from 'react';
import { validate } from '../../util/validators';
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, clearInputs } = props;
const { value, isValid } = inputState;
useEffect(() => {
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'
});
};
const element =
props.element === 'input' ? (
<input
id={props.id}
type={props.type}
placeholder={props.placeholder}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
maxLength={props.maxlength}
/>
) : (
<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;
My problem here is that i can't access this inputState, it looks like the value is stored in it...
And this is just an example how is this used in my components and this is functional programing in reactjs..
import React, { useState, useContext } from 'react';
import { Text, LanguageContext } from '../../lang/containers/Language';
import Input from '../../shared/components/FormElements/Input';
import Button from '../../shared/components/FormElements/Button';
import ErrorModal from '../../shared/components/UIElements/ErrorModal';
import {
VALIDATOR_EMAIL
} from '../../shared/util/validators';
import { useForm } from '../../shared/hooks/form-hook';
import { useHttpClient } from '../../shared/hooks/http-hook';
const NewSletter = () => {
const { dictionary } = useContext(LanguageContext);
const { isLoading, error, sendRequest, clearError } = useHttpClient();
const [joined, setJoined] = useState(false);
const [formState, inputHandler] = useForm(
{
email: {
value: '',
isValid: false
}
},
false
);
const newsletterSubmit = async event => {
event.preventDefault();
try {
const responseData = await sendRequest(
'http://localhost:5000/api/users/newsletter',
'POST',
JSON.stringify({
email: formState.inputs.email.value,
}),
{
'Content-Type': 'application/json'
}
);
if(responseData.message == 'subscribed'){
setJoined(true);
//here i need to clean all inputs in this form
}
} catch (err) {
console.log(err)
}
};
return (
<>
<ErrorModal error={error ? <Text tid={error}/> : null} onClear={clearError} />
<div className="newsletter-bg">
<div className="container">
<div className="newsletter-bg-title"><Text tid="newsletter"/></div>
<div className="space10px"></div>
<form className="newsletter-form" onSubmit={newsletterSubmit}>
<Input
element="input"
id="email"
type="email"
label={<Text tid="auth_lang4" />}
validators={[VALIDATOR_EMAIL()]}
errorText={<Text tid="auth_lang5" />}
onInput={inputHandler}
/>
<Button type="submit" disabled={!formState.isValid}>
<Text tid="add"/>
</Button>
{joined === true ? <div className="biltenmsg"> <span style={{color: 'green'}}><Text tid="joinedsletter"/></span> </div> : null}
</form>
</div>
</div>
</>
);
};
export default NewSletter;
I also tried serveral examples from here like reset() and others things, also tried to change useForm things but i couldnt delete value because it read value from input.js file in inputState how i figured... I also tryed with setformdata and some others examples but i didnt solve this problem.. Best regards.
You can only update inputState by dispatch only
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
};
}
case 'RESET': {
return {
value: action.value,
isTouched: action.isTouched,
isValid: action.isValid
};
}
default:
return state;
}
};
then
dispatch({
type: 'RESET',
value: props.initialValue || '',
isTouched: false,
isValid: props.initialValid || false
});
I don't see much problem in your formReducer which got used by useForm and inside you defined a inputHandler that is must-have to get any input work. However your Input is overly complicated IMHO.
Attached is an example useForm
const useForm = (props = {}) => {
const [state, dispatch] = useReducer(reducer, initialState(props))
const change = useCallback((payload) => {
dispatch({ type: 'change', payload })
}, [])
const validate = useCallback((payload) => {
if (!state.validate) return
dispatch({ type: 'validate', payload: payload || state.values })
}, [state])
const submit = useCallback((payload) => {
dispatch({ type: 'submit', payload })
}, [])
const onChange = useCallback(e => {
const target = e.target
const payload = { [target.name]: target.value }
change(payload)
validate(payload)
}, [change, validate])
const onSubmit = useCallback(e => {
if (e) e.preventDefault()
submit(state.values)
}, [submit, state.values])
/**
* #typedef {object} useFormObject
* #property {string} name Form name
* #property {object} values Form values
* #property {func} change Form change function
* #property {object} handlers Form events
* #property {func} handlers.onChange Event upon input change
* #property {func} handlers.onSubmit Event upon form submit
* #property {func} validate Form validate function
* #property {object} errors Form errors
* #property {bool} error Form in error
*/
return {
name: state.name,
values: state.values,
change,
handlers: {
onChange,
onSubmit
},
validate,
errors: state.errors,
error: hasError(state.errors),
}
}
export default useForm
and the usage is
const { values, handlers, errors } = useForm({ initialValues })
and then every input widget is about
<input value={values.abc} onChange={handlers.onChange} />
You can see there's no need for another level of reducer inside input, because both value and onChange are almost "read-only" properties from input point of view.
For completeness, reducer is attached here.
import hasError from './hasError'
const defaultState = {
values: {},
errors: {},
watchers: {}
}
const reducer = (state = defaultState, action) => {
let payload = {}
if (action && action.payload) {
payload = action.payload
}
const watchers = state.watchers || {}
const watch = state.watch || (() => { })
const validate = state.validate || (() => { })
const { onSubmitted } = watchers
let errors
switch (action.type) {
case 'change':
watch(action.type, payload)
return {
...state,
values: {
...state.values,
...payload
}
}
case 'validate':
errors = validate(payload, action.type)
watch(action.type, errors)
return {
...state,
errors: {
...state.errors,
...errors
}
}
case 'submit':
errors = validate(payload, action.type)
watch('validation', errors)
if (hasError(errors)) {
return {
...state,
errors
}
}
if (onSubmitted) {
onSubmitted(state.values)
}
return state
default:
return state
}
}
export default reducer
i have this code from https://jsbin.com/zirugiteju/edit?js,console,output
but i have a question, what is {store.getState().todos} its in the bottom of the code, see please this part
const render = () => {
ReactDOM.render(
this is the code complete
const todo = (state, action) => {
switch(action.type) {
case 'ADD_TODO':
console.log("ADD");
return {
id : action.id,
text: action.text,
completed: false
};
break;
case 'TOGGLE_TODO':
if (state.id !== action.id ) {
return state;
}
return {
...state,
completed: !state.completed
};
break;
default:
return state;
}
};
const todos = (state=[], action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
break;
case 'TOGGLE_TODO':
return state.map(t =>todo(t, action));
break;
default:
return state;
}
};
const visibilityFilter = (state='SHOW_ALL', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
break;
default:
return state;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
const { createStore } = Redux;
const store = createStore(todoApp);
const { Component } = React;
let nextTodoId = 0;
class TodoApp extends Component {
render() {
return (
<div>
<input ref={node => {
this.input = node
}} />
<button onClick={()=>{
store.dispatch({
type: 'ADD_TODO',
text: this.input.value,
id : nextTodoId++
});
this.input.value = '';
}}>
Add Todo</button>
<ul>
{this.props.todos.map(todo =>
<li key={todo.id}>
{todo.text}
</li>
)}
</ul>
</div>
);
}
}
const render = () => {
ReactDOM.render(
<TodoApp
todos={store.getState().todos}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();
The prop todos = {store.getState().todos} that you are passing to TodoApp component is a reducer, called todos, from your redux state that you combined in this part of the code:
const todoApp = combineReducers({
todos,
visibilityFilter
});
The getState() function returns the current state tree of your application. It is equal to the last value returned by the store's reducer.
You can learn more about redux States in this link: https://redux.js.org/api/store
I'm using react and redux and I'm trying a simple example.
If I clicked the button, then the number should increment by 1.
But when I currently click the button you'll see all element gonna + 1
how can i fix that?...
class Menu extends Component {
componentDidMount() {
this.props.getItems();
}
plus = () =>{
this.props.getplus();
}
render() {
const {item, count} = this.props.item
return (
<div>
{item.map(items => {
<div>{items.example}</div> <buttom onClick={this.plus}> + </button>
<div>{count}
</div> }
</div>
) }
const mapStateToProps = (state) => ({
item: state.item
})
export default connect(mapStateToProps , { getItems, getplus }) (Menu);
itemAction.js
export const getItems = () =>{
return {
type: GET_ITEMS
} }
export const getplus = () => {
return {
type: PLUS_ITEMS
} }
Reducer.js
const initialState = {
item: [
{
example:"example1"
},
{
example:"example2"
},
{
example:"example3"
},
],
count:0
}
export default function (state = initialState, action) {
switch(action.type){
case GET_ITEMS:
return {
...state
}
case PLUS_ITEMS:
return {
...state,
count:state.count + 1
}
default:
return state;
}
}
I see two errors in your code, replace this:
const {item, count} = this.props.item
with this:
const {item, count} = this.props
And map also count to your props:
const mapStateToProps = (state) => ({
item: state.item
count: state.count
})
Summary
In order to learn Redux, I am incorporating some state, actions, reducers, and trying to see how they are used in React Components.
I have set up a test object...
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
...and aim to:
increment the navigationCount by 1 when visiting pages
add or subtract from someNumber
push() & pop() elements from someList.
Versions
Currently using gatsby ^2.5.0, react ^16.8.6, and react-redux ^6.0.1.
Code
actions & reducers
import { combineReducers } from 'redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from './actionTypes.js';
// state
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
// action creators returning actions
export const pageIncrementer = navigationCount => {
return {
type: PAGE_INCREMENT,
navigationCount,
};
};
export const numberAdder = numberToAdd => {
return {
type: NUMBER_INCREASE,
numberToAdd,
};
};
export const numberMinuser = numberToMinus => {
return {
type: NUMBER_DECREASE,
numberToMinus,
};
};
export const listPusher = itemToAdd => {
return {
type: LIST_PUSH,
itemToAdd,
}
};
export const listPopper = () => {
return {
type: LIST_POP,
}
};
// reducers
const pageIncrementReducer = (state = initialState, action) => {
switch (action.type) {
case PAGE_INCREMENT:
return Object.assign({}, ...state, {
navigationCount: action.navigationCount+1
});
default:
return state.navigationCount;
}
};
const numberChanger = (state = initialState, action) => {
switch (action.type) {
case NUMBER_INCREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber+action.numberToAdd,
});
case NUMBER_DECREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber-action.numberToMinus,
});
default:
return state.someNumber;
};
};
const listChanger = (state = initialState, action) => {
switch (action.type) {
case LIST_POP:
return Object.assign({}, ...state, {
someList: state.someList.pop(),
});
case LIST_PUSH:
return Object.assign({}, ...state, {
someList: state.someList.push(action.itemToAdd),
});
default:
return state.someList;
}
}
// store
const rootReducer = combineReducers({
pageIncrementReducer,
numberChanger,
listChanger,
});
export default rootReducer;
React Component
import React from 'react';
import Layout from '../components/common/Layout.jsx';
import LandingBanner from '../components/landing/LandingBanner.jsx';
import LandingNavgrid from '../components/landing/LandingNavgrid.jsx';
import LandingApp from '../components/landing/LandingApp.jsx';
import { connect } from 'react-redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from '../state/actionTypes';
class LandingPage extends React.Component {
constructor(props){
super(props);
this.state = {
appliedNum: 2000,
};
}
componentDidMount(){
// this.props.pageIncrement(); // => numberChanger returned undefined
// this.props.numberIncrease(4444); // => pageIncrementReducer returned undefined
// this.props.numberDecrease(4444); // => pageIncrementReducer returned undefined
// this.props.listPush(4444); // => pageIncrementReducer returned undefined
this.props.listPop();
}
render(){
return (
<Layout>
<LandingBanner/>
<LandingNavgrid/>
<LandingApp/>
</Layout>
)
}
}
const filterNumbers = (list=[]) => {
console.log('filterNumbers list: ', list);
return list.filter(listElement => !!Number(listElement));
};
const mapStateToProps = (state, ownProps) => {
return {
someNumber: state.someNumber,
someList: filterNumbers(state.someList),
navigationCount: state.navigationCount,
};
};
const mapDispatchToProps = (dispatch) => {
return {
pageIncrement: () => dispatch({ type: PAGE_INCREMENT }),
numberIncrease: () => dispatch({ type: NUMBER_INCREASE }),
numberDecrease: () => dispatch({ type: NUMBER_DECREASE }),
listPush: () => dispatch({ type: LIST_PUSH }),
listPop: () => dispatch({ type: LIST_POP }),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LandingPage);
Errors
redux.js:449 Uncaught Error: Given action "LIST_POP", reducer
"pageIncrementReducer" returned undefined. To ignore an action, you
must explicitly return the previous state. If you want this reducer to
hold no value, you can return null instead of undefined.
first of all, you always need to return state on the default switch case.
default:
return state;
I am using multiple reducers in my project and then combining them with combineReducers() function and have all actions in single file. when i dispatch the action, it is returning me state values to undefined. I think It can't find out because of multiple reducerse. But when i use single reducer file. It is working fine. Can anyone please tell me what the issue.It is how i am combining the reducers.
const rootReducer = combineReducers({
isMobileReducer,
imageSliderReducer
})
and now passing to store, like below:
let store = createStore(rootReducer,applyMiddleware(thunk))
and in frontend how i am accessing state
const mapStateToProps = (state) => ({
images: state.images,
isMobile: state && state.isMobile
})
imageSliderReducer.js
import {
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from '../actions/actionTypes'
const initialState = {
images:[],
error:null
}
const imageSliderReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_BEGIN:
return {...state,error:null}
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
case FETCH_IMAGES_FAILURE:
return {...state,error:action.payload.error,images:[]}
default:
return state
}
}
export default imageSliderReducer;
isMobileReducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false
}
const isMobileReducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
default:
return state
}
}
export default isMobileReducer;
actionCreator.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from './actionTypes'
export function openMenu(isMobile) {
return {
type: OPEN_MENU
}
}
export function closeMenu(isMobile) {
return {
type: CLOSE_MENU
}
}
export function setDeviceType (isMobile) {
return {
type: SET_DEVICE_TYPE,
isMobile: isMobile
}
}
export function fetchImages() {
return dispatch => {
dispatch(fetchImagesBegin());
return fetch("https://7344.rio.com/wp-json/customapi/homeslider")
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
.catch(error => dispatch(fetchImagesFailure(error)));
};
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchImagesBegin = () => ({
type: FETCH_IMAGES_BEGIN
});
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
export const fetchImagesFailure = error => ({
type: FETCH_IMAGES_FAILURE,
payload: { error }
});
Try using this:
const mapStateToProps = (state) => ({
images: state.imageSliderReducer.images,
isMobile: state.isMobileReducer.isMobile
})