Dispatching action on Redux during asynchronus API request using redux thunk - reactjs

I am quite new to redux and react. I have also checked out a number of ways here to solve my problem but it appears I am not making any headway.
I intend performing an asynchronous operation using redux-thung following the tutorial https://github.com/reduxjs/redux-thunk, but the challenge I have is that the the function sendApplication() does not dispatch the action nextPage() neither does the function hitUrl() works. I have been on this issues for days. Someone should help me out please.
import React from 'react';
import { withStyles} from '#material-ui/styles';
import { FormLabel, TextField, Button } from '#material-ui/core';
import {connect} from 'react-redux';
import { nextPage, previousPage, enableBtnAvailability} from '../../actions/formPageController';
import { updateTextValueAvailability, clearField } from '../../actions/formInputController';
import { useStyles } from '../Styles/formStyles';
import { ValidatorForm, TextValidator} from 'react-material-ui-form-validator';
import sendApplication from '../../utility/sendRequest';
import { bindActionCreators } from 'redux';
const axios = require('axios');
const AvailablityTab= withStyles({
})((props) => {
console.log(props);
const handleChange=(e)=>{
const name= e.target.name;
const value = e.target.value;
const {updateTextValueAvailability} = props;
updateTextValueAvailability(name,value);
let unfilledFormFieldArray = props.text.filter((item)=> {
console.log(item);
return item.value=== "";
})
console.log(unfilledFormFieldArray);
console.log(unfilledFormFieldArray.length);
if(unfilledFormFieldArray.length ===0){
const {enableBtnAvailability} = props;
enableBtnAvailability();
}
}
const handleSubmit=()=>{
//const {postApplication} = props;
sendApplication();
console.log(props);
console.log('he submit');
}
const hitUrl = async function () {
//alert('hi')
try {
console.log(3);
const response = await axios.get('http://localhost:1337/api/v1/application/fetch-all');
console.log(response);
return response;
} catch (error) {
console.error(error);
}
};
const sendApplication = () => {
console.log(4);
console.log(props);
return function(props) {
console.log('xyz');
console.log(props);
const {nextPage} = props;
// dispatch(nextPage());
nextPage();
console.log(5);
alert('hi2')
return hitUrl().then(
() => {
console.log('thunk success');
nextPage();
},
() => {
console.log('thunk error');
//props.dispatch(previousPage())
},
);
};
}
const handlePrevious=()=>{
const {previousPage} = props;
previousPage();
}
console.log(props);
const classes = useStyles();
let validationRule = ['required'];
let errorMessages = ['This field is required'];
return (
<div className="formtab">
<ValidatorForm //ref="form"
onSubmit={handleSubmit}
onError={errors => console.log(errors)}
>
{props.text.map((each)=>{
let onFocus = false;
if(each.id === 1){
onFocus = true;
}
return(<div className={classes.question} key={each.id}>
<FormLabel className={classes.questionLabel} component="legend">{each.label}</FormLabel>
<TextValidator
id={"filled-hidden-label"}
className={classes.textField}
hiddenLabel
variant="outlined"
fullWidth
name={each.name}
onChange={handleChange}
value={each.value}
margin= "none"
placeholder={each.placeholder}
validators={validationRule}
errorMessages={errorMessages}
autoFocus= {onFocus}
/>
</div>)
})}
<div className={classes.buttondiv} >
<Button className={classes.prev} variant="contained" onClick={handlePrevious}>Previous</Button>
<Button className={classes.next} variant="contained" type="submit" disabled={!props.btnActivator} >Submit</Button>
</div>
</ValidatorForm>
</div>
)});
const mapStateToProps= (state)=>{
const availablity = state.availabilityQuestion;
return {
text: availablity.text,
radio: availablity.radio,
btnActivator: state.btnActivator.availability
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
postApplication: sendApplication,
previousPage,
enableBtnAvailability,
updateTextValueAvailability,
nextPage,
clearField
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(AvailablityTab);

Since sendApplication returns a function, but does not execute it, you can call it like this:
sendApplication()(props); // it looks like you expect props to be passed to your function
This should successfully execute your nextPage function and return the value returned by hitUrl.
The alternative would be to execute the function instead of returning it
sendApplication(props);
...
const sendApplication = (props) => {
console.log('xyz');
console.log(props);
const {nextPage} = props;
// dispatch(nextPage());
nextPage();
console.log(5);
alert('hi2')
return hitUrl().then(
() => {
console.log('thunk success');
nextPage();
},
() => {
console.log('thunk error');
//props.dispatch(previousPage())
},
);
};
Now we've eliminated the internal function and just called it directly instead. Now calling sendApplication will return the return value of hitUrl.

Related

Notification.requestPermission() is not a function

I am making a website in which I am able to send notifications to all the users but there is a problem, it says
Notification.requestPermission() is not a function
Here is my code:
import React, { useState } from "react";
const Notification = () => {
const [input, setInput] = useState("");
const handleInput = (e) => {
e.preventDefault();
setInput(e.target.value);
};
const sendNotification = () => {
Notification.requestPermission().then((perm) => {
if (perm === "granted") {
new Notification(input, {
body: "Go check it out!",
});
}
});
};
return (
<>
<input type="text" value={input} onChange={handleInput} />
<button onClick={sendNotification}>Send</button>
</>
);
};
export default Notification;
I am using react
Thank You in advance!

TypeError: weatherData.map is not a function

I'm trying to map over data from API, but while writing the code to display the data I got this error: TypeError: weatherData.map is not a function
I tried removing useEffect from the code and tried to add curly brackets: const [weatherData, setWeatherData] = useState([{}])
Update: Line 14 log undefined : console.log(weatherData.response)
import axios from 'axios'
import { useEffect, useState } from 'react'
import './App.css'
function App() {
const [search, setSearch] = useState("london")
const [weatherData, setWeatherData] = useState([])
const getWeatherData = async () => {
try {
const weatherData = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${search}&appid={APIKEY}`);
console.log(weatherData.response);
if (weatherData) {
setWeatherData(weatherData);
}
} catch (err) {
console.error(err);
}
}
useEffect(() => {
getWeatherData()
}, [getWeatherData])
const handleChange = (e) => {
setSearch(e.target.value)
}
return (
<div className="App">
<div className='inputContainer'>
<input className='searchInput' type="text" onChange={handleChange} />
</div>
{weatherData.map((weather) => {
return (
<div>
<h1>{weather.name}, {weather.country}</h1>
</div>
)
})}
</div>
)
}
export default App
You're having errors in fetching the data as well as rendering it.
Just change the entire App component like this :
import { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [search, setSearch] = useState("London");
const [weatherData, setWeatherData] = useState([]);
const APIKEY = "pass your api key here";
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`https://api.openweathermap.org/data/2.5/weather?q=${search}&appid=${APIKEY}`
);
setWeatherData(result.data);
};
fetchData();
}, [search]);
const handleChange = (e) => {
setSearch(e.target.value);
};
return (
<div className="App">
<div className="inputContainer">
<input className="searchInput" type="text" onChange={handleChange} />
</div>
<h1>
{" "}
{weatherData.name} ,{" "}
{weatherData.sys ? <span>{weatherData.sys.country}</span> : ""}{" "}
</h1>
</div>
);
}
export default App;
this should be working fine just make sure to change : const APIKEY = "pass your api key "; to const APIKEY = "<your API key> ";
this is a demo in codesandbox
Create a promise function:
const getWeatherData = async () => {
try {
const weatherData = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${search}&appid={APIKEY}`);
console.log(weatherData.response);
if (weatherData.response.data) {
setWeatherData(weatherData.response.data);
}
} catch (err) {
console.error(err);
}
}
Then call it.

Function setDoc() called with invalid data. Unsupported field value: a custom UserImpl object (found in field owner in document CreatedClasses)

This is the first time I'm asking a question here and also a newbie to coding. I'm trying to clone google classroom.
I am trying to use firestore to make a db collection when creating the class. But when I click create it doesn't create the class and create the db in firestore. It shows that the setDoc() function is invalid. Im using firestore version 9 (modular)
Here is my Form.js file. (The firestore related code is also included here)
import { DialogActions, TextField , Button} from "#material-ui/core"
import React, {useState} from 'react'
import { useLocalContext, useAuth } from '../../../context/AuthContext'
import { v4 as uuidV4 } from 'uuid'
import { db} from '../../../firebase'
import { collection, doc, setDoc } from "firebase/firestore"
const Form = () => {
const [className, setClassName] = useState('')
const [Level, setLevel] = useState('')
const [Batch, setBatch] = useState('')
const [Institute, setInstitute] = useState('')
const {setCreateClassDialog} = useLocalContext();
const {currentUser} = useAuth();
const addClass = async (e) => {
e.preventDefault()
const id = uuidV4()
// Add a new document with a generated id
const createClasses = doc(collection(db, 'CreatedClasses'));
await setDoc(createClasses, {
owner:currentUser,
className: className,
level: Level,
batch: Batch,
institute: Institute,
id: id
}).then (() => {
setCreateClassDialog(false);
})
}
return (
<div className='form'>
<p className="class__title">Create Class</p>
<div className='form__inputs'>
<TextField
id="filled-basic"
label="Class Name (Required)"
className="form__input"
variant="filled"
value={className}
onChange={(e) => setClassName(e.target.value)}
/>
<TextField
id="filled-basic"
label="Level/Semester (Required)"
className="form__input"
variant="filled"
value={Level}
onChange={(e) => setLevel(e.target.value)}
/>
<TextField
id="filled-basic"
label="Batch (Required)"
className="form__input"
variant="filled"
value={Batch}
onChange={(e) => setBatch(e.target.value)}
/>
<TextField
id="filled-basic"
label="Institute Name"
className="form__input"
variant="filled"
value={Institute}
onChange={(e) => setInstitute(e.target.value)}
/>
</div>
<DialogActions>
<Button onClick={addClass} color='primary'>
Create
</Button>
</DialogActions>
</div>
)
}
export default Form
And also (I don't know whether this is helpful but my context file is below)
import React, { createContext, useContext, useEffect, useState } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
signOut,
GoogleAuthProvider,
signInWithPopup
} from "firebase/auth";
import { auth } from "../firebase";
const AuthContext = createContext();
const AddContext = createContext()
export function useAuth() {
return useContext(AuthContext);
}
export function useLocalContext(){
return useContext(AddContext)
}
export function ContextProvider({children}){
const [createClassDialog,setCreateClassDialog] = useState(false);
const [joinClassDialog, setJoinClassDialog] = useState(false);
const value = { createClassDialog, setCreateClassDialog, joinClassDialog, setJoinClassDialog };
return <AddContext.Provider value={value}> {children} </AddContext.Provider>;
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true)
function signup(email, password) {
return createUserWithEmailAndPassword(auth,email, password);
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
function logout() {
return signOut(auth);
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email)
}
function googleSignIn() {
const googleAuthProvider = new GoogleAuthProvider();
return signInWithPopup(auth, googleAuthProvider);
}
function updateEmail(email) {
return currentUser.updateEmail(email)
}
function updatePassword(password) {
return currentUser.updatePassword(password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged( auth, (user) => {
setCurrentUser(user);
setLoading(false)
});
return () => {
unsubscribe();
};
}, []);
return (
<AuthContext.Provider
value={{ currentUser, login, signup, logout, googleSignIn, resetPassword,updateEmail, updatePassword }}
>
{!loading && children}
</AuthContext.Provider>
);
}
The console error message:
Try something like this, excluding the collection function from setting the document.
// Add a new document with a generated id
await setDoc(doc(db, 'CreatedClasses'), {
owner:currentUser,
className: className,
level: Level,
batch: Batch,
institute: Institute,
id: classId
}).then (() => {
setCreateClassDialog(false);
})

Need to call an alert message component from action in react

I've created a common component and exported it, i need to call that component in action based on the result from API. If the api success that alert message component will call with a message as "updated successfully". error then show with an error message.
calling service method in action. is there any way we can do like this? is it possible to call a component in action
You have many options.
1. Redux
If you are a fan of Redux, or your project already use Redux, you might want to do it like this.
First declare the slice, provider and hook
const CommonAlertSlice = createSlice({
name: 'CommonAlert',
initialState : {
error: undefined
},
reducers: {
setError(state, action: PayloadAction<string>) {
state.error = action.payload;
},
clearError(state) {
state.error = undefined;
},
}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const error = useSelector(state => state['CommonAlert'].error);
const dispatch = useDispatch();
return <>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() =>
dispatch(CommonAlertSlice.actions.clearError())} />
{children}
</>
}
export const useCommonAlert = () => {
const dispatch = useDispatch();
return {
setError: (error: string) => dispatch(CommonAlertSlice.actions.setError(error)),
}
}
And then use it like this.
const App: React.FC = () => {
return <CommonAlertProvider>
<YourComponent />
</CommonAlertProvider>
}
const YourComponent: React.FC = () => {
const { setError } = useCommonAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <> ... </>
}
2. React Context
If you like the built-in React Context, you can make it more simpler like this.
const CommonAlertContext = createContext({
setError: (error: string) => {}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const [error, setError] = useState<string>();
return <CommonAlertContext.Provider value={{
setError
}}>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
{children}
</CommonAlertContext.Provider>
}
export const useCommonAlert = () => useContext(CommonAlertContext);
And then use it the exact same way as in the Redux example.
3. A Hook Providing a Render Method
This option is the simplest.
export const useAlert = () => {
const [error, setError] = useState<string>();
return {
setError,
renderAlert: () => {
return <MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
}
}
}
Use it.
const YourComponent: React.FC = () => {
const { setError, renderAlert } = useAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <>
{renderAlert()}
...
</>
}
I saw the similar solution in Antd library, it was implemented like that
codesandbox link
App.js
import "./styles.css";
import alert from "./alert";
export default function App() {
const handleClick = () => {
alert();
};
return (
<div className="App">
<button onClick={handleClick}>Show alert</button>
</div>
);
}
alert function
import ReactDOM from "react-dom";
import { rootElement } from ".";
import Modal from "./Modal";
export default function alert() {
const modalEl = document.createElement("div");
rootElement.appendChild(modalEl);
function destroy() {
rootElement.removeChild(modalEl);
}
function render() {
ReactDOM.render(<Modal destroy={destroy} />, modalEl);
}
render();
}
Your modal component
import { useEffect } from "react";
export default function Modal({ destroy }) {
useEffect(() => {
return () => {
destroy();
};
}, [destroy]);
return (
<div>
Your alert <button onClick={destroy}>Close</button>
</div>
);
}
You can't call a Component in action, but you can use state for call a Component in render, using conditional rendering or state of Alert Component such as isShow.

useMutation update,the cache changed,but UI not change

I useMutation to send message ,but the message list in chat window not change. I found that the cache has changed . Please help , I can't understand.
The useQuery not work . UI have no change :(
But~! When I put them in one js file. it works.... why???
The version I used is #apollo/react-hooks 3.1.1
Parent window.js
import React from 'react';
import { useQuery } from "#apollo/react-hooks";
import { GET_CHAT } from "#/apollo/graphql";
import ChatInput from "#/pages/chat/components/input";
const ChatWindow = (props) => {
const { chatId, closeChat } = props;
const { data, loading, error } = useQuery(GET_CHAT, { variables: { chatId: chatId } });
if (loading) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;
const { chat } = data;
return (
<div className="chatWindow" key={'chatWindow' + chatId}>
<div className="header">
<span>{chat.users[1].username}</span>
<button className="close" onClick={() => closeChat(chatId)}>X</button>
</div>
<div className="messages">
{chat.messages.map((message, j) =>
<div key={'message' + message.id} className={'message ' + (message.user.id > 1 ? 'left' : 'right')}>
{message.text}
</div>
)}
</div>
<div className="input">
<ChatInput chatId={chatId}/>
</div>
</div>
);
};
export default ChatWindow;
Child input.js
import React, { useState } from 'react';
import { useApolloClient, useMutation } from "#apollo/react-hooks";
import { ADD_MESSAGE, GET_CHAT } from "#/apollo/graphql";
const ChatInput = (props) => {
const [textInput, setTextInput] = useState('');
const client = useApolloClient();
const { chatId } = props;
const [addMessage] = useMutation(ADD_MESSAGE, {
update(cache, { data: { addMessage } }) {
const { chat } = client.readQuery({
query: GET_CHAT,
variables: {
chatId: chatId
}
});
chat.messages.push(addMessage);
client.writeQuery({
query: GET_CHAT,
variables: {
chatId: chatId
},
data: {
chat
}
});
}
});
const onChangeInput = (event) => {
event.preventDefault();
setTextInput(event.target.value);
};
const handleKeyPress = (event, chatId, addMessage) => {
if (event.key === 'Enter' && textInput.length) {
addMessage({
variables: {
message: {
text: textInput,
chatId: chatId
}
}
});
setTextInput('');
}
};
return (
<input type="text"
value={textInput}
onChange={(event) => onChangeInput(event)}
onKeyPress={(event) => handleKeyPress(event, chatId, addMessage)}
/>
);
};
export default ChatInput;
You probably solved the issue by now, but for the record:
Your code mutates the chat state:
chat.messages.push(addMessage);
State should not be mutated (see the React setState Docs for more details).
Contruct a new array instead:
const newChat = [...chat, addMessage]

Resources