how to override state Redux - reactjs

I don't understand something in react-redux.
I have created a slice called Introduction look below:
import { createSlice } from "#reduxjs/toolkit";
import { IntroductionFields } from "../helpers/interface";
const initialState: IntroductionFields = {
fullName:'',
subtitle:'',
description:'',
location:'',
email:'',
portfolio: {name:'' , url:''},
project: {name: '' , url: ''},
learning: '',
collaborating: '',
else: '',
}
const Introduction = createSlice({
name: 'intro',
initialState,
reducers:{
update(state, actions){
const key = actions.payload.name;
const val = actions.payload.value;
state.fullName = val; // WORK
state = {...state, [key]: val} // NO WORK
console.log(actions.payload.name , " " , actions.payload.value);
},
}
})
export const IntroductionActions = Introduction.actions;
export default Introduction;
and I have two more components,
first component has fields (inputs) and every field has an onChange that calls the dispatch and uses update on the reducer that I created in the introduction slice and I send the key and value, see below.
const Intro: React.FC<Props> = ({ moveForward }) => {
const dispatch = useDispatch();
const changeHandler = (event: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => {
const {name , value} = event.target;
dispatch(IntroductionActions.update({name, value}))
}
return (.... // HERE I HAVE INPUTS...)
}
In the second component I want to get the values from the Introduction slice so if I change some fields in Intro component, I want to see the changes in my Preview component.
import React, { useEffect } from 'react'
import classes from './Preview.module.scss';
import { useSelector } from 'react-redux';
import { RootState } from '../../../store/store';
const Preview = () => {
const introduction = useSelector((state:RootState) => state.intro);
return (
<div className={classes.previewContainer}>
{introduction.fullName && <h1>Hi! My name is {introduction.fullName}</h1>}
</div>
)
}
export default Preview
If you'll look to the first code section
you will see these two lines.
state.fullName = val; // WORK
state = {...state, [key]: val} // NO WORK
If I directly write into the field in state it works prefect, but if I try to do the second line it doesn't work...
I want it to be dynamic that is why I want to use the second line...

You can set the state like this since there is no need to copy the whole state to the new state.
update(state, actions){
const key = actions.payload.name;
const val = actions.payload.value;
state[key] = val;
},
The section Create a Redux State Slice will explain in depth how/why

Dispatch the action with object as payload
dispatch(IntroductionActions.update({fullName: name, subtitle: subtitle}))
and your reducer function will be like this
update(state, actions){
return ({...state, ...actions.payload})
}
Here based on the payload, state will get updated , here fullName and subtitle values will get updated.

Related

Update a react-redux state too fast does not take the modification into account

I have an object stored in react-redux which contains multiple sub-objects, like:
MyObject =
{
{ id: 1, name: "object 1"}
{ id: 2, name: "object 2"}
...
}
This object can be updated very quickly multiple times, for example with a function like this:
function modifyMyObject() {
//Load the object from Redux and create a clone to be modified
let myObject = JSON.parse(JSON.stringify(this.props.myObject))
//Change the properties of my object
...
//Update the object on Redux
this.props.setMyObject(myObject)
}
However I noticed that if I call modifyMyObject() very quickly with different modifications, the object is not updated properly.
I guess that the state in redux does not have time to be updated before I try to make a new modification.
Here is the object slice :
import {createSlice} from '#reduxjs/toolkit'
const initialState = {
value: {},
}
export const myObjectSlice = createSlice({
name: 'object',
initialState,
reducers: {
setMyObject: (state, action) => {
state.value = action.payload
},
},
})
// Action creators are generated for each case reducer function
export const { setMyObject } = myObjectSlice.actions
export default myObjectSlice.reducer
Is there a better way to handle these quick changes? Or do you have a suggestion to improve this code? Thank you!
I'm posting here the solution to this problem which was suggested by HåkenLid:
Method which is NOT working: in App.js, create a copy of the slice.state, modify it and save it to Redux:
function modifyMyObject() {
//Load the object from Redux and create a clone to be modified
let myObject = JSON.parse(JSON.stringify(this.props.myObject))
//Change the properties of my object
...
//Update the object on Redux
this.props.setMyObject(myObject)
}
The problem: the object is out of date when saved.
The solution: In app.js:
function modifyMyObject(newObject) {
this.props.updateMyObject(newObject) //call the function from the slice
}
In MyObjectSlice.js:
import {createSlice} from '#reduxjs/toolkit'
const initialState = {
value: {},
}
export const myObjectSlice = createSlice({
name: 'object',
initialState,
reducers: {
updateMyObject: (state, action) => {
let newObject = action.payload
//-> insert here the logic to update the object <-
...
state.value = newObject
},
},
})
// Action creators are generated for each case reducer function
export const { updateMyObject } = myObjectSlice.actions
export default myObjectSlice.reducer
Are you trying try/catch block to handle JSON.parse? I hope this below can help you:
function modifyMyObject() {
function parseJSONSafely(str) {
try {
return JSON.parse(str);
}
catch (e) {
console.err(e);
// Return a default object, or null based on use case.
return {}
}
}
//Load the object from Redux and create a clone to be modified
editedObject = parseJSONSafely(JSON.stringify(this.props.myObject))
if (editedObject !== {}) {
//Change the properties of my object
editedObject['proterty'] = /* ... */
//Update the object on Redux
this.props.setMyObject(editedObject)
}
}

update values in the Reducer in Redux

i got two values i.e.company and id from navigation.
let id = props.route.params.oved;
console.log("id-->",id);
let company = props.route.params.company;
console.log("company--->",company);
i got two values as a integer like this:--
id-->1
comapny-->465
Description of the image:---
if i am giving input 1 in that textInput and click on the card(lets say first card i.e.465 then i am getting those two values in navigation as in interger that i have mention above.so each time i am getting updated values.
i am getting updated values from navigation.
so i want to store those values in redux.
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
in the component create a function that updates the values
const updateReducer = () => {
dispatch(changeCompany(props.route.params.oved, props.route.params.company))
}
then call the function in react navigation lifecycle event
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
updateReducer()
});
return unsubscribe;
}, [navigation])
its possible that a better solution would be to update the reducer before the navigation happens and not pass the data in the params but rather pull it from redux but this is the answer to the question as asked

i could not edit data by using react-redux

I have an issue with update new data by using react-redux, Add and remove are working fine, but it will return null object when i want to edit one of the data.
I am not sure what stage cause wrong.
action.users.js
import { v4 as uuid } from 'uuid';
// ADD_USER
export const addUser = ({ username = '', location = '' } = {}) => ({
type: 'ADD_USER',
user: { id: uuid(), username, location },
});
//REMOVE_USER
export const removeUser = ({ id } = {}) => ({ type: 'REMOVE_USER', id });
//EDIT_USER
export const editUser = ({ id, updates } = {}) => ({
type: 'EDIT_USER',
id,
updates,
});
components.EditUserPage.js
import React from 'react';
import { connect } from 'react-redux';
import UserForm from './UserForm';
import { editUser } from '../actions/users';
const EditUserPage = props => {
return (
<div>
<UserForm
user={props.user}
onSubmit={user => {
props.dispatch(editUser(props.user.id, user));
props.history.push('/Users');
}}
/>
</div>
);
};
const mapStateToProps = (state, props) => {
return {
user: state.user.find(user => user.id === props.match.params.id),
};
};
export default connect(mapStateToProps)(EditUserPage);
reducers.users.js
const usersReducerDefaultState = [];
export default (state = usersReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_USER':
return [...state, action.user];
case 'REMOVE_USER':
return state.filter(({ id }) => id !== action.id);
case 'EDIT_USER':
return state.map(user => {
if (user.id === action.id) {
return {
...user,
...action.updates,
};
} else {
return user;
}
});
default:
return state;
}
};
The page should go to ./edit when i click one of the data and the input value will show the currently selected data in the userform component. it seems like going well at this stage, i change the input value and click the create button, the page back to /User, unfortunately, the selected data return null object. please help me. you answer will help me to jump out of this nightmare.
Change
props.dispatch(editUser(props.user.id, user));
to
props.dispatch(editUser({id: props.user.id, updates: user}));
You define the method signature of edit user as
function editUser ({ id, updates } = {}) {…}
That is, a function that takes one (optional) argument. That argument is expected to be an object with an id and updates property. (I don't think you should make the argument optional. It is needed for the rest of the function to work.)
However, you call the function with two arguments, presumable a number and an object. Also, if props.user.id ends up being undefined, then it will be replaced with the default value of {}, and id and updates will be undefined, but no error will occur (which is what you want, because you are passing the wrong type to the function).
Alternatively, you could define the method signature to take to positional arguments and not change the function call:
const editUser = (id, updates) => (…)
or, if your UserForm component includes id in the call to onSubmit:
const editUser = ({ id, ...updates }) => (…) // use destructuring to select the id from the argument
// call it like this:
props.dispatch(editUser({user}));

Filter function is not working in react/redux

I am newbie in react/redux and I try to do an app to manage my sentences.
I try to filter my sentences and it does it correct but when i delete some words from the input field, my state remains filtered.
this is the action.
export const filterWord = (e) => {
return {
type: 'FILTER_WORD',
e
}
}
this is the reducer:
case 'FILTER_WORD':
return state.filter((sentence) => sentence.word.includes(action.e.target.value));
Hope someone can give me an advice
The best way to handling filtering is not filter your reducer state based on the filter word but to do that in a selector
If you filter your state in a reducer, the next time you change a filter or remove it, you would not have the original state but a duplicate state
You can instead store the filterword in reducer
export const filterWord = (e) => {
return {
type: 'FILTER_WORD',
word: e.target.value,
}
}
const reducer = (state = { data: [], filterWord: null}) => {
...
case 'FILTER_WORD':
return {...state, filterWord: action.word};
...
}
Now in your mapStateToProps component you can filter the result for the user component
const mapStateToProps = (state) => {
return {
data: state.data.filter((sentence) => sentence.word.includes(state.filterWord));
}
}

Why PropTypes failed with Value is undefined

I cannot figure out why prototypes in my function component in React fails with this:
`index.js:1 Warning: Failed prop type: The prop `profiles` is marked as required in `Home`, but its value is `undefined`.`
The app is working fine and Profiles is defined and I'm using React-redux with hooks and maybe that causing the issue becasue I don't know actually what to do to make the PropTypes to work
My home where this come ups:
import React, { useEffect, useState } from "react";
import { Row, Col, Jumbotron, Container, Image } from "react-bootstrap";
import { ProfileMiddleware } from "../Store/Middleware";
import { PropTypes } from "prop-types";
import { useDispatch, useSelector } from "react-redux";
import { USERNAME } from "../Services/constAPI";
import Experiences from "../Components/Experiences/Experiences";
import { Spinner } from "../Components/Spinner/Spinner.js";
const Home = () => {
const dispatch = useDispatch();
const { profiles, displaySpinner } = useSelector(state => ({
profiles: state.ProfileReducer.profiles,
displaySpinner: state.ProfileReducer.displaySpinner
}));
useEffect(() => {
dispatch(ProfileMiddleware.getOneProfile(USERNAME));
}, [dispatch]);
return !profiles.object ? (
<>
<Jumbotron>
<Container>
<Row>
<Col md={6}>
<Image src={profiles.imageUrl} alt="profile" roundedCircle />
</Col>
<Col md={6}>
<h1>{profiles.firstname + " " + profiles.surname}</h1>
<h4>{profiles.title}</h4>
<h5>{profiles.area}</h5>
<p>{profiles.email}</p>
<p>{profiles.bio}</p>
</Col>
</Row>
<Spinner displaySpinner={displaySpinner} />
</Container>
</Jumbotron>
<Experiences />
</>
) : (
<h3 className="red-text mt-5">The profile is not available</h3>
);
};
Home.propTypes = {
profiles: PropTypes.object.isRequired
};
export default Home;
The reducer as I'm using Redux
import { ProfileActions } from "../Actions";
function ProfileReducer(
state = {
profiles: {},
displaySpinner: false
},
action
) {
console.log("data in action", action.data);
console.log("Action type", action.type);
switch (action.type) {
case ProfileActions.GET_ONE_PROFILE:
return {
...state,
displaySpinner: true
};
case ProfileActions.GET_ONE_PROFILE_SUCCESS:
return {
...state,
profiles: action.data,
displaySpinner: false
};
default:
return state;
}
}
export default ProfileReducer;
I can show else if necessary but the APP works but PropTypes saying profiles are undefined that I cannot understand.
You're not passing in any props to Home. If you were, it would look something like
const Home = (props) => {
Instead, you are getting profiles from your redux store. So simply change
Home.propTypes = {
profiles: PropTypes.object.isRequired
};
to
Home.propTypes = {};
profiles is not a prop being passed to Home. The proptypes for the Home component should be deleted.
You say in the comments:
I have to check the profiles to be an obj with PropTypes I'm trying to find a solution to make it works. If you know a solution please share an answer
If you can guarantee that in your selector that's even better, but there're ways to do so in the component.
So let's take a snippet of your code and do that:
const Home = () => {
const dispatch = useDispatch();
const { profiles, displaySpinner } = useSelector(state => ({
profiles: state.ProfileReducer.profiles,
displaySpinner: state.ProfileReducer.displaySpinner
}));
// note Array's and other variable are also objects
// so we need to do a special check
const isProfilesAnObject = profiles && profiles.constructor.name === 'Object';
// created outside of `every` loop, and ternary shortcut to empty array
const profilesKeys = isProfilesAnObject ? Object.keys(profiles) : [];
// check that profileKeys is a subset of REQUIRED_KEYS
// n.b. you need to define this somewhere
const isProfilesCorrect = REQUIRED_KEYS.every(requiredKey => profileKeys.includes(requiredKey))
useEffect(() => {
dispatch(ProfileMiddleware.getOneProfile(USERNAME));
}, [dispatch]);
return isProfilesCorrect ?
This should work like below:
const checkProfiles = profiles => {
const REQUIRED_KEYS = ['a', 'b', 'z', 'y']
const isProfilesAnObject = profiles && profiles.constructor.name === 'Object';
// created outside of `every` loop, and ternary shortcut to empty array
const profilesKeys = isProfilesAnObject ? Object.keys(profiles) : [];
// check that profileKeys is a subset of REQUIRED_KEYS
// n.b. you need to define this somewhere
const isProfilesCorrect = REQUIRED_KEYS.every(requiredKey => profilesKeys.includes(requiredKey))
return isProfilesCorrect;
}
const [profile1, profile2, profile3] = [{
'a': '10'
}, {
a: '10', b: '20', z: '260', y: '250'
},{
a: '10', b: '20', extra_key: 'I\'m being a little bit extra', z: '260', y: '250'
}]
// missing keys
console.log(`profile1 (with keys ${Object.keys(profile1)}) is: ${checkProfiles(profile1) ?'correct': 'incorrect'}`);
// just the right number of keys
console.log(`profile2 (with keys ${Object.keys(profile2)}) is: ${checkProfiles(profile2) ?'correct': 'incorrect'}`);
// doesn't check if there aren't extra keys
console.log(`profile3 (with keys ${Object.keys(profile3)}) is: ${checkProfiles(profile3) ?'correct': 'incorrect'}`);
There is also invariant which you can use for stricter checking. It throws an error if something is false, so you can put exact checking in there, as seen below.
(n.b. ignore the process and module objects I had to define, and note I'm using tiny-invariant for convenience )
const checkProfiles = profiles => {
const REQUIRED_KEYS = ['a', 'b', 'z', 'y']
const isProfilesAnObject = profiles && profiles.constructor.name === 'Object';
// created outside of `every` loop, and ternary shortcut to empty array
const profilesKeys = isProfilesAnObject ? Object.keys(profiles) : [];
// check that profileKeys is a subset of REQUIRED_KEYS
// n.b. you need to define this somewhere
const isProfilesCorrect = REQUIRED_KEYS.every(requiredKey => profilesKeys.includes(requiredKey))
// really need to make sure 'z' exists and is a string
invariant(typeof profiles.z === 'string', `Profiles is expected to have a key 'z' that is a string, but found ${JSON.stringify(profiles.z)}`)
return isProfilesCorrect;
}
console.log(checkProfiles({
a: 10,
z: 'valid'
}))
try {
checkProfiles({
a: 10,
z: 20
});
} catch (e) {
console.error(e);
}
<script>
var process = {
env: 'production'
}
var module = {
exports: {}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/tiny-invariant#1.2.0/dist/tiny-invariant.cjs.min.js">
</script>
Either of those should work depending on how seriously you need to make sure your variables exist and are correct
Actually, you didn't pass props, pay attention to:
const Home = () => {
You should write const Home = props => { or destruct the props in the beginning of your function component, like below:
const Home = ({ profiles }) => {
And then use it inside your execution context of the function component. Also, you can put the default value for your props like below:
const Home = ({ profiles = 'something' }) => {
The 'something' is a sample, it can be everything, or write like below at the end of your function component declaration:
Home.defaultProps = {
profiles: 'something'
};
I hope it helps you, but surely you should read the ReactJS docs a little bit more.

Resources