React component not updating using redux - reactjs

I'm trying to create a very simple Project in React to learn Redux, and I'm expecting that, whenever I click a button, the text inside a paragraph will be rendered, however, I can't get the app component (the only one I have) to get updated. Where am i failing?.
import { createStore } from 'redux'
const deleteUsers = 'delete users';
const createUser = 'créate user';
const createUserStranger = 'create stranger';
function deleteUsersActionCreator() {
return {
type: deleteUsers,
data: []
}
}
function createUsersActionCreator() {
return {
type: createUser,
data: ['Irina', 'Denis', 'Julio']
}
}
function createUserStrangerActionCreator() {
return {
type: createUserStranger,
data: 'stranger'
}
}
const initialState = {
users: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case createUser:
return {
...state,
users: [...state.users, ...action.data]
}
case deleteUsers:
return {
...state,
users: []
}
case createUserStranger:
return {
...state,
users: [...state.users, action.data]
}
default:
return state
}
};
function dispatchAction(action) {
store.dispatch(action)
}
const store = createStore(reducer);
let users = [];
store.subscribe(() => {
users = store.getState().users
console.log('users', users)
});
const App = () => {
console.log(store.getState().users.map(e => e));
console.log('rendering again')
return (
<div className="App">
<h1>People App </h1>
<hr/>
<p>
{
store.getState().users.map( user => {
return
(<p>{user}</p>)
})
}
</p>
<button onClick={()=> dispatchAction(createUsersActionCreator())}>Add Family</button>
<button onClick={()=> dispatchAction(createUserStrangerActionCreator())}>Add Stranger</button>
<button onClick={()=> dispatchAction(deleteUsersActionCreator())}>Delete users</button>
</div>
)
}
export default App
I know I should be using useState, usteEffect and this approaches, but since I'm learning react from zero, I thought it was a Good idea to learn redux as well. I've also Heard about Zustand (which I'm gonna learn too) but I'd like to have a Good grasp of raw redux. Any advise is very Good welcome :)

Since you are using React - better to use Hooks useSelector and useDispatch inside components
const App = () => {
const users = useSelector(state => state.users)
const dispatch = useDispatch()
console.log('rendering again', users)
return (
<div className="App">
<h1>People App </h1>
<hr/>
<p>
{
users.map( user => {
return
(<p>{user}</p>)
})
}
</p>
<button onClick={()=> dispatch(createUsersActionCreator())}>Add Family</button>
<button onClick={()=> dispatch(createUserStrangerActionCreator())}>Add Stranger</button>
<button onClick={()=> dispatch(deleteUsersActionCreator())}>Delete users</button>
</div>
)
}
export default App

Related

TypeError: Redux Action is not a function

I was trying to dispatch a redux action(setMarker) into a component with mapDispatchToProps, but whenever I invoke that action I get this
TypeError: setMarker is not a function
import React from 'react';
import { connect } from 'react-redux';
import { setMarker } from '../../redux/map/map.actions';
import './suggestion.styles.scss';
const Suggestion = (location, { setMarker }) => {
const { area, city, address } = location;
return (
<div className="suggestion" onClick={() => console.log(setMarker)}>
<i className="fas fa-map-marker-alt fa-2x"></i>
<div className="address">
<h3 className="location-title">{`${area}, ${city}`}</h3>
<p className="location-desc">{address}</p>
</div>
</div>
);
};
const mapDispatchToProps = {
setMarker,
};
export default connect(null, mapDispatchToProps)(Suggestion);
My actions file contains the following code
import mapActionTypes from './map.types';
export const setMarker = (location) => ({
type: mapActionTypes.SET_MARKER,
payload: location,
});
And my reducer file contains the following code
import mapActionTypes from './map.types';
const INITIAL_STATE = {
location: {
lat: 23.810331,
lng: 90.412521,
},
};
const mapReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case mapActionTypes.SET_MARKER:
return {
...state,
location: action.payload,
};
default:
return state;
}
};
export default mapReducer;
you need to import it like this: { setMarker } so that it becomes a destructured prop
or you could do
const Suggestion = (props) => {
then call
props.setMarker()
also if you want another tip, you can do this for mapDispatch now
const mapDispatchToProps = {
setMarker
}
much easier to read :)
If Suggestion is a react component in the functional form then you would get props as an argument. You should use object destructuring syntax if you want to access them directly or you can use props.setMarker.
const Suggestion = ({location, setMarker}) => {
const { area, city, address } = location;
return (
<div className="suggestion" onClick={() => setMarker(location)}>
<i className="fas fa-map-marker-alt fa-2x"></i>
<div className="address">
<h3 className="location-title">{`${area}, ${city}`}</h3>
<p className="location-desc">{address}</p>
</div>
</div>
);
};

Why redux onclick action in reactjs app isnt working?

Here is simplified code:
-action.js
export const removeVisitedUser = id => async dispatch => {
try {
const visited = JSON.parse(localStorage.getItem(`visited_`)) || [];
const filtered = visited.filter(i => i !== id);
debugger
dispatch({type: REMOVE_VISITED_USER, payload: {filtered}})
} catch (err) {
dispatch({ type: ERROR_REMOVE_VISITED_USER }, err)
}
}
export const errorRemoveVisitedUser = () => ({
type: ERROR_REMOVE_VISITED_USER
});
-reducer.js
import { REMOVE_VISITED_USER } from "./action";
const initialState = {
visited: JSON.parse(localStorage.getItem(`visited_`)) || [],
};
export default (state = initialState, action) => {
const { visited } = state;
const { type, payload } = action;
switch (type) {
case REMOVE_VISITED_USER: {
return { ...state, visited: payload.filtered};
};
-reactComponent.js
const reactComponent= ({
visited: { visited},
removeVisitedUser,
}) => {
return (
<div className="visited-list">
<div className="visited-list__items">
<MyLogo />
{visited.map((i, ind) => (
<div
key={ind}
onClick={() => removeVisitedUser(i._id)}
className={`visited-list__item ${active(ind)}`}
>
{!i.img
? <i className="fa fa-user-circle" />
: <div style={{ backgroundImage: `url(${avatarGetter(i.img)})` }} />}
</div>
))}
</div>
</div>
);
};
You can see debugger in removing action, as i know it should run when I click on any visited user div, but it didnt, so action didnt work too.
I spend a lot of time to understand how it should work properly, but still didnt figure out.
Thank you.
I cant see you using mapDispatchToProps anywhere. can you provide this code? or if you don't have it you'll need to add it so that react knows to use it as a prop

Custom react hook triggers api call multiple times

I cannot figure out how to handle my function components calling my api repeatedly. I have two components which retrieve data, one of them calls the api twice. Once before the second component once after.
I am using a custom react hook and axios get method to retrieve the data. My two components are are nested. The first component when loads and fetches data. Inside this component is a child component which when renders it fetches data right before passing the first set of data as props to another child component. When it completes loading it reloads the first child component which again calls the api for data. I understand the function components reload on state change. I would be happy for it to not call the api a second time. Is there a way to check if it already has data and bypass the api call?
Custom hook to retrieve data
import React, { useState, useEffect, useReducer } from "react";
import axios from "axios";
const dataFetchReducer = (state, action) => {
switch (action.type) {
case "FETCH_INIT":
return { ...state, isLoading: true, hasErrored: false };
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: action.payload
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
hasErrored: true,
errorMessage: "Data Retrieve Failure"
};
case "REPLACE_DATA":
// The record passed (state.data) must have the attribute "id"
const newData = state.data.map(rec => {
return rec.id === action.replacerecord.id ? action.replacerecord : rec;
});
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: newData
};
default:
throw new Error();
}
};
const useAxiosFetch = (initialUrl, initialData) => {
const [url] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
hasErrored: false,
errorMessage: "",
data: initialData
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: "FETCH_INIT" });
try {
let result = await axios.get(url);
if (!didCancel) {
dispatch({ type: "FETCH_SUCCESS", payload: result.data });
}
} catch (err) {
if (!didCancel) {
dispatch({ type: "FETCH_FAILURE" });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
const updateDataRecord = record => {
dispatch({
type: "REPLACE_DATA",
replacerecord: record
});
};
return { ...state, updateDataRecord };
};
export default useAxiosFetch;
Main component which renders the "CompaniesDropdown" twice inside
CompaniesDropdown is one of three dropdowns within the ListFilterContainer component but the only one which calls the api more than once. The other two dropdowns load by selection of the CompaniesDropdown.
import React, { useMemo, useEffect, useContext } from "react";
import InvoiceList from "../src/Components/Lists/InvoiceList";
import useAxiosFetch from "../src/useAxiosFetch";
import { ConfigContext } from "./_app";
import ListFilterContainer from "../src/Components/Filters/InvoiceFilters";
// import "../css/ListView.css";
const Invoices = props => {
const context = useContext(ConfigContext);
useEffect(() => {
document.title = "Captive Billing :: Invoices";
});
const {
data,
isLoading,
hasErrored,
errorMessage,
updateDataRecord
} = useAxiosFetch("https://localhost:44394/Invoice/GetInvoices/false", []);
const newInvoicesList = useMemo(
() => data
// .filter(
// ({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
// )
// .sort(function(a, b) {
// if (a.firstName < b.firstName) {
// return -1;
// }
// if (a.firstName > b.firstName) {
// return 1;
// }
// return 0;
// }),
// [speakingSaturday, speakingSunday, data]
);
const invoices = isLoading ? [] : newInvoicesList;
if (hasErrored)
return (
<div>
{errorMessage} "Make sure you have launched "npm run json-server"
</div>
);
if (isLoading) return <div>Loading...</div>;
const dataProps = {
data: invoices,
titlefield: "invoiceNumber",
titleHeader: "Invoice Number:",
childPathRoot: "invoiceDetail",
childIdField: "invoiceId",
childDataCollection: "invoiceData"
};
var divStyle = {
height: context.windowHeight - 100 + "px"
};
return (
<main>
<ListFilterContainer />
<section style={divStyle} id="invoices" className="card-container">
<InvoiceList data={dataProps} />
</section>
</main>
);
};
Invoices.getInitialProps = async ({ req }) => {
const isServer = !!req;
return { isServer };
};
export default Invoices;
Actual result is described above. My main concern is to not have the api calls more than once.
Here is some additional code to help. It is the filter control mentioned above. It, as you will notice really just contains dropdowns and a text box. The first dropdown is the one that calls the api twice. The second two are not visible until that one is selected.
import React, { useState, useMemo } from "react";
import CompaniesDropdown from "../Dropdowns/CompaniesDropdown";
import LocationsDropdown from "../Dropdowns/LocationsDropdown";
import AccountsDropdown from "../Dropdowns/AccountsDropdown";
import Search from "./SearchFilter/SearchFilter";
const InvoiceFilters = props => {
const [company, setCompany] = useState("");
const [location, setLocation] = useState(undefined);
const [account, setAccount] = useState(undefined);
const handleClientChange = clientValue => {
setCompany(clientValue);
};
const handleLocationsChange = locationValue => {
setLocation(locationValue);
};
const handleAccountsChange = AccountValue => {
setAccount(AccountValue);
};
return (
<section className="filter-container mb-3">
<div className="form-row">
<div className="col-auto">
<CompaniesDropdown change={e => handleClientChange(e)} />
</div>
<div className="col-auto">
<LocationsDropdown
selectedCompany={company}
change={e => handleLocationsChange(e)}
/>
</div>
<div className="col-auto">
<AccountsDropdown
selectedCompany={company}
change={e => handleAccountsChange(e)}
/>
</div>
<div className="col-auto">
<Search />
</div>
</div>
</section>
);
};
InvoiceFilters.getInitialProps = async ({ req }) => {
const isServer = !!req;
return { isServer };
};
export default InvoiceFilters;
Also the datalist
import React from "react";
import Link from "next/link";
import InvoiceListRecord from "./InvoiceListRecord";
const InvoiceList = props => {
let dataCollection = props.data.data;
return dataCollection.length == 0 ? "" : dataCollection.map((item, index) => {
return (
<section key={"item-" + index} className="card text-left mb-3">
<header className="card-header">
<span className="pr-1">{props.data.titleHeader}</span>
<Link
href={
"/" +
props.data.childPathRoot +
"?invoiceId=" +
item[props.data.childIdField]
}
as={
"/" +
props.data.childPathRoot +
"/" +
item[props.data.childIdField]
}
>
<a>{item[props.data.titlefield]}</a>
</Link>{" "}
</header>
<div className="card-body">
<div className="row">
<InvoiceListRecord
data={item}
childDataCollection={props.data.childDataCollection}
/>
</div>
</div>
</section>
);
});
};
InvoiceList.getInitialProps = async ({ req }) => {
console.log("Get Intitial Props works: Invoices Page!");
const isServer = !!req;
return { isServer };
};
export default InvoiceList;
and the list items component.
import React from "react";
const InvoiceListRecord = props => {
var invoiceData = JSON.parse(props.data[props.childDataCollection]);
return invoiceData.map((invKey, index) => {
return (
<div className="col-3 mb-1" key={"item-data-" + index}>
<strong>{invKey.MappedFieldName}</strong>
<br />
{invKey.Value}
</div>
);
});
};
export default InvoiceListRecord;
The API is not called more than once if the url is the same. It just gets the value from data variable. The api call is not made again, unless the url changes.
I created an example from your code, changing all the unknown components to div. I added a console.log in the useEffect of the useAxiosFetch hook. And to re-render the component, I added a button to increment the count.
You'll see that the console.log from the hook is printed only once, even though the component re-renders on every button click. The value just comes from the data variable from the hook and the api call is not made again and again.

Deleting an item from my redux state gives multiple errors

I am having 2 issues:
Initially I can add clients to the empty array through action creators and my reducer. However, whenever I delete the items from the list and try to add new clients to it, it gives me an error: TypeError: Invalid attempt to spread non-iterable instance.
When I said I am deleting the items, what really happens is I create the clients, and then when I click on the delete button next to one of them, all of the clients delete. There is not error in the console, but I just want to delete the specific client with the corresponding id.
Here is my code!
Clients.js
import React, { Component } from 'react'
import AddClient from './AddClient'
import {connect} from 'react-redux'
import {deleteClient} from '../../store/actions/clientActions'
class Clients extends Component {
handleClick = (id) => {
console.log(id)
this.props.deleteClient(id)
}
render() {
const {clientList} = this.props
return (
<div className="container mt-5">
<h2>Here Are Your List of Clients...</h2>
{clientList && clientList.map(client => {
return(
<div key={client.id}>
<div>
Client Name: {client.name} | Client Price: {client.price}
<button onClick={() => {this.handleClick(client.id)}}>Delete</button>
</div>
</div>
)
})}
<AddClient/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
clientList : state.clients.clientList,
}
}
const mapDispatchToProps = (dispatch) => {
return{
deleteClient : (id) => dispatch(deleteClient(id))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Clients)
Actions:
export const addClient = (client) => {
return(dispatch, getState) => {
dispatch({type: 'ADD CLIENT', client})
}
}
export const deleteClient = (id) => {
return(dispatch, getState) => {
dispatch({type: 'DELETE CLIENT', id})
}
}
Reducer:
const initState = {
clientList: []
}
const clientReducer = (state = initState, action) => {
switch (action.type) {
case 'ADD CLIENT' :
action.client.id = Math.random();
let clientList = [...state.clientList, action.client];
clientList.sort((a, b) => a.name.localeCompare(b.name));
return {
clientList
};
case 'DELETE CLIENT' :
const id = action.id;
clientList = state.clientList.filter(client =>
{return client.id !== id});
return clientList;
default : return state;
}
}
export default clientReducer
Lastly, this is AddClient.js
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {addClient} from '../../store/actions/clientActions'
class AddClient extends Component {
state = {
id: null,
name: null,
price: null,
}
handleChange = (e) => {
this.setState({
[e.target.id] : e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addClient(this.state);
e.target.reset();
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit} className="mt-5">
<h3>Add a new client:</h3>
<label htmlFor="name">Client Name: </label>
<input type="text" id="name" onChange={this.handleChange}/><br/>
<label htmlFor="price">Client Price: </label>
<input type="text" id="price" onChange={this.handleChange}/> <br/>
<button className="btn btn-primary">Add Client</button>
</form>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
addClient: (client) => dispatch(addClient(client))
}
}
export default connect(null, mapDispatchToProps)(AddClient)
Thank you for all the help, I am fairly new to React and Redux. Let me know if there is any other code you would like to see.
Here's how you can accomplish the delete:
export const deleteClient = (id) => {
const index = find the index of the client you want to delete from the array
return(dispatch, getState) => {
dispatch({type: 'DELETE CLIENT', index})
}
}
case 'DELETE CLIENT' :
return {
...state,
clientList: [
...state.clientList.slice(0, action.index),
...state.clientList.slice(action.index + 1)
]
}
I figured it out, the problem is within my clientReducer.js
This needs to change:
case 'DELETE CLIENT' :
const id = action.id;
clientList = state.clientList.filter(client =>
{return client.id !== id});
return clientList;
to...
case 'DELETE CLIENT' :
const id = action.id;
let newClientList = state.clientList.filter(client => {
return id !== client.id;
})
return {clientList : newClientList};
case 'DELETE CLIENT' :
const id = action.id;
const clientList = state.clientList.filter(client =>
{return client.id !== id});
return {
...state,
clientList
}
You're currently returning just an array, instead of an object. Since that's probably the only thing you have in your redux store right now, it's not breaking (in the ADD action), but you probably want to apply the previous state first, then add your newly filtered clientlist to the state you're returning.

UIkit's modals in React: integration

I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like
export class Modal extends Component {
static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)
static show = name => {
const modal = Modal.getByName(name)
if (modal) modal.show()
}
static hide = name => {
const modal = Modal.getByName(name)
if (modal) modal.hide()
}
render() {
// a modal
}
}
this is used in this way
export const LoginFormModal = props => (
<Modal name="login-form" className="login-form-modal" hideClose>
<LoginForm />
</Modal>
)
and show/hide is called programmatically where needed (even redux's actions)
Modal.hide("login-form")
this is in a Redux action, like this
export const login = credentials => {
return dispatch => {
dispatch(showLoader())
API.authentication.login(
credentials,
response => {
setCurrentUser(
Object.assign({}, response.user, { user_id: response.user.id })
)
Modal.hide("login-form")
dispatch(loginSucceded(response))
dispatch(hideLoader())
dispatch(push("/"))
dispatch(fetchNotificationsCounter())
},
error => {
dispatch(loginFailed(error))
dispatch(hideLoader())
}
)
}
}
This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.
Anyone can lead me to how integrate the parts in a more react-appropriate way?
Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:
You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props
componentWillReceiveProps(nextProps) {
if (nextProps.modalopen !== this.props.modalopen) {
if (nextProps.modalopen) {
getByName(...).show()
} else {
getByName(...).hide()
}
}
}
(this is inside the Modal class)
The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:
For example, instead of exposing open() and close() methods on a
Dialog component, pass an isOpen prop to it.
So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:
ModalWindow - will react to state changes and render depending what's in store:
import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';
class ModalWindow extends React.Component {
renderModalTitle = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return 'Info';
case MODAL_ACTION.YES_OR_NO:
return 'Are you sure?';
default:
return '';
}
};
renderModalContent = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return <InfoContent />;
case MODAL_ACTION.YES_OR_NO:
return <YesOrNoContent />;
default:
return null;
}
};
render() {
return (
this.props.isModalVisible ?
<div>
<p>{this.renderTitle()}</p>
<div>
{this.renderModalContent()}
</div>
</div>
:
null
);
}
}
export default connect((state) => ({
modalAction: state.modal.modalAction,
isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);
modal reducer it will expose API to show/hide modal window in the application:
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
const INITIAL_STATE = {
isModalVisible: false,
modalAction: '',
};
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_MODAL:
return { ...state, isModalVisible: true, modalAction: action.modalAction };
case HIDE_MODAL:
return { ...state, isModalVisible: false };
default:
return state;
}
}
export const MODAL_ACTION = {
YES_OR_NO: 'YES_OR_NO',
INFO: 'INFO',
};
const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);
So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:
dispatch(showInformation())
...
dispatch(hideModal())
Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.
I use a combination of a hook and a component for this.
Hook:
import { useState } from "react";
import UIkit from "uikit";
export default function useModal() {
const [isOpen, setIsOpen] = useState(false);
const [ref, setRef] = useState(null);
const open = (e) => {
UIkit.modal(ref).show();
setIsOpen(true);
};
const close = (e) => {
UIkit.modal(ref).hide();
UIkit.modal(ref).$destroy(true);
setIsOpen(false);
};
return [setRef, isOpen, open, close];
}
Component:
import React, { forwardRef } from "react";
const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
<div
ref={ref}
data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
>
<div className="uk-modal-dialog uk-margin-auto-vertical">
<button
type="button"
className="uk-modal-close-default"
data-uk-icon="close"
onClick={close}
/>
{isOpen && children()}
</div>
</div>
));
export default Modal;
Consumption:
function Demo() {
const [ref, isOpen, open, close] = useModal();
return (
<div>
<button
type="button"
className="uk-button uk-button-default"
onClick={open}
>
upgrade
</button>
<Modal isOpen={isOpen} close={close} ref={ref} full>
{() => (
<div>
<div className="uk-modal-header">
<h2 className="uk-modal-title">title</h2>
</div>
<div className="uk-modal-body">
body
</div>
</div>
)}
</Modal>
</div>
);
}
Read more: https://reactjs.org/docs/integrating-with-other-libraries.html

Resources