I'm building a react app using redux for state management.
Inside the SearchField component I am using useReducer for handle the search field inputs (cityField and countryField).
When i submit the fetchData function runs and sends a request to an API.
the SearchField component code:
import React, { useReducer } from "react";
import { connect } from 'react-redux';
import { SET_CITY_FIELD, SET_COUNTRY_FIELD } from '../../redux/search-field/search-field.types';
import { reducer, INITIAL_STATE } from "../../redux/search-field/search-field.reducer";
import { fetchData } from '../../redux/weather-api-data/data.actions';
import { SearchFieldContainer, SearchInput, OptionalField, FormComponent } from './search-field.styles';
const SearchField = () => {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
const { cityField, countryField } = state;
const onFormSubmit = e => {
e.preventDefault()
cityField.length < 1 ?
alert('Please insert a city')
:
fetchData(cityField, countryField);
}
return (
<SearchFieldContainer>
<FormComponent onSubmit={onFormSubmit}>
<SearchInput type="search" placeholder="Search City" aria-label="Search"
onChange={event => dispatch({ type: SET_CITY_FIELD, payload: event.target.value })}
/>
</FormComponent>
<FormComponent className='country-form' onSubmit={onFormSubmit}>
<SearchInput className='country' type="search" placeholder="Country" aria-label="Search"
onChange={event => dispatch({ type: SET_COUNTRY_FIELD, payload: event.target.value })}
/>
<OptionalField>OPTIONAL</OptionalField>
</FormComponent>
</SearchFieldContainer>
)
}
const mapDispatchToProps = dispatch => ({
fetchData: (cityField, countryField) => dispatch(fetchData(cityField, countryField))
})
export default connect(null, mapDispatchToProps)(SearchField);
The problem is that when the OnFormSubmit function is called, the fetchData function does not send any request and there is no errors in the console. The searchField and countryField data are stored correctly (or at least if I show them in the console I get the actual values). The fetchData function was previously located in the App and worked correctly.
I thank in advance anyone who gives me an answer and tries to find the solution.
The fetchData code:
export const fetchData = (cityField, countryField) => {
return (dispatch) => {
dispatch(fetchCurrentDataRequest())
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityField},${countryField}&units=metric&appid=MY_API_KEY`)
.then(response => {
const currentData = response.data
dispatch(fetchCurrentDataSuccess(currentData));
const { lat, lon } = currentData.coord
dispatch(fetchDailyDataRequest())
axios.get(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&units=metric&exclude=current,minutely,hourly,alerts&appid=MY_API_KEY`)
.then(response => {
const dailyData = response.data.daily
dispatch(fetchDailyDataSuccess(dailyData))
})
.catch(error => {
const errorMessage = error.message
dispatch(fetchCurrentDataFailure(errorMessage))
})
})
.catch(error => {
const errorMessage = error.message
alert('No results found')
dispatch(fetchCurrentDataFailure(errorMessage))
})
}
I resolved using useDispatch hook instead of mapdispatchToProps, so the new code is:
import React, { useReducer } from "react";
import { useDispatch } from 'react-redux';
import { fetchData } from '../../redux/weather-api-data/data.actions';
import { reducer, INITIAL_STATE } from '../../redux/search-field/search-field.reducer';
import { SET_CITY_FIELD, SET_COUNTRY_FIELD } from '../../redux/search-field/search-field.types';
import { SearchFieldContainer, SearchInput, OptionalField, FormComponent } from './search-field.styles';
const SearchField = () => {
const dispatchData = useDispatch();
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
const { cityField, countryField } = state;
const onFormSubmit = e => {
e.preventDefault()
cityField.length < 1 ?
alert('Please insert a city')
:
dispatchData(fetchData(cityField, countryField));
}
return (
<SearchFieldContainer>
<FormComponent onSubmit={onFormSubmit}>
<SearchInput type="search" placeholder="Search City" aria-label="Search"
onChange={event => dispatch({ type: SET_CITY_FIELD, payload: event.target.value })}
/>
</FormComponent>
<FormComponent className='country-form' onSubmit={onFormSubmit}>
<SearchInput className='country' type="search" placeholder="Country" aria-label="Search"
onChange={event => dispatch({ type: SET_COUNTRY_FIELD, payload: event.target.value })}
/>
<OptionalField>OPTIONAL</OptionalField>
</FormComponent>
</SearchFieldContainer>
)
}
export default SearchField;
Related
I am building a simple CRUD app, and I have 90% of it working but for some reason I can't get the "edit" functionality to work. When I go to edit my team members name, nothing happens when I click "Edit Name". Worth noting, I copied the code from my "add user" component and modified were needed. Here is my code:
// EDIT USER
import React, { useState, useContext, useEffect } from 'react'
import { GlobalContext } from '../context/GlobalState'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { Form, FormGroup, Label, Input, Button } from 'reactstrap'
const EditUser = (props) => {
const [selectedUser, setSelectedUser] = useState({
id: '',
name: ''
});
const { users, editUser } = useContext(GlobalContext);
const navigate = useNavigate();
const currentUserId = useParams(props);
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
const onSubmit = (e) => {
e.preventDefault()
editUser(selectedUser)
navigate('/');
}
const onChange = (e) => {
setSelectedUser({...selectedUser, [e.target.name]: e.target.value})
}
return (
<div className='container w-25'>
<Form className='form-control' onSubmit={onSubmit}>
<FormGroup>
<Label>Edit Name</Label>
<Input type='text' name='name' value={selectedUser.name} onChange={onChange} placeholder='Enter Name'></Input>
</FormGroup>
<Button type='submit'>Edit Name</Button>
<Link to='/' className='btn btn-danger m-2'>Back</Link>
</Form>
</div>
)
}
export default EditUser
// ADD USER
import React, { useState, useContext } from 'react'
import { GlobalContext } from '../context/GlobalState'
import { Link, useNavigate } from 'react-router-dom'
import { v4 as uuid } from 'uuid';
import { Form, FormGroup, Label, Input, Button } from 'reactstrap'
const AddUser = () => {
const [name, setName] = useState('');
const { addUser } = useContext(GlobalContext);
const navigate = useNavigate();
const onSubmit = (e) => {
e.preventDefault()
const newUser = {
id: uuid(),
name: name
}
addUser(newUser);
navigate('/');
}
const onChange = (e) => {
setName(e.target.value);
}
return (
<div className='container w-25'>
<Form className='form-control' onSubmit={onSubmit}>
<FormGroup>
<Label>Name</Label>
<Input
type='text'
name={name}
value={name}
onChange={onChange}
placeholder='Enter Name'></Input>
</FormGroup>
<Button type='submit'>Submit</Button>
<Link to='/' className='btn btn-danger m-2'>Back</Link>
</Form>
</div>
)
}
export default AddUser
// GLOBAL CONTEXT
import React, { createContext, useReducer } from 'react';
import AppReducer from './AppReducer';
const initialState = {
users: []
};
// Create Context \\
export const GlobalContext = createContext(initialState);
// Provider Component \\
export const GlobalProvider = ({children}) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
// Actions
const removeUser = (id) => {
dispatch({
type: 'REMOVE_USER',
payload: id
});
}
const addUser = (user) => {
dispatch({
type: 'ADD_USER',
payload: user
});
}
const editUser = (user) => {
dispatch({
type: "EDIT_USER",
payload: user
});
}
return(
<GlobalContext.Provider value={{
users: state.users,
removeUser,
addUser,
editUser
}}>
{children}
</GlobalContext.Provider>
)
}
// APP REDUCER
export default (state, action) => {
switch(action.type) {
case 'REMOVE_USER':
return {
users: state.users.filter(user => {
return user.id !== action.payload
})
}
case 'ADD_USER':
return {
users: [action.payload, ...state.users]
}
case 'EDIT_USER':
const updateUser = action.payload
const updateUsers = state.users.map(user => {
if(user.id === updateUser.id) {
return updateUser;
}
return user;
})
return {
users: updateUsers
}
default:
return state
}
}
Issue
The issue seems to be that the Edit component isn't accessing the route paths params object correctly, to reference the route path's userId param.
const currentUserId = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
Here the entire params object is named currentUserId. In the useEffect hook callback this params object is assigned to a local variable userId and then used to find a matching user object in the users array. It's comparing a specific user's id property (a string type) to the entire params object (an object type). This OFC will never be equal and selectedUser is undefined and the local selectedUser state is never initialized to the user you are trying to edit.
Solution
Either access into the params object to correctly access the specific parameter:
const currentUserId = useParams();
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId.userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [currentUserId, users]);
Or just destructure the userId path param directly:
const { userId } = useParams();
useEffect(() => {
const selectedUser = users.find((user) => user.id === userId);
if (selectedUser) {
setSelectedUser(selectedUser);
}
}, [userId, users]);
Suggestion
Initialize the selectedUser state directly. Use a useEffect hook to check if there is a user to edit and issue a back navigation if there is not one to edit.
const EditUser = () => {
const navigate = useNavigate();
const { userId } = useParams();
const { users, editUser } = useGlobalContext(); // *
const [selectedUser, setSelectedUser] = useState(
users.find((user) => user.id === userId)
);
useEffect(() => {
if (!selectedUser) navigate(-1);
}, [navigate, selectedUser]);
const onSubmit = (e) => {
e.preventDefault();
editUser(selectedUser);
navigate("/");
};
const onChange = (e) => {
setSelectedUser({ ...selectedUser, [e.target.name]: e.target.value });
};
return (
<div className="container w-25">
{selectedUser && (
<Form className="form-control" onSubmit={onSubmit}>
<FormGroup>
<Label>Edit Name</Label>
<Input
type="text"
name="name"
value={selectedUser.name}
onChange={onChange}
placeholder="Enter Name"
/>
</FormGroup>
<Button type="submit">Edit Name</Button>
<Link to="/" className="btn btn-danger m-2">
Back
</Link>
</Form>
)}
</div>
);
};
* Note: This was just a custom hook created for convenience in the context code:
const useGlobalContext = () => useContext(GlobalContext);
So basically I'm trying to create a code that allows me to update the slug with the use of params.
Don't know why My code throws this error.
"TypeError: Cannot read property 'params' of undefined in react".
I tried replacing
useEffect(() => {
loadCategory();
}, []);
with
useEffect(() => {
if(match.params.slug) loadOrders()
}, [match.params.slug])
but it still didn't work.
This is the code I wrote.
import React, { useState, useEffect } from "react";
import {
HistoryContainer,
HistoryBg,
TextContainer2,
TextContainer3,
Text,
CatForm,
FormLabel,
FormControl,
ButtonPrimary,
} from "./CategoryUpdateElements";
import AdminNav from "../AdminNav/index";
import { toast } from "react-toastify";
import { useSelector } from "react-redux";
import { getCategory, updateCategory } from "../../../functions/category";
const CategoryUpdate = ({ history, match }) => {
const { user } = useSelector((state) => ({ ...state }));
const [name, setName] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
loadCategory();
}, []);
const loadCategory = () =>
getCategory(match.params.slug).then((c) => setName(c.data.name));
const handleSubmit = (e) => {
e.preventDefault();
// console.log(name);
setLoading(true);
updateCategory(match.params.slug, { name }, user.token)
.then((res) => {
// console.log(res)
setLoading(false);
setName("");
toast.success(`"${res.data.name}" is updated`);
history.push("/admin/category");
})
.catch((err) => {
console.log(err);
setLoading(false);
if (err.response.status === 400) toast.error(err.response.data);
});
};
return (
<>
<HistoryContainer>
<HistoryBg>
<AdminNav />
<TextContainer2>
<TextContainer3>
{loading ? <Text>Loading..</Text> : <Text>Update category</Text>}
<CatForm onSubmit={handleSubmit}>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
autoFocus
required
/>
<ButtonPrimary>Save</ButtonPrimary>
</CatForm>
</TextContainer3>
</TextContainer2>
</HistoryBg>
</HistoryContainer>
</>
);
};
export default CategoryUpdate;
UPDATE:
To add context to this problem. This code lets me update the name of the slug, but the TypeError doesn't let me follow through with this haha. I was actually following a tutorial regarding this and obviously, his code works. I was sure that I was following it properly as I wrote the code exactly like his but the only difference is my ui.
I also tried console logging match and after checking it out, what I saw was "undefined" which is not surprising.. It should have shown me the slug but instead it gave me "undefined".
This is his code which allows him to update his slug.
import React, { useState, useEffect } from "react";
import AdminNav from "../../../components/nav/AdminNav";
import { toast } from "react-toastify";
import { useSelector } from "react-redux";
import { getCategory, updateCategory } from "../../../functions/category";
const CategoryUpdate = ({ history, match }) => {
const { user } = useSelector((state) => ({ ...state }));
const [name, setName] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
loadCategory();
}, []);
const loadCategory = () =>
getCategory(match.params.slug).then((c) => setName(c.data.name));
const handleSubmit = (e) => {
e.preventDefault();
// console.log(name);
setLoading(true);
updateCategory(match.params.slug, { name }, user.token)
.then((res) => {
// console.log(res)
setLoading(false);
setName("");
toast.success(`"${res.data.name}" is updated`);
history.push("/admin/category");
})
.catch((err) => {
console.log(err);
setLoading(false);
if (err.response.status === 400) toast.error(err.response.data);
});
};
const categoryForm = () => (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Name</label>
<input
type="text"
className="form-control"
onChange={(e) => setName(e.target.value)}
value={name}
autoFocus
required
/>
<br />
<button className="btn btn-outline-primary">Save</button>
</div>
</form>
);
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-2">
<AdminNav />
</div>
<div className="col">
{loading ? (
<h4 className="text-danger">Loading..</h4>
) : (
<h4>Update category</h4>
)}
{categoryForm()}
<hr />
</div>
</div>
</div>
);
};
export default CategoryUpdate;
Still new to coding. Hope you guys can help me with this ^_^
I think your problem with match which is getting as the props. If you are having trouble with handle match props please try
useRouteMatch instaed.
import { useRouteMatch } from "react-router-dom";
function YourComponent() {
let match = useRouteMatch();
// Do whatever you want with the match...
return <div />;
}
I think this is more convinent to use.
For more examples
I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;
action.js
import axios from 'axios';
import { EVENT_ADD_FAIL, EVENT_ADD_REQUEST, EVENT_ADD_SUCCESS } from '../constraints/eventConstraint';
const addEvent = (event) => async (dispatch) => {
dispatch({ type: EVENT_ADD_REQUEST, payload: event });
try {
const { data } = await axios.post(`http://localhost:4000/event`, event);
dispatch({ type: EVENT_ADD_SUCCESS, payload:data });
}
catch (error) {
dispatch({ type: EVENT_ADD_FAIL, payload:error.message });
};
};
export { addEvent };
constraint.js
export const EVENT_ADD_REQUEST = 'EVENT_ADD_REQUEST';
export const EVENT_ADD_SUCCESS = 'EVENT_ADD_SUCCESS';
export const EVENT_ADD_FAIL = 'EVENT_ADD_FAIL';
reducer.js
import {EVENT_ADD_FAIL, EVENT_ADD_REQUEST, EVENT_ADD_SUCCESS } from "../constraints/eventConstraint";
function eventAddReducer(state = {}, action) {
switch(action.type) {
case EVENT_ADD_REQUEST:
return { loading: true };
case EVENT_ADD_SUCCESS:
return { loading: false, event: action.payload, success:true };
case EVENT_ADD_FAIL:
return { loading: false, error: action.payload, success:false };
default:
return state
};
};
export { eventAddReducer }
store.js
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { eventAddReducer } from './reducers/eventReducer';
const initialState = {};
const reducer = combineReducers({
addEvent: eventAddReducer
});
const store = createStore(reducer, initialState, compose(applyMiddleware(thunk)));
export default store
event.js
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { addEvent } from '../actions/eventAction';
const AddEvent = () => {
const history = useHistory();
const [event, setEvent] = useState();
const addNewEvent = useSelector(state => state.addEvent);
console.log(addNewEvent)
const dispatch = useDispatch();
const handleChange = e => {
setEvent({ ...event,[e.target.name]:e.target.value})
};
const submitHandler = async (e) => {
e.preventDefault();
await dispatch(addEvent(event));
};
// if(addNewEvent.success === true) {
// history.push('/')
// }; ===========>>>>>>>>>>> It works at first but after submission first time next time it automatically redirects to '/' because react-redux holds state
return (
<>
<form onSubmit = { submitHandler } >
<div className="form-group">
<label htmlFor="name">Name:</label>
<input type="text" className="form-control" id="name" name="name" onChange={e => handleChange(e)} />
</div>
<div className="form-group">
<label htmlFor="description">Description:</label>
<input type="text" className="form-control" id="description" name="description" onChange={e => handleChange(e)} />
</div>
<div className="form-group">
<label htmlFor="price">Price:</label>
<input type="text" className="form-control" id="price" name="price" onChange={e => handleChange(e)} />
</div>
<Link to='/'> <button type="button" className="btn btn-success"> Back </button> </Link>
<button type="submit" className="btn btn-success float-right"> Add Event </button>
</form>
</>
)
};
export default AddEvent
Everything is working fine but I want after successful submission of the form it needs to redirect to some page. It is simple without react-redux we can simply redirect after submission of form but I am trying to learn redux and don't know much about redux. I tried to use success = true in reducer it works at the first time but as redux holds state when I tried to open the link it automatically redirects to the homepage as success = true is hold by react-redux. Any help will be appreciated
First: Make sure you reset success per action:
function eventAddReducer(state = {}, action) {
switch(action.type) {
case EVENT_ADD_REQUEST:
return {
loading: true,
success: null // <-- Look at this
};
/** ... */
};
};
Second: Connect success store-variable to your component, and check for it in componentDidupdate event like:
import { connect } from 'react-redux';
class AddEvent extends React.Component {
componentDidUpdate(prevProps) {
const {success} = this.props;
const {succcess: prevSuccess} = prevProps;
if (success && success !== prevSuccess) {
/** Redirect here */
}
}
/** .... */
}
const mapStateToProps = ({ addEvent: { success } }) => ({
success
});
export default connect(mapStateToProps)(AddEvent);
Using Hooks
const AddEvent = ({ success }) => {
useEffect(() => {
if (success) {
/** Redirect here */
}
}, [success]); // <-- This will make sure that the effect only runs when success variable has changed
};
const mapStateToProps = ({ addEvent: { success } }) => ({
success
});
export default connect(mapStateToProps)(AddEvent);
I ran into the same problem now, and I solved it in two ways
The first: to complete your solution at
event.js file:
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { addEvent } from '../actions/eventAction';
const AddEvent = () => {
const history = useHistory();
const [event, setEvent] = useState();
const addNewEvent = useSelector(state => state.addEvent);
console.log(addNewEvent)
const dispatch = useDispatch();
const handleChange = e => {
setEvent({ ...event,[e.target.name]:e.target.value})
};
const submitHandler = async (e) => {
e.preventDefault();
await dispatch(addEvent(event));
};
addNewEvent.success && history.push('/')
return (
<>
// after submition success only you will redirect to "/"
{addNewEvent.success && history.push('/')}
<form onSubmit = { submitHandler } >
<div className="form-group">
<label htmlFor="name">Name:</label>
<input type="text" className="form-control" id="name" name="name" onChange={e => handleChange(e)} />
</div>
<div className="form-group">
<label htmlFor="description">Description:</label>
<input type="text" className="form-control" id="description" name="description" onChange={e => handleChange(e)} />
</div>
<div className="form-group">
<label htmlFor="price">Price:</label>
<input type="text" className="form-control" id="price" name="price" onChange={e => handleChange(e)} />
</div>
<Link to='/'> <button type="button" className="btn btn-success"> Back </button> </Link>
<button type="submit" className="btn btn-success float-right"> Add Event </button>
</form>
</>
)
};
export default AddEvent
we can only access success value from store reducer after return not before, so you can access value every re-render and redirect based on your condition
now in react-router-dom v6 you can use useNavigate() and make changes for below lines
import { Link, useNavigate } from "react-router-dom";
// rest of imports
const AddEvent = () => {
const navigate = useNavigate();
//rest of code
return (
<>
{addNewEvent.success && navigate('/')}
//rest of code
</>
)
};
export default AddEvent
The second: you can make condition at action.js by sending navigate as an argument on dispatch action and write your condition after dispatch success as below
event.js file
import { Link, useNavigate } from "react-router-dom";
// rest of imports
const AddEvent = () => {
const navigate = useNavigate();
const submitHandler = async (e) => {
e.preventDefault();
await dispatch(addEvent(event,navigate));
};
//rest of code
return (
<>
//rest of code
</>
)
};
export default AddEvent
and at action.js file
import axios from 'axios';
import { EVENT_ADD_FAIL, EVENT_ADD_REQUEST, EVENT_ADD_SUCCESS } from '../constraints/eventConstraint';
const addEvent = (event,navigate) => async (dispatch) => {
dispatch({ type: EVENT_ADD_REQUEST, payload: event });
try {
const { data } = await axios.post(`http://localhost:4000/event`, event);
dispatch({ type: EVENT_ADD_SUCCESS, payload:data });
//add your navigation or condition here
navigate("/");
}
catch (error) {
dispatch({ type: EVENT_ADD_FAIL, payload:error.message });
};
};
export { addEvent };
I know this not the most ideal solution , but how about creating an action that will reset success and dispatch it inside of an useEffect?
Something like this:
Reducer
import {EVENT_ADD_FAIL, EVENT_ADD_REQUEST, EVENT_ADD_SUCCESS } from "../constraints/eventConstraint";
function eventAddReducer(state = {}, action) {
switch(action.type) {
case EVENT_ADD_REQUEST:
return { loading: true };
case EVENT_ADD_SUCCESS:
return { loading: false, event: action.payload, success:true };
case EVENT_ADD_FAIL:
return { loading: false, error: action.payload, success:false };
case RESET:
return {
...state,
loading: false,
success:false
} // This will reset everything including success
default:
return state
};
};
export { eventAddReducer }
and in your event.js file call an action that will dispatch RESET. Make sure you put it inside of an useeffect.
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { addEvent } from '../actions/eventAction';
const AddEvent = () => {
const history = useHistory();
const [event, setEvent] = useState();
const addNewEvent = useSelector(state => state.addEvent);
console.log(addNewEvent)
const dispatch = useDispatch();
React.useEffect(() =>{
myResetAction()
}, [])
const handleChange = e => {
setEvent({ ...event,[e.target.name]:e.target.value})
};
const submitHandler = async (e) => {
e.preventDefault();
await dispatch(addEvent(event));
};
// if(addNewEvent.success === true) {
// history.push('/')
// }; ===========>>>>>>>>>>> It works at first but after submission first time next time it automatically redirects to '/' because react-redux holds state
return (
<>
<form onSubmit = { submitHandler } >
<div className="form-group">
<label htmlFor="name">Name:</label>
<input type="text" className="form-control" id="name" name="name" onChange={e => handleChange(e)} />
</div>
<div className="form-group">
<label htmlFor="description">Description:</label>
<input type="text" className="form-control" id="description" name="description" onChange={e => handleChange(e)} />
</div>
<div className="form-group">
<label htmlFor="price">Price:</label>
<input type="text" className="form-control" id="price" name="price" onChange={e => handleChange(e)} />
</div>
<Link to='/'> <button type="button" className="btn btn-success"> Back </button> </Link>
<button type="submit" className="btn btn-success float-right"> Add Event </button>
</form>
</>
)
)}
Doing this will help.
Can you explain me what wrong with my mapStateToProps?
I have Login component form like this.
I want to get state after action send data state to store but it returns undefined (console.log(propReducerState.isAuthenticated)). Here is my Login
import React, {Fragment, useState} from 'react';
import {connect} from 'react-redux';
import {Link, withRouter} from 'react-router-dom';
import {LoginAction} from '../../actions/LoginAction';
import PropTypes from 'prop-types';
const Login = (propAction, propReducerState) => {
const [formData, setFormData] = useState({
email:'',
password:'',
isAuthenticated:null
});
const {email, password} = formData;
const onChange = e => setFormData({...formData, [e.target.name]: e.target.value});
const onSubmit = async e => {
e.preventDefault();
propAction.LoginAction({
email,
password
});
console.log(propReducerState.isAuthenticated);
}
return (
<Fragment>
<h1>Sign In</h1>
<div className="message">{formData.isAuthenticated}</div>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-groum'>
<input
type='text'
placeholder='Please enter your Email'
name='email'
value={email}
onChange={e => onChange(e)} />
<input
type='password'
name='password'
value={password}
onChange={e => onChange(e)} />
</div>
<input type='submit' className='btn btn-primary btn-normal' value='Login' />
</form>
</Fragment>
)
};
const stateStoreToProp = state => ({
propReducerState: state.LoginReducer
})
export default connect(
stateStoreToProp,
{LoginAction}
)(Login);
and here is my LoginReducer:
import {
LOGIN_SUCCESS, LOGIN_FAIL
} from '../actions/typeName';
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null
}
const LoginReducer = (state = initialState, action) => {
const {type, payload} = action;
switch(type) {
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token)
return {
...state,
...payload,
isAuthenticated: true,
loading: false
}
case LOGIN_FAIL:
localStorage.removeItem('token')
return {
...state,
token:null,
isAuthenticated: false,
loading: true
}
default:
return {
state
}
}
}
export default LoginReducer;
Thank you for your support.
Update: As I tested with all of your suggestions but no luck. I will add LoginAction for you to check it. Thanks
import axios from 'axios';
import {
LOGIN_SUCCESS, LOGIN_FAIL
} from './typeName';
export const LoginAction = ({email, password}) => async next => {
const config = {
headers: {
'Content-Type': 'application/json'
}
}
const body = JSON.stringify({email, password});
try {
const res = await axios.post('/api/admin/login', body, config);
next({
type: LOGIN_SUCCESS,
payload: res.data
});
}catch(err){
next({
type: LOGIN_FAIL,
});
}
}
This is how you're suppose to connect:
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
loginAction: () => dispatch(LoginAction),
}
}
Please make sure that your connect function look like this :
export default connect(
stateStoreToProp,
mapDispatchToProps
)(Login);
and not like this :
export default connect(
null,
{stateStoreToProp, LoginAction}
)(Login);
That depends on how are you using the reducer
You should have something lik this:
export default combineReducers({
test: testReducer
});
And then
const store = createStore(reducer, {});
Where reducer is the result of combineReducers
Then in mapStateToProps the result state of testReducer will be inside test property
Don't use stringify to convert the state to string, keep it as an object
Your connect() has wrong parameter list. The first has to be stateStoreToProp, as the documentation suggests.
You should export it as the following:
export default connect(
stateStoreToProp, {LoginAction}
)(Login);
Read further here: https://react-redux.js.org/api/connect#connect-parameters
I hope that helps!
I solved this problem. Problem is that I put it in form submit
The stateStore always change then I just put it under form submit and everything work well.
Also with that, the prop should be one only. I defined two props and it is not right way.
const Login = ({LoginAction, propReducerState}) => {
const [formData, setFormData] = useState({
email:'',
password:'',
isAuthenticated:null
});
const {email, password} = formData;
const onChange = e => setFormData({...formData, [e.target.name]: e.target.value});
const onSubmit = async e => {
e.preventDefault();
LoginAction({
email,
password
});
}
console.log(propReducerState);
return (
<Fragment>
<h1>Sign In</h1>
<div className="message">{formData.isAuthenticated}</div>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-groum'>
<input
type='text'
placeholder='Please enter your Email'
name='email'
value={email}
onChange={e => onChange(e)} />
<input
type='password'
name='password'
value={password}
onChange={e => onChange(e)} />
</div>
<input type='submit' className='btn btn-primary btn-normal' value='Login' />
</form>
</Fragment>
)
};
const stateStoreToProp = state => ({
propReducerState: state.LoginReducer
})
export default connect(
stateStoreToProp,
{LoginAction}
)(Login);