i am trying to build a react redux form using the connect() instead useSelector and useDispatch.
I managed to display the list of data and to reset the forms. But i didn't manage to send data. Here is the code:
Reducer.js
const initialState = {
tasks: {
name: "",
age: "",
job: "",
},
list: [
{
id: 0,
name: "Maillard",
age: 35,
job: "soldier",
},
],
};
export const toDoReducer = (state = initialState, action) => {
switch (action.type) {
case "name":
return {
...state,
tasks: {
name: action.payload,
},
};
case "age":
return {
...state,
tasks: {
age: action.payload,
},
};
case "job":
return {
...state,
tasks: {
job: action.payload,
},
};
case "clear":
return {
...state,
tasks: {
name: "",
age: "",
job: "",
},
};
case "add":
return {
...state,
tasks: {
...state.tasks,
id: state.list.length + 1,
},
};
default:
return {
...state,
};
}
};
export default toDoReducer;
import React from "react";
import { connect } from "react-redux";
import {
setName,
setAge,
setJob,
clearForm,
addForm,
} from "../../redux/action";
export const Form = (props) => {
return (
<div>
Name
<input value={props.list.name} onChange={(e) => { props.Name(e.target.value) }} />
Age
<input value={props.list.age} onChange={(e) => { props.Age(e.target.value) }} />
Profession
<input value={props.list.job} onChange={(e) => { props.Job(e.target.value) }} />
<div style={{ padding: 20 }}>
<button onClick={props.Clear}>Reset</button>
<button onClick={props.Add}>Envoyer</button>
</div>
</div>
);
};
const mapDispatchToProps = (dispatch) => {
return {
Name: () => {
dispatch({
type: "name",
setName,
});
},
Age: () => {
dispatch({ type: "age", setAge });
},
Job: () => {
dispatch({
type: "job",
setJob,
});
},
Clear: () => {
dispatch({ type: "clear", clearForm, });
},
Add: () => {
dispatch({)}
// My problem comes from the Add
<!-- begin snippet: js hide: false console: true babel: false -->
type: "add",
addForm
})
}
};
};
const mapStateToProps = (state) => ({
list: state.tasks,
});
export default connect(mapStateToProps, mapDispatchToProps)(Form);
// export default Form;
My problems comes from in mapDispatchToProps, i don't know what to do for the function Add
Your actions should be the ones defining the action type and you should call them in the mapDispatchToProps, not pass them as part of the action.
So your addForm should be something like
const addForm = () => ({
type:'add'
});
and in your mapDispatchToProps it should be like
Add: () => dispatch(addForm()),
But you have the same problem with all your dispatching
for example the Name should be
action
const setName = (payload) => ({
type:'name',
payload
});
mapDispatchToProps
Name: (nameValue) => {
dispatch(setName(nameValue));
},
Related
I am using xstate with react to implement a basic login functionality. The code is here and the issue I am facing is, on the event AUTHENTICATING it is meant to invoke a service authenticateUser and it is not invoking. No visible errors in the console. The component looks like
import { useMachine } from "#xstate/react";
import { createMachine, assign } from "xstate";
import "./App.css";
const authenticateUserNew = async (c, e) => {
console.log("service invoked");
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve();
} else {
reject();
}
}, 1000);
});
};
const loginMachine = createMachine(
{
id: "login-machine",
initial: "unauthenticated",
context: {
isAuthenticated: false,
},
states: {
unauthenticated: {
on: {
AUTHENTICATING: {
invoke: {
id: "authenticateUser",
src: (c, e) => authenticateUserNew(c, e),
onDone: {
target: "authenticated",
actions: assign({ isAuthenticated: (context, event) => true }),
},
onError: {},
},
},
},
},
authenticated: {
on: {
LOGOUT: {
target: "unauthenticated",
},
},
},
},
},
{
services: {
authenticateUser: () => {
console.log("service invoked");
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve();
} else {
reject();
}
}, 1000);
});
},
},
}
);
function App() {
const [current, send] = useMachine(loginMachine);
return (
<div className="App">
<h2>{current.value}</h2>
<br />
<h3>
isAuthenticated: {current.context.isAuthenticated ? "True" : "False"}
</h3>
<br />
<button onClick={() => send("AUTHENTICATING")}>AUTHENTICATE</button>
<br />
<button onClick={() => send("LOGOUT")}>LOGOUT</button>
</div>
);
}
export default App;
I have tried both the approach where I can externalize function and use it or define it inside the service section of state machine, in both the case it wasn't invoked.
1st approach
invoke: {
id: "authenticateUser",
src: (c, e) => authenticateUserNew(c, e),
onDone: {
target: "authenticated",
actions: assign({ isAuthenticated: (context, event) => true }),
},
onError: {},
}
2nd approach
invoke: {
id: "authenticateUser",
src: "authenticateUser",
onDone: {
target: "authenticated",
actions: assign({ isAuthenticated: (context, event) => true }),
},
onError: {},
}
React version: ^17.0.2
xstate: ^4.3.5
#xstate/react: 2.0.1
From docs:
An invocation is defined in a state node's configuration with the invoke property
You are instead trying to invoke in an event node, not a state one.
For example, you could do:
...
states: {
unauthenticated: {
on: {
AUTHENTICATE: {
target: 'authenticating'
},
},
},
authenticating: {
invoke: {
id: "authenticateUser",
src: 'authenticateUser',
onDone: {
target: "authenticated",
actions: assign({ isAuthenticated: (context, event) => true }),
},
onError: {
target: 'unauthenticated'
},
},
},
authenticated: {
on: {
LOGOUT: {
target: "unauthenticated",
},
},
},
},
...
and send the AUTHENTICATE event:
<button onClick={() => send("AUTHENTICATE")}>AUTHENTICATE</button>
Moreover, I'd like to suggest to avoid the isAuthenticated at all. You can check if you're authenticated with the matches method:
<h3>
isAuthenticated: {current.matches('authenticated') ? "True" : "False"}
</h3>
Why is transactionId not activating inside the useEffect hook?
It is an edit route through the context api. It strange because react-router-dom picks up the transactionId in the url.
edit transaction modal
let history = useHistory();
const { transaction, editTransaction } = useContext(GlobalContext);
const [selectedTransaction, setSelectedTransaction] = useState({
id: null,
category: "",
heading: "",
description: "",
subHeading: "",
author: "",
});
const transactionId = match.params.id;
useEffect(() => {
const transactionId = transactionId;
const selectedTransaction = transaction.find(
(t) => t.id === parseInt(transactionId)
);
setSelectedTransaction(selectedTransaction);
}, [transactionId, transaction]);
const [open, setOpen] = useState(true);
const cancelButtonRef = useRef(null);
const onSubmit = (e) => {
editTransaction(selectedTransaction);
history.push("/");
};
const handleOnChange = (transactionKey, val) =>
setSelectedTransaction({
...selectedTransaction,
[transactionKey]: val,
});
let formData = {
name: setSelectedTransaction.name,
amount: setSelectedTransaction.amount,
category: setSelectedTransaction.category,
};
global state
import React, { createContext, useReducer, useEffect } from "react";
import AppReducer from "./AppReducer";
//Initial State
const initialState = {
transactions: [
{
id: "1",
name: "Rent",
href: "#",
category: "expense",
amount: 1000,
currency: "USD",
status: "processing",
date: "July 1, 2020",
datetime: "2020-07-11",
type: "Bills",
},
{
id: "2",
name: "IRS",
href: "#",
category: "income",
amount: 5000,
currency: "USD",
status: "success",
date: "July 18, 2020",
datetime: "2020-07-18",
type: "Extra Income",
},
{
id: "3",
name: "Paypal",
href: "#",
category: "income",
amount: 15000,
currency: "USD",
status: "success",
date: "July 18, 2020",
datetime: "2020-07-18",
type: "Income",
},
{
id: "4",
name: "AT&T",
href: "#",
category: "expense",
amount: 2000,
currency: "USD",
status: "success",
date: "July 11, 2020",
datetime: "2020-07-11",
type: "Phone",
},
],
totalTransactionCount: 4,
};
//Create context
export const GlobalContext = createContext(initialState);
//Provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
const { totalTransactionCount, transactions } = state;
//Actions
function deleteTransaction(id) {
dispatch({
type: "DELETE_TRANSACTION",
payload: id,
});
}
function addTransaction(transaction) {
dispatch({
type: "ADD_TRANSACTION",
payload: transaction,
});
}
const editTransaction = (transaction) => {
dispatch({
type: "EDIT_TRANSACTION",
payload: transaction,
});
};
useEffect(() => {
dispatch({
type: "SET_TRANSACTION_COUNT",
payload: transactions.length,
});
}, [transactions]);
return (
<GlobalContext.Provider
value={{
transactions: state.transactions,
totalTransactionCount,
deleteTransaction,
addTransaction,
editTransaction,
}}>
{children}
</GlobalContext.Provider>
);
};
reducer
export default (state, action) => {
switch (action.type) {
case "DELETE_TRANSACTION":
return {
...state,
transactions: state.transactions.filter(
(transaction) => transaction.id !== action.payload
),
};
case "EDIT_TRANSACTION":
const updatedTransaction = action.payload;
const updatedTransactions = state.transactions.map((transaction) => {
if (transaction.id === updatedTransaction.id) {
return updatedTransaction;
}
return transaction;
});
return {
...state,
transactions: updatedTransactions,
};
case "ADD_TRANSACTION":
return {
...state,
transactions: [action.payload, ...state.transactions],
};
case "SET_TRANSACTION_COUNT":
return {
...state,
totalTransactionCount: action.payload,
};
default:
return state;
}
};
I'm experiencing some difficulty with trying to map through an objects property which is an array of objects. I get back an error message that .map is undefined, basically saying there is no array. The array i want to map through is education. At times I also get a proxy error that the route is timing out and this loses the profile data i'm fetching and the profile object is empty. Is there anyway to fix this too.
My model:
import mongoose from 'mongoose'
const profileSchema = new mongoose.Schema(
{
experience: [
{
title: {
type: String,
required: true,
},
company: {
type: String,
required: true,
},
location: {
type: String,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
education: [
{
level: {
type: String,
required: true,
enum: [
'None',
'GCSE or equivalent',
'A-Level or equivalent',
'Certificate of Higher Education',
'Diploma of Higher Education',
'Bachelors',
'Masters',
'PhD',
],
},
school: {
type: String,
required: true,
},
fieldofstudy: {
type: String,
required: true,
},
city: {
type: String,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
},
],
skills: [
{
name: {
type: String,
required: true,
},
yearsExperience: {
type: Number,
required: true,
},
},
],
additionalInfo: {
desiredJobTitle: {
type: String,
},
desiredJobType: {
type: [String],
},
desiredSalary: {
type: Number,
},
readyToWork: {
type: Boolean,
default: false,
},
relocate: {
type: Boolean,
default: false,
},
},
savedJobs: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job',
},
],
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
},
{ timestamps: true }
)
const Profile = mongoose.model('Profile', profileSchema)
export default Profile
My action:
import axios from 'axios'
import {
PROFILE_DETAILS_REQUEST,
PROFILE_DETAILS_SUCCESS,
PROFILE_DETAILS_FAIL,
PROFILE_CREATE_REQUEST,
PROFILE_CREATE_SUCCESS,
PROFILE_CREATE_FAIL,
PROFILE_CREATE_EDUCATION_REQUEST,
PROFILE_CREATE_EDUCATION_SUCCESS,
PROFILE_CREATE_EDUCATION_FAIL,
} from '../constants/profileConstants'
import { setAlert } from './alertActions'
export const getCurrentProfile = () => async (dispatch, getState) => {
try {
dispatch({ type: PROFILE_DETAILS_REQUEST })
const {
userLogin: { userData },
} = getState()
const config = {
headers: {
Authorization: `Bearer ${userData.token}`,
},
}
const { data } = await axios.get('/api/v1/profile/me', config)
dispatch({
type: PROFILE_DETAILS_SUCCESS,
payload: data,
})
} catch (error) {
dispatch({
type: PROFILE_DETAILS_FAIL,
payload:
error.response && error.response.data.error
? error.response.data.error
: null,
})
}
}
My reducer:
export const profileDetailsReducer = (state = { profile: {} }, action) => {
switch (action.type) {
case PROFILE_DETAILS_REQUEST:
return {
...state,
loading: true,
}
case PROFILE_DETAILS_SUCCESS:
return {
loading: false,
profile: action.payload,
}
case PROFILE_DETAILS_FAIL:
return {
loading: false,
error: action.payload,
}
case PROFILE_DETAILS_RESET:
return {
profile: {},
}
default:
return state
}
}
My dashboard component:
import React, { useEffect } from 'react'
import Moment from 'react-moment'
import { useDispatch, useSelector } from 'react-redux'
import { getCurrentProfile } from '../actions/profileActions'
import Loader from '../components/layout/Loader'
import DashboardActions from '../components/dashboard/DashboardActions'
const Dashboard = ({ history }) => {
const dispatch = useDispatch()
const profileDetails = useSelector((state) => state.profileDetails)
const { loading, error, profile } = profileDetails
const userLogin = useSelector((state) => state.userLogin)
const { userData } = userLogin
console.log(profile)
useEffect(() => {
if (!userData) {
history.push('/login')
} else {
dispatch(getCurrentProfile())
}
}, [dispatch, history, userData])
return (
<>
<h1 class='mb-4'>Dashboard</h1>
<p>Welcome {userData && userData.name}</p>
<br />
{loading ? (
<Loader />
) : (
<>
<DashboardActions />
<h2 className='my-2'>Education Details</h2>
<table className='table'>
<thead>
<tr>
<th>Level</th>
<th>Field of study</th>
<th>School</th>
</tr>
</thead>
<tbody>{profile.education.map((edu) => console.log(edu))}</tbody>
</table>
</>
)}
</>
)
}
export default Dashboard
Issue
state.profile.educatioin is undefined in your initial state.
export const profileDetailsReducer = (state = { profile: {} }, action) => { ...
Solution(s)
Define an initial state that contains an education array
const initialState = {
profile: {
education: [],
},
};
export const profileDetailsReducer = (state = initialState, action) => { ...
Or provide a fallback value from your selector
const { loading, error, profile: { education = [] } } = profileDetails;
...
<tbody>{education.map((edu) => console.log(edu))}</tbody>
Or provide the fallback in the render
<tbody>{(profile.education ?? []).map((edu) => console.log(edu))}</tbody>
This question already has answers here:
All reducers will be invoked when an action is dispatched?
(3 answers)
Closed 2 years ago.
I am learning about Redux and I have two reducers, a contactReducer to show contacts on the page and a testReducer to just mess around with. In one of my component files I have this function:
const mapDispatchToProps = (dispatch) => ({
getContacts: () => dispatch({ type: "TEST_ACTION" }),
});
These are my two reducer files:
contactReducer:
import { GET_CONTACTS } from "../actions/types";
const initialState = {
contacts: [
{
id: 1,
name: "John Doe",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "Karen Williams",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
return {
...state,
};
default:
console.log("testing action in contactReducer");
return state;
}
}
and testReducer:
import { GET_CONTACTS } from "../actions/types";
const initialState = {
contactsTest: [
{
id: 1,
name: "ffffffffffff",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "ggggggggggggg",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "aaaaaaaaaaaaaa",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
};
export default function (state = initialState, action) {
switch (action.type) {
case "TEST_ACTION":
return {
...state,
};
default:
console.log("testing action");
return state;
}
}
So, what I noticed from the console.log statements in the reducer files was that for every contact, both the contactReducer and testReducer's function was called with this line:
getContacts: () => dispatch({ type: "TEST_ACTION" }),
});
What if I have multiple reducers but I only want to call one of their functions for dispatch, what would I do?
combineReducers, Is a helper function in redux that helps you divide your reducers. take a look at this link: LINK
I'm new to react-redux, I was working on a tutorial and I wanted to add a few features of my own.
How do I add a new method to add a new song to the existing array of song objects > I was confused because theres already a song key in combine reducers. What should I return/pass as a parameter to add a new song?
import { combineReducers } from "redux";
const songsReducer = () => {
return [
{
title: "song one",
duration: "4:30"
},
{
title: "song one",
duration: "4:00"
},
{
title: "song one",
duration: "3:28"
},
{
title: "song one",
duration: "3:50"
}
];
};
const selectedSongReducer = (selectedSong = null, action) => {
if (action.type === "SONG_SELECTED") {
return action.payload;
}
return selectedSong;
};
**const addSong = () => {};** // need help with this function
export default combineReducers({
songs: songsReducer,
selectedSong: selectedSongReducer
});
const addSong = song => ({
type: 'ADD_SONG',
payload: song,
});
const songsReducer = (songs = [], action) => {
switch (action.type) {
case 'ADD_SONG': {
return [...songs, action.payload.song]; //immutable
}
default: {
return [
{
title: 'song one',
duration: '4:30',
},
{
title: 'song one',
duration: '4:00',
},
{
title: 'song one',
duration: '3:28',
},
{
title: 'song one',
duration: '3:50',
},
];
}
}
};
The action will return a song with type ADD_SONG:
const addSong = (song) => {
return {
type: "ADD_SONG",
payload: {
song
}
}
}
In reducer:
const selectedSongReducer = (selectedSong = [], { type, payload = {} }) => {
if (type === "SONG_SELECTED") {
return payload;
}
if (type === "ADD_SONG") {
return [...selectedSong].push(payload.song) // Imutable selectedSong
}
return selectedSong;
};
Always use switch case for cleaner code
and return like this return [...selectedSong, action.payload];
to avoid mutation
const addSong = (song) => {
return {
type: "ADD_SONG",
payload: {
song
}
}
}
const selectedSongReducer = (selectedSong = [], action) => {
switch (action.type) {
case 'SONG_SELECTED': {
return [...selectedSong, action.payload.song];
}
default: null
}
};
const songsReducer = (song = [], action) => {
switch (action.type) {
case 'ADD_SONG': {
return [...songs, action.payload.song];
}
default: {
return [
{
title: 'song one',
duration: '4:30',
},
{
title: 'song one',
duration: '4:00',
},
{
title: 'song one',
duration: '3:28',
},
{
title: 'song one',
duration: '3:50',
},
];
}
}
};