Convert React Context API Class to function of Hook - javascript

How to change this class base Context API to reach Hook without changing other components that already consumed it? I am new to react and spend all night and I got stuck.
The actual code is more than this but I'm trying to remove some of the code for simplicity purposes:
import React, { createContext, Component } from 'react'
const MainContext = createContext();
class MainContextProvider extends Component {
state = {
isLogin : false,
loginData : [],
spinner : false
}
handleUserLogin = (res) => {
this.setState({
...this.state,
isLogin : res.isLogin,
loginData : res.data
})
}
showSpinner = (status) => {
this.setState({
...this.state,
spinner : status
})
}
render() {
console.log(this.state)
return (
<MainContext.Provider value = {{
...this.state,
showSpinner : this.showSpinner,
handleUserLogin : this.handleUserLogin,
}}>
{this.props.children}
</MainContext.Provider>
);
}
}
const MainContextConsumer = MainContext.Consumer;
export {MainContextProvider, MainContextConsumer, MainContext};
I wrap index.js with this MainContextProvider so all components can consume the states or use the methods.

Here is how to use context with hooks and keeping the same API as what you already have:
import React, { createContext, useContext, useState } from "react";
import "./style.css";
// Define your context, this is the same
const MainContext = createContext();
function Provider({ children }) {
// Define some state to hold the data
let [state, setState] = useState({
isLogin: false,
loginData: [],
spinner: false
});
// Define a few functions that change the state
let handleUserLogin = res => {
setState(s => ({
...s,
isLogin: res.isLogin,
loginData: res.data
}));
};
// Define a few functions that change the state
let showSpinner = status => {
setState(s => ({ ...s, spinner: status }));
};
// Pass the `state` and `functions` to the context value
return (
<MainContext.Provider
value={{ ...state, handleUserLogin, showSpinner }}
>
{children}
</MainContext.Provider>
);
}
function Stuff() {
// Inside your component use the context with `useContext` hook
let { showSpinner, handleUserLogin, ...state } = useContext(MainContext);
return (
<div>
<div>
<code>{JSON.stringify(state, null, 2)}</code>
</div>
<button onClick={() => showSpinner(Math.random())}>
Show Spinner
</button>
</div>
);
}
export default function App() {
return (
<Provider>
<Stuff />
</Provider>
);
}
See the demo on StackBlitz

As Sam R. suggestion, I make little modification and works as expected. Maybe it's better to use Reducer but I prefer not. And I think Context API is more simple compare to Redux.
MainContext.js :
import React, { createContext, useState } from 'react'
const MainContext = createContext();
const MainContextProvider = ({ children }) => {
// Define some state to hold the data
let [state, setState] = useState({
isLogin: false,
loginData: [],
spinner: false
});
// Define a few functions that change the state
let handleUserLogin = res => {
setState(s => ({
...s,
isLogin: res.isLogin,
loginData: res.data
}));
};
// Define a few functions that change the state
let showSpinner = status => {
setState(s => ({ ...s, spinner: status }));
};
// Pass the `state` and `functions` to the context value
return (
<MainContext.Provider
value={{ ...state, handleUserLogin, showSpinner }}
>
{children}
</MainContext.Provider>
);
}
const MainContextConsumer = MainContext.Consumer;
export {MainContextProvider, MainContextConsumer, MainContext};
Login.js :
import React, { useState, useContext } from "react";
import { Link } from "react-router-dom";
import { useHistory } from "react-router-dom";
import {MainContext} from "../contextApi/MainContext";
import { login } from "../api/Api_User";
const Login = () => {
const history = useHistory();
const { handleUserLogin, showSpinner } = useContext(MainContext);
const [user , setUser] = useState({ email : "", password : "" })
const [errors , setErrors] = useState({ emailErr : "", passErr : "" })
const handleChange = e => {
const {name , value} = e.target
setUser( prevState => ({ ...prevState,[name] : value }))
setErrors({ emailErr : "", passErr : "" });
}
const handleSubmit = (e) => {
// client side validation
if(!user.email) { setErrors({ emailErr : "Please enter email" }); return false; }
if(!user.password) { setErrors({ passErr : "Please enter password" }); return false; }
showSpinner(true)
const data = {
email: user.email,
password: user.password
}
// axios call
login(data).then(res => {
setTimeout(() => {
showSpinner(false)
if (res) {
if (res.status === true) {
localStorage.setItem("token", res.token); // jwt token from server
handleUserLogin(res) // store server respond to global states
return history.push('/dashboard')
}
// server side validation
if (res.status === false) {
res.path === 'email' && setErrors({ emailErr : res.message })
res.path === 'password' && setErrors({ passErr : res.message })
}
}
},100 )
});
}
return (
<div className="page">
<div className="page-content mt-5 mb-5">
<div className="content-sticky-footer">
<div className="container">
<div className="row">
<div className="col">
<div className="card mb-0">
<div className="card-header">
<h3 className="mx-auto mt-4">LOGIN MEMBER</h3>
</div>
<div className="card-body">
<div className="form-group">
<label>Email address *</label>
<input
type="email" className="form-control"
name="email"
value={user.email}
onChange={handleChange}
/>
<span className="text-danger label-sm ">
{errors.emailErr}
</span>
</div>
<div className="form-group">
<label>Password *</label>
<input
type="password" className="form-control"
name="password"
value={user.password}
onChange={handleChange}
/>
<span className="text-danger label-sm ">
{errors.passErr}
</span>
</div>
<div className="form-footer mt-2">
<button
type="button"
className="btn btn-primary btn-block btn-lg btn-submit"
onClick={handleSubmit}
>
Login
</button>
</div>
<div className="text-center mt-3 text-dark">
Do not have account?
<Link to="/register"> Register</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Login
Index.js :
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './css/App.css';
import App from './App';
import { BrowserRouter} from "react-router-dom";
import {MainContextProvider} from './contextApi/MainContext';
import axios from "axios";
// express server with mongodb
axios.defaults.baseURL = "http://localhost:3001";
ReactDOM.render(
<MainContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</MainContextProvider>,
document.getElementById('root')
);

Related

useReducer returns undefined

-Hello guys , I've encountered a problem which taken a lot time in research but I did not get a solution:
->here I have 2 separate components header.jsx(contains booking.com homepage clone) and hotel.jsx and I want to send a state containing search data from header to hotel using context and use reducer . I've declared a separate context file searchcontext.js ,exported the context and its provider , wrapped the app using the provider in index.js , and I've called the dispatch function in header.js in order to send the needed state , when I call useContext in hotel.js to get the state the console return undefined ,any help please :)
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { SearchContextProvider } from './context/searchcontext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<SearchContextProvider>
<App />
</SearchContextProvider>
</React.StrictMode>
);
searchcontext.js
import { createContext, useReducer } from "react";
const INITIAL_STATE = {
city: undefined,
dates: [],
options: {
adult: undefined,
children: undefined,
room: undefined,
},
};
export const SearchContext = createContext(INITIAL_STATE);
const searchReducer = (action, state) => {
switch (action.type) {
case "NEW_SEARCH":
return action.payload;
case "RESET_SEARCH":
return INITIAL_STATE;
default:
return state;
}
};
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(searchReducer, INITIAL_STATE);
return (
<SearchContext.Provider
value={{
city: state.city,
dates: state.dates,
options: state.options,
dispatch,
}}
>
{children}
</SearchContext.Provider>
);
};
header.jsx file
import "./header.css";
import { DateRange } from "react-date-range";
import { useContext, useState } from "react";
import "react-date-range/dist/styles.css"; // main css file
import "react-date-range/dist/theme/default.css"; // theme css file
import { format } from "date-fns";
import { useNavigate } from "react-router-dom";
import {SearchContext} from "../../context/searchcontext";
const Header = ({ type }) => {
const [destination, setDestination] = useState("");
const [openDate, setOpenDate] = useState(false);
const [dates, setDates] = useState([
{
startDate: new Date(),
endDate: new Date(),
key: "selection",
},
]);
const [openOptions, setOpenOptions] = useState(false);
const [options, setOptions] = useState({
adult: 1,
children: 0,
room: 1,
});
const navigate = useNavigate();
const handleOption = (name, operation) => {
setOptions((prev) => {
return {
...prev,
[name]: operation === "i" ? options[name] + 1 : options[name] - 1,
};
});
};
const{dispatch}= useContext(SearchContext);
const handleSearch = () => {
dispatch({type:"NEW_SEARCH",payload:{destination,dates,options}});
console.log(true)
navigate("/hotels", { state: { destination, dates, options } });
};
return (
<div className="header">
<div className="headerContainer" >
<div className="headerSearch">
<div className="headerSearchItem">
<input
type="text"
placeholder="where are you going?"
className="headerSearchInput"
onChange={(e) => setDestination(e.target.value)}
/>
</div>
<div className="headerSearchItem">
<span
onClick={() => setOpenDate(!openDate)}
className="headerSearchText"
>
{`${format(dates[0].startDate, "dd/MM/yyyy")} to ${format(
dates[0].endDate,
"dd/MM/yyyy"
)}`}
</span>
{openDate && (
<DateRange
editableDateInputs={true}
onChange={(item) => setDates([item.selection])}
moveRangeOnFirstSelection={false}
ranges={dates}
minDate={new Date()}
className="date"
/>
)}
</div>
<div className="headerSearchItem">
<span
onClick={() => setOpenOptions(!openOptions)}
className="headerSearchText">
{`${options.adult}`} adult {`${options.children}`} children
{`${options.room}`} room
</span>
{openOptions && (
<div className="options">
<div className="option">
<span className="optionTitle">Adults</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("adult", "d");
}}
disabled={options.adult <= 1}
>
-
</button>
<span>{options.adult}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("adult", "i");
}}
>
+
</button>
</div>
</div>
<div className="option">
<span className="optionTitle">Children</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("children", "d");
}}
disabled={options.children <= 0}
>
-
</button>
<span>{options.children}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("children", "i");
}}
>
+
</button>
</div>
</div>
<div className="option">
<span className="optionTitle">Rooms</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("room", "d");
}}
disabled={options.room <= 1}
>
-
</button>
<span>{options.room}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("room", "i");
}}
>
+
</button>
</div>
</div>
</div>
)}
</div>
<div className="headerSearchItem">
<button className="headerBtn" onClick={handleSearch}>
Search
</button>
</div>
</div>
</>
)}
</div>
</div>
);
};
export default Header;
hotel.jsx file
import "./hotel.css";
import { useState } from "react";
import { useContext } from "react";
import { SearchContext } from "../../context/searchcontext";
const Hotel = () => {
const [sliderNumber, setSliderNumber] = useState(0);
const [openSlider, setOpenSlider] = useState(false);
//here where the problem encouters
const {state}= useContext(SearchContext);
console.log(state);//returns undefined
return (
<div style={{ margin: -8 }}>
<div className="hotelItemContainer">
<div className="hotelItemWrapper">
<button className="hotelBookBtn"> Reserve or Book Now!</button>
<h1 className="hotelTitle">lorem</h1>
<span className="hotelSpecFeatures">
You're eligible for a Genius discount! , to save at this property
, all you have to do is sign in.
</span>
</div>
</div>
</div>
);
};
export default Hotel;
In your hotel.js you are expecting a state property. However you have not been exposing one in your search context
searchcontext.js
Your context was not exposing a state
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(searchReducer, INITIAL_STATE);
return (
<SearchContext.Provider
value={{
city: state.city,
dates: state.dates,
options: state.options,
state, // you forgot to add this
dispatch,
}}
>
{children}
</SearchContext.Provider>
);
};
alternatively don't use the state property in hotel.js instead use the destructure values that are exposed in your context.

Cannot destructure property as it is undefined. useContext error

I dont understand why does the below error keeps happening, so I could really use some help!
Error:
Login.jsx:10 Uncaught TypeError: Cannot destructure property 'user' of '(0 , react__WEBPACK_IMPORTED_MODULE_1__.useContext)(...)' as it is undefined.
at Login (Login.jsx:10:1)
Context.js:
import { createContext, useEffect, useReducer } from "react";
import Reducer from "./Reducer";
const INITIAL_STATE = {
user: null,
isFetching: false,
error: false,
};
export const Context = createContext(INITIAL_STATE);
export const ContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, INITIAL_STATE);
return (
<Context.Provider
value = {{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</Context.Provider>
);
};
Login.js:
import axios from 'axios';
import { useContext, useRef } from 'react';
import { Link } from 'react-router-dom';
import { Context } from '../../context/Context';
import './login.css';
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { user, dispatch, isFetching } = useContext(Context); //error
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: 'LOGIN_START' });
try {
const res = await axios.post(`http://localhost:5000/api/auth/login`, {
username: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: 'LOGIN_SUCCESS', payload: res.data });
} catch (err) {
dispatch({ type: 'LOGIN_FAILURE' });
console.log(err);
}
};
console.log(user);
return (
<div className='login'>
<span className='loginTitle'>Login</span>
<form className='loginForm' onSubmit={handleSubmit}>
<label>Username</label>
<input
type='text'
className='loginInput'
placeholder='Enter your username...'
ref={userRef}
/>
<label>Password</label>
<input
type='password'
className='loginInput'
placeholder='Enter your password...'
ref={passwordRef}
/>
<button className='loginButton' type='submit' disabled={isFetching}>
Login
</button>
</form>
<button className='loginRegisterButton'>
<Link className='link' to='/register'>
Register
</Link>
</button>
</div>
);
}
Did you wrap the parent of the Login component with the ContextProvider?
import { ContextProvider } from '../../context/Context';
import Login from "..."
function App() {
return(
<>
<ContextProvider>
<Login>
//any other child over here can access the context from this provider
</ContextProvider>
</>
)
}
export default App;

How to resolve Unhandled Rejection (TypeError): cyclic object value?

This block of code:
const data = {
user_id: userID,
comment: comment
};
const url = `http://localhost:5000/blog/comments${category}/${slug}`;
const headers = { "Access-Control-Allow-Origin": "*" };
axios.post(url, data, headers).then(resp => {
console.log(resp.data.message);
});
}
is giving me this error:
Unhandled Rejection (TypeError): cyclic object value
from what I've gathered so far "cyclic object value" refers to javascript objects that at some point refer to itself hence throws an error. From the code above it's not very obvious how I accomplished such error.
I would appreciated any pointers here.
Attempt to provide a minimal example:
Post.jsx:
import React, { useState, useEffect } from "react";
import { useGlobalContext } from "../context";
import { Link } from "react-router-dom";
import AddComment from "./AddComment";
import axios from "axios";
const Post = props => {
const { userID, trace, setTrace, isAuthenticated } = useGlobalContext();
const [obj, setObj] = useState([]);
const [comments, setComments] = useState([]);
const [comment, setComment] = useState("");
const [showLoginWarning, setShowLoginWarning] = useState(false);
const { firstColumn, secondColumn, slug, category } = props;
const handleComment = e => {
e.preventDefault();
if (!isAuthenticated) {
setShowLoginWarning(true);
} else {
const data = {
user_id: userID,
comment: comment
};
const url = `http://localhost:5000/blog/comments${category}/${slug}`;
const headers = { "Access-Control-Allow-Origin": "*" };
axios
.post(url, data, headers)
.then(resp => {
console.log(resp.data.message);
})
.catch(err => {
console.log(err);
});
}
};
useEffect(() => {
setTrace(category + `/${slug}`);
//console.log(trace);
let obj = firstColumn.filter(post => post.slug === slug);
if (obj.length === 0) {
obj = secondColumn.filter(post => post.slug === slug);
}
setObj(obj);
const headers = { "Access-Control-Allow-Origin": "*" };
const url = `http://localhost:5000/blog/comments${trace}/${slug}`;
axios
.get(url)
.then(resp => {
setComments(resp.data.message);
console.log(resp.data.message);
})
.catch(err => {
console.log(err);
});
}, []);
useEffect(() => {
const timeout = setTimeout(() => {
setShowLoginWarning(false);
}, 7000);
}, [showLoginWarning]);
const img_file = `http://localhost:5000/blog/fetch_image/post/${slug}`;
return (
<div style={{ marginTop: "40px" }} className="container">
<div className="columns">
<div className="column is-four-fifths">
<div className="content">
{obj.map(prop => (
<div key={prop.id}>
<h4 className="title is-4">
<img src={img_file} alt={prop.title} />
<br />
{prop.title}
<small>{prop.timestamp}</small>
</h4>
<br />
<p>{prop.post}</p>
</div>
))}
</div>
</div>
<div className="column">
<br />
<Link to={category} className="button is-link" id="backbtn">
Back
</Link>
</div>
</div>
<section className="section">
<h1 className="title">Section</h1>
<h2 className="subtitle">
A simple container to divide your page into <strong>sections</strong>,
like the one you're currently reading.
</h2>
<AddComment
onComment={handleComment}
showLoginWarning={showLoginWarning}
onChange={setComment}
/>
</section>
</div>
);
};
export default Post;
AddComment.jsx:
import React from "react";
const AddComment = props => {
const { showLoginWarning, onComment, onChange } = props;
return (
<>
{showLoginWarning && (
<div className="notification is-warning">
<strong>You need to be logged in to comment.</strong>
</div>
)}
<section id="textareaSection" className="section">
<form onSubmit={onComment}>
<div className="field">
<div className="control">
<textarea
className="textarea"
cols="2"
rows="2"
placeholder="What's On Your Mind ?"
onChange={onChange}
/>
<button
type="submit"
style={{ float: "right", marginTop: "1em" }}
className="button is-link"
>
Post
</button>
</div>
</div>
</form>
</section>
</>
);
};
export default AddComment;
index.js:
import React from "react";
import ReactDOM from "react-dom";
import "./assets/bulma/css/bulma.css";
import "./assets/stylesheet.css";
import Post from "./Post";
import reportWebVitals from "./reportWebVitals";
import { AppProvider } from "./context";
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<Post />
</AppProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();

How can i redirect after successful submit of form using react-redux

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.

Error: Actions must be plain objects. Use custom middleware for async actions. React-redux error

index.js
import store from "./store";
import { createStore } from 'redux';
import reducers from './reducers';
import { Provider } from 'react-redux';
console.log('init state', store.getState());
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
action.js
import * as types from "./types";
import axios from 'axios';
import { incrementProgress, decrementProgress } from "./progress";
const userLogin = username => ({
type: types.AUTH_LOGIN,
username,
});
const userLogout = () => ({
type: types.AUTH_LOGOUT,
});
const fakeLoginRequest = (username, password) => {
alert(username, password)
}
export const doLogin = username => {
alert(username)
};
export const doLogout = () => dispatch => {
dispatch(userLogout());
};
LoginForm.jsx - Dumb Component
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
};
this.handleClick = this.handleClick.bind(this);
}
fakeLogin = e => {
const { username } = this.state;
e.preventDefault();
if (!username) {
return alert("Provide a username.");
}
this.props.doLogin(username);
this.setState({ username: "" });
};
render() {
return (
<div>
<MuiThemeProvider>
<div>
<TextField
hintText="Enter your Username"
floatingLabelText="Username"
onChange = {(event,newValue) => this.setState({username:newValue})}
/>
<br/>
<TextField
type="password"
hintText="Enter your Password"
floatingLabelText="Password"
onChange = {(event,newValue) => this.setState({password:newValue})}
/>
<br/>
<RaisedButton label="Submit" primary={true} style={style} onClick={this.fakeLogin}/>
</div>
</MuiThemeProvider>
</div>
);
}
}
export default LoginForm;
LoginPage.jsx - Wise Component
const LoginPage = ({ auth, doLogin, doLogout }) => (
<div>
<NavBar/>
<div className="row">
<div style={{display: 'flex', justifyContent: 'center'}} className="col-md-4 col-md-offset-4">
<LoginForm doLogin={doLogin} doLogout={doLogout} auth={auth} />
</div>
</div>
</div>
);
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { doLogin, doLogout })(LoginPage);
I am totally new to react-redux and I am trying to create a flow that recognizes the current login status of the user. What I am trying to with the codes above is that I wanted to make sure that the username and password pass correctly and alerts them.
However, I get the error
Error: Actions must be plain objects. Use custom middleware for async actions. React-redux error.
On this.props.doLogin(username); of the LoginForm.jsx. It seems like it's also related to the onClick of the button. I don't understand because the function that I am calling is not async.
What am I doing wrongly?
Like the error says, your action creators have to return an object.
export const doLogin = username => {
console.log('READ THE DOCS. IT IS VERY HELPFUL TO READ THE DOCS.');
console.log(username);
return {
type: 'YOUR_ACTION_TYPE',
payload: { whatever: 'you', want: 'it', to: 'be' }
};
};

Categories