Redux Actions must be plain objects error - reactjs

I am developing a frontend application using ReactJS. I haven't used redux before and I am getting an error.
I have the following code:
import { connect } from 'react-redux';
import PharmacistPreregisterComponent from "../components/PharmacistPreregisterComponent";
import { postPreregisteredPharmacist } from "../actions";
const mapDispatchToProps = dispatch => ({
onClick: (email, drugstoreId, alert) => {
dispatch(
postPreregisteredPharmacist(email, drugstoreId, alert)
);
}
});
export default connect (
null,
mapDispatchToProps
)(PharmacistPreregisterComponent)
In PharmacistPreregisterComponent the method:
handleSubmit(event) {
event.preventDefault();
this.onClick(
this.state.email,
this.state.drugstoreId,
this.state.alertMessage);
this.setState({
email: '',
drugstoreId: '',
alertMessage: ''
});
}
And the following action:
const PREREGISTER_PHARMACIST_SAVE_URL = "http://localhost:3000/admin/preregister/add"
export function postPreregisteredPharmacist(email, drugstoreId, alert) {
return dispatch => {
console.log("in action");
return fetch(PREREGISTER_PHARMACIST_SAVE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "email": email, "drugstoreId": drugstoreId})
}).then ( response => {
console.log(response);
}).catch( error => {
console.log(error);
})
}
}
When submitting the form I get Actions must be plain objects. Use custom middleware for async actions. and I can't seem to figure out what the problem is.

As suggested by you in the comments, since you do not wish to update redux state based on the API request you can simply convert you function into a normal function instead of a action
Also consider setting the state to empty only if the API request is successful
import PharmacistPreregisterComponent from "../components/PharmacistPreregisterComponent";
import { postPreregisteredPharmacist } from "../actions";
handleSubmit(event) {
event.preventDefault();
postPreregisteredPharmacist (
this.state.email,
this.state.drugstoreId,
this.state.alertMessage
).then((response) => {
console.log(response);
this.setState({
email: '',
drugstoreId: '',
alertMessage: ''
});
});
}
export default PharmacistPreregisterComponent)
const PREREGISTER_PHARMACIST_SAVE_URL = "http://localhost:3000/admin/preregister/add"
export function postPreregisteredPharmacist(email, drugstoreId, alert) {
return fetch(PREREGISTER_PHARMACIST_SAVE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "email": email, "drugstoreId": drugstoreId})
})
}

Related

how to pass returned fetch data to a reducer using redux saga

I'm doing a fetch request that makes a new user in my database. All of it works and a new user is made/api-key returned.
The problem is that i am unable to pass the received response of my fetch request to my reduces.
I'm wondering if I should call another action as a response to my successful fetch request that triggers a reducer and takes the response of the request as payload.
Or if I am able to pass the response of the fetch request to the reducer instantly.
Here is my SAGA:
import { call, put, takeEvery, takeLatest, delay } from 'redux-saga/effects';
import {REGISTER} from '../redux/actions/loginAPIcall'
function* callAPIregister(){
const json = yield fetch('http://spotlight-api.local/api/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'apptest3',
email: 'apptest3#test.be',
password: '123456789'
}),
})
.then((response) => response.json())
.then(data => {
console.log(data)
})
yield put({type: 'REGISTER_SAGA', payload: json})
}
export function* watchAPIcall(){
yield takeEvery(REGISTER, callAPIregister)
}
and below is my reducer:
import {REGISTER, LOGIN} from '../actions/loginAPIcall'
const initialState = {
apiCalling: false,
occupation: null
}
function addAPIcall(state = initialState, action, payload){
console.log('inside the api reducer')
switch(action.type){
case "REGISTER_SAGA":
console.log('inside register_saga reducer', payload)
return {
apiCalling: true,
occupation: 'REGISTER'
}
case LOGIN:
return {
apiCalling: true,
occupation: 'LOGIN'
}
default:
return state;
}
}
export default addAPIcall
when loggin the reducer payload now it says undefined.
yield by itself will wait until Promise is resolved if Promise will be returned from the yielded statement. So correct callAPIregister will be
function* callAPIregister(){
// yield will wait for Promise to resolve
const response = yield fetch('http://spotlight-api.local/api/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'apptest3',
email: 'apptest3#test.be',
password: '123456789'
}),
})
// Again yield will wait for Promise to resolve
const data = yield response.json()
console.log(data)
yield put({type: 'REGISTER_SAGA', payload: data})
}
And also I recommend to consider using call in yield statements. It is for easier unit testing
In my opinion, this thing will work for you. Made 'FETCH_FAILED' type well if there's any error in fetching then you can catch that error. So, make one more variable in your reducers initial_state object.
sagas.js
import { call, put, takeLatest, takeEvery } from 'redux-saga/effects';
import {REGISTER} from '../redux/actions/loginAPIcall';
function getData(payload){
return fetch('http://spotlight-api.local/api/register', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
.then(response => response.json())
.then(json => json)
.catch(error => {
throw error;
});
}
function* callAPIregister(){
try{
const payload = {
name: 'apptest3',
email: 'apptest3#test.be',
password: '123456789'
}
const response = yield call(getData, payload);
//In this please check what is the name of your data variable
//Eg if its message then you can
console.log(response);
//use response: response.message
yield put({type: 'REGISTER_SAGA', response: response})
} catch (error){
yield put({ type: 'FETCH_FAILED', error });
}
}
export function* watchAPIcall(){
yield takeEvery(REGISTER, callAPIregister)
}
In your reducer you can create a variable in initial state object and then in your 'REGISTER_SAGA' capture the data that we got from our saga
reducer.js
const initialState = {
apiCalling: false,
occupation: null,
data: []
}
case "REGISTER_SAGA":
console.log('inside register_saga reducer', payload)
return {
apiCalling: true,
occupation: 'REGISTER',
data: action.response
}
import { takeEvery, put, call } from "redux-saga/effects";
import { AnyAction } from "redux";
const users = [
{
id: 1,
name: "Keshav Gera",
email: "Keshav.Gera#gmail.com"
},
{
id: 2,
name: "Happy Gera",
email: "Happy.Gera#gmail.com"
}
];
yield put(getUsersSuccess({ users }));

React/Redux - Is passing this.props.history to a thunk an anti-pattern?

I have a form in a simple react app. When the form submits, it fires a thunk.
handleSubmit(e) {
e.preventDefault();
this.props.dispatch(loginUser({
username: this.state.username,
password: this.state.password,
history: this.props.history
}));
}
As you can see, I am passing react-router-dom's history to the thunk. Here is the thunk:
export const loginUser = input => dispatch => {
return fetch('xxxxxxxxxx', {
method: 'POST',
body: JSON.stringify({
username: input.username,
password: input.password
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => {
if(!res.ok) {
return Promise.reject(res.statusText)
}
return res.json();
})
.then(token => {
localStorage.setItem('token', token.authToken);
input.history.push('/');
})
.catch(err => console.error(`error: ${err}`));
}
As you can see, if the fetch is successful, I then push to the history.
Is this an anti-pattern? Or is this ok. It works* but I want to know if it's super weird/not recommended
According to this page: https://reacttraining.com/react-router/web/guides/redux-integration you are doing the correct thing including history in your thunk input.
Quote:
"The solution here is simply to include the history object (provided to all route components) in the payload of the action, and your async handler can use this to navigate when appropriate."

showing success and error messages in react/redux app

I'm trying to add toast notifications to my app, one plugin I've been trying to use is react-toastify.
The issue I'm having is probably more a general react/redux issue more than with a plugin such as react-toastify.
I'm using a reducer to set the redux state for errors and success messages, from what I understand with the current code, each error or success message is persistent in the store until another action is called to clear them.
The issue I can't figure out is how do I trigger a toast only once. Eg. I enter the wrong credentials, it creates an error toast, but whenever the state changes and reloads (typing anything into the email or password fields) it creates another toast.
How do I get it to only show once?
userActions.js
function handleErrors(res) {
if (res.ok) {
return res.json();
} else {
return res.json().then(err => {throw err;});
}
}
export const login = (user) => dispatch => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res =>
dispatch({
type: LOGIN,
payload: res
})
)
.catch(error =>
dispatch({
type: ERROR,
payload: error
})
)
}
userReducer.js
const initialState = {
errors: '',
success: ''
};
export default function(state = initialState, action) {
switch (action.type) {
case LOGIN:
return {
...state,
errors: '',
success: action.payload.message
};
case ERROR:
return {
...state,
success: '',
errors: action.payload.message
}
default:
return state;
}
}
app.js
app.post('/login', function(req, res) {
... return res.status(500).send({ message: 'Wrong credentials' });
... return res.status(200).send({ message: 'good!' });
});
login.js
class Login extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
}
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
render() {
const { errors, login, success } = this.props;
if (success !== '') toast.success(success, {
position: toast.POSITION.TOP_CENTER
});
if (errors !== '') toast.error(errors, {
position: toast.POSITION.TOP_CENTER
});
return (
<div>
<input type="text" id="email" placeholder="Email Address" onChange={this.handleChange} />
<input type="password" id="password" placeholder="Password" onChange={this.handleChange} />
<button onClick={() => login(JSON.stringify({email: this.state.email, password: this.state.password}))}>Log In</button>
<ToastContainer />
</div>
)
}
}
const mapStateToProps = state => ({
errors: state.store.errors,
success: state.store.success
});
export default connect(mapStateToProps, {login})(Login);
You're calling toast.success or toast.error inside render which makes a new toast pop up every time you re-render the component.
The solution is simple. Move your toast calls outside render, where they will only be called once.
One way to achieve this is to return a value from your userAction.
export const login = (user) => dispatch => {
return new Promise((resolve, reject) => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res => {
dispatch({
type: LOGIN,
payload: res
})
resolve(res)
}
)
.catch(error => {
dispatch({
type: ERROR,
payload: error
})
reject(error)
}
)
}
}
Then use that value to toast in login.js.
class Login ... {
...
loginUser = () => {
this.props.login(JSON.stringify({email: this.state.email, password: this.state.password}))
.then(res => {
toast.success(res.message, { position: toast.POSITION.TOP_CENTER })
}
).catch(error => {
toast.error(error.message, { position: toast.POSITION.TOP_CENTER })
}
)
}
...
render() {
return (
...
<button onClick={this.loginUser}>Log In</button>
...
)
}
}
There are other ways to achieve the same functionality and depending on the structure of your project, you may want to toast in a more generalized way.

Update state values from a different file in react native

I am keeping all my functions in one file, and calling those functions in activities where needed in my react native project. Now in one of my functions which has a fetch api, I am fetching data from my online server and printing the response after a successful query.
Now I want to be able to update state value with the response from the fetch method in the then.
App.js
...
import {registerUsers} from './src/utils/api.js'
export class App extends Component{
state = {
isLoggedIn:false,
isLoading:false,
isAppready:false
}
_Register = (email,password,fullName) =>{
this.setState({isLoading:true})
//calling the register user function here
registerUsers(email,password,fullName)
}
...
The api file
import React from 'react'
import { Alert } from 'react-native';
export function registerUsers(email, password, fullName) {
fetch('http://00.00.00.00/reg/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userEmail: email,
userPassword: password,
userFullName: fullName
})
}).then((response) => response.json())
.then((responseJson) => {
//setState({ isLoggedIn: true, isLoading: false })
// Showing response message coming from server after inserting records.
Alert.alert(responseJson);
}).catch((error) => {
// this.setState({ isLoggedIn: true, isLoading: false })
console.error(error);
});
}
I would now want to update the state values thus isLoggedIn: true and isLoading:false after the fetch method has been processed. The problem now is that I can't figure it out where to update the state values since I am calling the registerUsers function from a different file.
I would be grateful if someone could share an idea as to how to figure this out. Thanks
registerUsers should return the promise. That way, you can handle the response directly inside your component:
API:
export function registerUsers(email, password, fullName) {
return fetch('http://00.00.00.00/reg/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userEmail: email,
userPassword: password,
userFullName: fullName
})
}).then( response => response.json());
}
Component:
import {registerUsers} from './src/utils/api.js'
export class App extends Component{
state = {
isLoggedIn:false,
isLoading:false,
isAppready:false
}
_Register = (email,password,fullName) =>{
this.setState({isLoading:true})
//calling the register user function here
registerUsers(email, password, fullName)
.then( responseJson => {
this.setState({
isLoggedIn: true,
isLoading: false,
data: responseJson
});
}).catch( error => {
this.setState({ isLoggedIn: false, isLoading: false });
});
}

How to make a rest post call from ReactJS code?

I am new to ReactJS and UI and I wanted to know how to make a simple REST based POST call from ReactJS code.
If there is any example present it would be really helpful.
Straight from the React Native docs:
fetch('https://mywebsite.example/endpoint/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
})
})
(This is posting JSON, but you could also do, for example, multipart-form.)
Also see docs for ReactJS AJAX FAQs if not using React Native.
React doesn't really have an opinion about how you make REST calls. Basically you can choose whatever kind of AJAX library you like for this task.
The easiest way with plain old JavaScript is probably something like this:
var request = new XMLHttpRequest();
request.open('POST', '/my/url', true);
request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
request.send(data);
In modern browsers you can also use fetch.
If you have more components that make REST calls it might make sense to put this kind of logic in a class that can be used across the components. E.g. RESTClient.post(…)
Another recently popular packages is : axios
Install : npm install axios --save
Simple Promise based requests
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
you can install superagent
npm install superagent --save
then for make post call to server
import request from "../../node_modules/superagent/superagent";
request
.post('http://localhost/userLogin')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ username: "username", password: "password" })
.end(function(err, res){
console.log(res.text);
});
As of 2018 and beyond, you have a more modern option which is to incorporate async/await in your ReactJS application. A promise-based HTTP client library such as axios can be used. The sample code is given below:
import axios from 'axios';
...
class Login extends Component {
constructor(props, context) {
super(props, context);
this.onLogin = this.onLogin.bind(this);
...
}
async onLogin() {
const { email, password } = this.state;
try {
const response = await axios.post('/login', { email, password });
console.log(response);
} catch (err) {
...
}
}
...
}
I think this way also a normal way. But sorry, I can't describe in English ((
submitHandler = e => {
e.preventDefault()
console.log(this.state)
fetch('http://localhost:5000/questions',{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(this.state)
}).then(response => {
console.log(response)
})
.catch(error =>{
console.log(error)
})
}
https://googlechrome.github.io/samples/fetch-api/fetch-post.html
fetch('url/questions',{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(this.state)
}).then(response => {
console.log(response)
})
.catch(error =>{
console.log(error)
})
Here is a the list of ajax libraries comparison based on the features and support.
I prefer to use fetch for only client side development or isomorphic-fetch for using in both client side and server side development.
For more information on isomorphic-fetch vs fetch
Here is a util function modified (another post on stack) for get and post both. Make Util.js file.
let cachedData = null;
let cachedPostData = null;
const postServiceData = (url, params) => {
console.log('cache status' + cachedPostData );
if (cachedPostData === null) {
console.log('post-data: requesting data');
return fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
})
.then(response => {
cachedPostData = response.json();
return cachedPostData;
});
} else {
console.log('post-data: returning cachedPostData data');
return Promise.resolve(cachedPostData);
}
}
const getServiceData = (url) => {
console.log('cache status' + cachedData );
if (cachedData === null) {
console.log('get-data: requesting data');
return fetch(url, {})
.then(response => {
cachedData = response.json();
return cachedData;
});
} else {
console.log('get-data: returning cached data');
return Promise.resolve(cachedData);
}
};
export { getServiceData, postServiceData };
Usage like below in another component
import { getServiceData, postServiceData } from './../Utils/Util';
constructor(props) {
super(props)
this.state = {
datastore : []
}
}
componentDidMount = () => {
let posturl = 'yoururl';
let getdataString = { name: "xys", date:"today"};
postServiceData(posturl, getdataString)
.then(items => {
this.setState({ datastore: items })
console.log(items);
});
}
Here is the simple method to define and call post APIs in reactjs. Install axios using command npm install axios and call post req method wherever you want, it will return array that contains 100 elements.
// Define post_req() Method in authAction.js
import axios from 'axios';
const post_req = (data) => {
return new Promise((resolve, reject) => {
const url = 'https://jsonplaceholder.typicode.com/posts'
const header = {
"Access-Control-Allow-Origin": "*",
"Content-Type: application/json"
}
axios({
method: 'post',
url: url,
data: data,
headers: header
});
.then((res)=>{resolve(res);})
.catch((err)=>{reject(err);})
})
}
// Calling post_req() Method in react component
import React, { Component } from 'react';
import { post_req } from 'path of file authAction.js'
class MyReactComponent extends Component {
constructor(props) {
super(props);
this.state = {
myList:[]
};
}
componentDidMount() {
let data = {
.......
}
this.props.post_req(data)
.then((resp)=>{this.setState({myList:resp.data})})
.catch((err)=>{console.log('here is my err',err)})
}
render() {
return (
<div>
....
</div)
}
}
export default MyReactComponent;
import React ,{useState}from 'react';
import Axios from 'axios';
export default function Formlp()
{
const url ="";
const [state, setstate] = useState({
name:"",
iduser:""
})
function handel(e){
const newdata={...state}
newdata[e.target.id]=e.target.value
setstate(newdata);
}
function submit(e)
{
e.preventDefault();
// Axios.post(url,{name:state.name,iduser:state.iduser}).then( res=>{console.log(res)});
console.log(state)
}
return (
<div onSubmit={ (e)=> submit(e)}>
<input onChange={ (e)=>handel(e) } id="name" value={state.name} placeholder="name" type="text" >
<input onChange={ (e)=>handel(e) } id="iduser" value={state.iduser} placeholder="iduser" type="text" >
<button>submit</button>
</form>
</div>
);
}
Here is a quick example for v18+ while handling form data and creating a POST request with the data.
async function handleOrderSubmit(event){
event.preventDefault()
try{
const formData= {name: event.target.name.value, email: event.target.email.value, message: event.target.name.message}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
};
const response = await fetch('https://www.example.com/form', requestOptions);
const data = await response.json();
navigate("/form-response", { state: {data: data, status: true} })
}
catch(error){
navigate("/form-response", { state: {status: false} })
}
}
Note 1: Using status on '/form-response' page, you can customise what to show user. For true, you can show a different section and for false a different one.
Note 2: If the status is successful, you can access data on the next page also and customise it according to user information.
Note 3: event.preventDefault() is important to avoid page reloading.
Here is an example: https://jsfiddle.net/69z2wepo/9888/
$.ajax({
type: 'POST',
url: '/some/url',
data: data
})
.done(function(result) {
this.clearForm();
this.setState({result:result});
}.bind(this)
.fail(function(jqXhr) {
console.log('failed to register');
});
It used jquery.ajax method but you can easily replace it with AJAX based libs like axios, superagent or fetch.

Resources