immutable react reducer state is not updating - reactjs

I am trying to create a simple website using react-redux and the immutable-assign library (instead of immutable) to handle my state. (documentation for immutable-assign: https://github.com/engineforce/ImmutableAssign)
I've made solutions with both the 'immutable' and 'immutable-assign' libraries, but neither work (code for immutable solution is commented out in the reducer below. No matter which changes I make, the state never changes, and the values are never assigned to menuItems
The setMenu(newMenu) function is currently called with dummydata in the form of a list of arrays in the following format:
menuItems: {
id: "113",
foodItem: "tesatewr",
description: "gfdgsdfsdf",
price: 999
}
The reducer:
import { iassign } from 'immutable-assign'
export function setMenu(newMenu) {return {type: 'SET_MENU_ITEMS', newMenu}}
const initialState = {
date: 'test',
menuId: 'test',
menuItems: []
}
function menuViewReducer(state = initialState, action){
switch(action.type){
case 'SET_MENU_ITEMS':
var itemList = iassign(
state,
function (n) { n.push('testtest'); return n}
)
return state.set(['menuItems'], itemList)
default:
return state
}
}
/* CODE FOR IMMUTABLE
function menuViewReducer(state = fromJS(initialState), action){
switch(action.type){
case 'SET_MENU_ITEMS':
return state.updateIn(['menuItems'], (menuItems) => menuItems.push(fromJS(action.newMenu.menuItems)))
default:
return state
}
} */
export const menuSelector = {
date: state => state.menuViewList.date,
menuId: state => state.menuViewList.menuId,
menuItems: state => state.menuViewList.menuItems
}
export default menuViewReducer
Render function:
render(){
return (
<div>
Test data here: {this.props.menuItems}
<ul className="menuViewList">{ this.mapMenuItemsToListElements() }</ul>
<button
onClick={() => this.mapMenuItemsToListElements()}> get data
</button>
</div>
)
}

It's really hard to figure out what's not working from this code. The best I can do is give you some debugging tips:
First off, are you getting any errors? If yes, that seems like a good place to start.
Otherwise, try to narrow down where the problem is occurring.
Are you sure your reducer is actually getting called?
I would try putting a console.log right after your case 'SET_MENU_ITEMS': so you know when your code is being run.
If it's not:
The problem could be a number of things:
Your reducer isn't connected to your store properly
You're not properly dispatching actions to your store
The actions you're dispatching don't have their type property properly set.
If it is:
The problem could be a number of different things. Some that I can think of:
Your state isn't being updated (properly). Try logging the state at the start of your reducer and your new state right before you return it. Or consider using redux-devtools to inspect your state.
Your view isn't getting updated. Maybe your component isn't connected to your store properly.

I found the error and as Simon pointed out, its hard to find from my submitted code.
I was calling setMenu(newMenu) in a generator function, so I should have called it like this:
yield put(setMenu(newMenu))
instead of
setMenu(newMenu)

Related

A react `useReducer` with some dependencies

i'm creating a react component: TxtEditor
Inside the editor, there is a useReducer hook for manipulating the text:
APPEND => to alter the current text.
UPPERCASE => to convert uppercase letter.
But the reducer function is not a pure function. There are some dependencies:
props.disabled => ignores all the reducer actions.
props.onTxtChanged => will be executed after the text has modified.
So I created the reducer function inside the function component.
This is a problem for useReducer because each time the component rendered, a new identical function with different by reference always re-created.
Thus making useReducer executing the reducer function twice on next render -and- triggering props.onTxtChanged twice too. I don't know why react doing this.
Then to solve the problem, I wrapped the reducer function with useCallback.
It seem be working, but NOT. Because the props.onTxtChanged might be passed by user with an inline function. And the function always be re-created (identical but different by reference), thus making useCallback useless.
And finally I created a reducer function outside the function component.
The function is always the same by reference and making useReducer working properly.
To inject the dependencies I made a HACK like this:
const [state, dispatch] = useReducer(txtReducer, /*initialState: */{
text : 'hello',
props : props, // HACK: a dependency is injected here
});
state.props = props; // HACK: a dependency is updated here
So the props can be accessed in the reducer function:
const txtReducer = (state, action) => {
const props = state.props;
if (props.disabled) return state; // disabled => no change
}
It's working but it contain a hack.
I want the professional way doing this stuff.
Do you have any suggestion?
See the complete sandbox code here
This is what I would do:
function TxtEditor(props) {
const [state, dispatch] = useReducer(txtReducer, { text : 'hello'});
// Wait for the state to change and only then emmit a text change
useEffect(() => {
props.onTextChange(state.text);
}, [state])
return <input onInput={handleInput} />
function handleInput() {
if (props.disabled) return; // <-- just don't fire an update
// [...] call to your reducer
}
}
Reducers are just a useState with a bit of logic. So only let them handle a (singular) state and don't make it responsible for many things at once. Also they should only be responsible for the actual state logic, not something outside, like if the text box is disabled or not.
A potential solution to the desired hook described in the comments of this post:
I hope you don't mind the typescript. I just find it easier to work with.
enum TextProcessorMode {
APPEND,
}
interface TextProcessorActionOptions {
disabled?: boolean;
mode: TextProcessorMode;
}
interface TextProcesserAction {
newText: string;
options: TextProcessorActionOptions;
/**
* Is called when the text was successfully processed.
*/
onChange(text: string): void;
}
export default function textProcessorReducer(
state: string,
action: TextProcesserAction
): string {
if (action.options.disabled) return state;
let newState: string;
switch (action.options.mode) {
case TextProcessorMode.APPEND:
newState = state + action.newText;
break;
// Handle other modes
}
action.onChange(newState);
return newState;
}

React DOM not updated when prop from redux store changes

This is driving me crazy for hours now... I have a module that displays a list that is fetched from a server and loaded into the redux store on button press. That works properly. I mention this as this is the reason why I don't understand the following behavior.
This object array from the store is mapped into my component with
const mapStateToProps = (state) => {
return {
extracted_templates: state.extracted_templates
}
}
And used in the render() as follows... I removed some other DOM parts to keep it simple
render(){
return(
<div className="main-container">
{Object.values(this.props.extracted_templates).length > 0 ?
<ExtractedTemplatesList templates={Object.entries(this.props.extracted_templates)} clickHandler={this.clickHandler} /> : '' }
</div>
);
}
The clickHandler modifies the store using the same action as the fetch function uses.
clickHandler(action, id, parent){
console.log(action+" "+parent)
switch(action){
case 'dismiss':
let new_template_list = this.props.extracted_templates
delete new_template_list[id]
// console.log(new_template_list)
this.props.dispatch(setExtractedTemplates(new_template_list))
break;
default:
break;
}
}
Everything is called correctly, the store updates correctly (as I can see in my web-dev console) but this time the DOM doesn't get updated.
For completeness, here's the action and the reducer implementation
action:
export const setExtractedTemplates = (templates) => ({
type: actions.SET_EXTRACTED_TEMPLATES,
payload: templates
});
reducer:
case actions.SET_EXTRACTED_TEMPLATES:
console.log({action})
return {
...state,
extracted_templates: action.payload
}
You're mutating the existing data, and you're putting the exact same object back into the store:
let new_template_list = this.props.extracted_templates
delete new_template_list[id]
this.props.dispatch(setExtractedTemplates(new_template_list))
Both of those are bugs. You should never mutate data from the store, and the result of an action should be new data in the store.
This is one of the reasons why we recommend putting as much logic as possible into reducers. Also, you should be using our official Redux Toolkit package, which would both catch this accidental mutation here, and simplify the update logic in a reducer.
Try this:
clickHandler(action, id, parent){
console.log(action+" "+parent)
switch(action){
case 'dismiss':
let new_template_list = {...this.props.extracted_templates} //make a new copy
delete new_template_list[id]
// console.log(new_template_list)
this.props.dispatch(setExtractedTemplates(new_template_list))
break;
default:
break;
}
}
You modified the same object saved in the redux store. This is potentially dangerous because you changed the state without using a reducer. When React did the shallow comparison, it didn't see difference so UI was not updated. You can make a copy before save it to store.
Further more you can modify your reducer in this way:
case actions.SET_EXTRACTED_TEMPLATES:
console.log({action})
return {
...state,
extracted_templates: [...action.payload] //make a new copy
}

How update array in real time not after refresh page (Redux)

I'm doing to do list and want do the functional when you click on button "done" the text will be crossed out.
I done array with deals which have fields 'text' and 'isDone'. isDone by default is false, when on click I get all array with deals and text deal in which you click. Than I map array with deals that I get and compare text from click deal and text in all deals in array.If they the same I change isDone from false to true.
But it update if i refresh the page and I need that it updata on click.
I use redux-persist and all states put into localStorage
button
<button onClick={()=>this.props.done(this.props.deals,value.text)}>Done</button>
Action
export function done(newDeals,dealText){
return(dispatch) =>{
newDeals.map(value=>{
if(value.text === dealText){
value.isDone = !value.isDone
}
})
dispatch(doneDeal(newDeals));
}
}
export function doneDeal(newDeals){
return{
type: DONE,
newDeals
}
}
Reducer
export default function toDoList(state = initialState, action){
switch(action.type){
case DONE:
return {
...state, deals: action.newDeals
}
default:
return state
}
}
I delete code that have no sense for this example, but need more info please ask I will tell
Thank you!
You have to use mapStateToProps to get the recently updated state from Redux state.
All what you need is to wrap your component export with the following:
const mapStateToProps = state => ({
deals: state.deals
});
export default connect(mapStateToProps)(ComponentName);
By this you are getting the needed state data from the initialState you have defined in your reducer and the reference to these data is the "deals" which can be used as normal prop: this.props.deals in case of class component OR as parameter through descructureing ({deals}) in case of functional component.
I don't know the full structure of the component because you haven't added it but this is the correct way to get the state from redux.
To make things more clear for you, you can read more through this link:
https://react-redux.js.org/using-react-redux/connect-mapstate
UPDATE: I figured out your problem after adding your reply.
The problem is with your code here:
<button onClick={()=>this.props.done(this.props.deals,value.text)}>Done</button>
You are getting the deals directly from the Redux global state and when dispatching your action you are passing them directly in the dispatch method. So your component is not able to listen to any change happening to your component. You need to save the deals in your local state:
state = {
deals: this.props.deals
}
and change the onClick to the following:
<button onClick={()=>this.props.done(this.state.deals,value.text)}>Done</button>

Can I bind store's state with a component in react-redux?

I am Newbie in react-redux, I created a small CRUD application with react-redux, when I click over add button from a child component it takes data from all input boxes and updates my store's array with the help of Reducer.
const initialState = {
itemObjectArr:[] }
My Reducer updates this itemObjectArr, and send it to needy component with the help of connect(mapStateToProps)(NeedyChildComponent) function.
Now the real problem occurs when I try to print state.itemObjectArr on the console.
const mapStateToProps = (state)=>{
console.log(state.itemObjectArr);
return {itemObject:state.itemObjectArrs}}
Above code gives an error on console TypeError: Cannot read property 'itemObjectArrs' of undefined
and if I do console.log(state) it prints objects contained array easily without any error.
I searched for this error but not get anything important, I think this store's state is immutable and when it passes to mapStateToProps function as an argument then mapStateToProps checks changes in state and if it does not get any changes then it returns an error. maybe this is occurring here.
What does your reducer look like? Since the store is returned as undefined in your component, it sounds to me that you haven't defined a default case in your switch statement that returns the initial state before your update action is executed, like:
const myReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_ITEM_OBJECT_ARR': return { itemObjectArr: [ 1, 2, 3] }
default:
return initialState;
}
}

hello world reducer for react/redux

There are a few other hello world app questions with regard to react, but mine is specific to the reducer. Im not exactly sure what I should put in the reducer for my specific action.
Note*: I thought maybe i need to add a message: "" key value pair to my initial state and then declare a
var newState = Object.assign({}, state, {
message:"hello world"
});
into my if statement in the reducer, then dispatch it in the component, but that seems unneccesary since it should always print hello world, so hard coding seems more efficient. Hopefully there isn't too much clutter in this question as well.
Here is my component:
var HelloWorld = React.createClass({
helloWorld: function() {
this.props.dispatch(actions.printHello());
},
render: function() {
return (
<div className="HelloWorld">
<h1>Hello World!</h1>
</div>
);
}
});
var Container = connect()(HelloWorld);
module.exports = Container;
my action:
var $ = require("jquery")
var PRINT_HELLO = 'PRINT_HELLO';
var printHello = function() {
return {
type: GUESS_NUM
};
};
exports.PRINT_HELLO = PRINT_HELLO;
exports.printHello = printHello;
and reducer:
var actions = require('./actions');
var initialRepositoryState = {
type: null
};
var capstoneApp = function (state,action) {
state = state || initialRepositoryState;
if (action.type === actions.PRINT_HELLO) {
var newState = Object.assign({}, state, {
message:"hello world"
});
return newState;
}
};
I don't think you will need my index.js but I will provide if necessary.
Thanks in advance for input!
The reducer holds your state. So the way of thinking about it is "what are the parts of my program that can change?" And then from there, the next question is "What is the minimal amount of state needed to hold on to my program?"
From looking at your example, I get the impression that you are trying to build an application that sometimes displays "hello world" in the UI. Let me make a few more interactions to help describe all the pieces.
Initially, I'll help you create a program that has an empty label and a button. When you click on the button, it will display "hello world".
Okay, so to answer the first question: What can change? The app can either display "hello world" or nothing. We could store that in a couple of different ways. If the string is hard-coded, like you've alluded to above, then really you have a bool of show the message, or not.
And to the second question: one truthy value is pretty much the definition of a minimal state. So let's make a reducer:
var initialState = {
showMessage: false,
};
var reducer = function(state, action) {
var newState = Object.assign({}, state);
if (action.type === 'BUTTON_PRESS') {
newState.showMessage = !newState.showMessage;
}
return newState;
}
Okay, so now we've created a reducer that, when it gets the BUTTON_PRESS action, it flips the bit of its state.
Now, let's talk about how to hook that reducer up to redux. Right now, the reducer is a plain javascript function. We just need to pass that store into redux with the initial state. [createStore][1]
P.S. I normally write ES2015 code so there may be small typos in the commonJS import syntax
var redux = require('redux');
var store = redux.createStore(reducer, initialState);
The next part is to look at redux-react.
Redux-react is glue that works both ways in react. It connects data from the redux store to react props, and it connects react callbacks (such as a click) to redux actions.
So conceptually, it looks like this. You have a react component that has a button. When you click the button, we want to generate a 'BUTTON_PRESS' action. After this, your react component no longer cares what happens to BUTTON_PRESS. Its job is done. BUTTON_PRESS could do one of infinite things. Now, assume that redux-react does its job and gets the action passed to the reducer. The reducer computes its new logic and returns a new state. This new state has the value of showMessage. Then, redux-react does the other half of connecting and makes showMessage a prop for the component. Just to be clear, there is no explicit reason why the same react component has to respond to the state changed by your action. They could be different components.
To put it into bullet points, here is how the codeflow should work:
We create an initial store with showMessage = false
When creating the React component, we use connect to bind the showMessage to a prop, and to handle onClick of the button in the component to generate a 'BUTTON_PRESS' action.
Since showMessage is false, there is originally only a button present.
The user presses the button. React calls into the onClick handler
We use redux-react to dispatch a BUTTON_PRESS event
When redux gets an action, it calls the reducer with the current state and the action. The reducer is responsible for generating a new state in response to this action
The reducer sets showMessage to true
redux-react listens to store changes and when it changes it modifies the prop for the react component
The prop changes so react calls render()
Inside your render method you see that this.props.showMessage is true, so you display the message.
And here is how such a react component could be implemented. There are enough differences between React components in ES5 vs ES2015 that I'm just going to give you the ES2015 version and apologize again.
class HelloWorld extends Component {
render() {
const message = this.props.showMessage ? "Hello world!" : "";
return (
<div id="label">{message}</div>
<div id="button" onClick={this.props.handleOnClick}>Toggle the message </div>
);
}
}
HelloWorld.propTypes = {
showMessage: PropTypes.bool.isRequired,
handleOnClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
showMessage: state.showMessage,
});
const mapDispatchToProps = dispatch => ({
handleOnClick: dispatch({ type: 'BUTTON_PRESS', payload: '' })
});
export default connect(mapStateToProps, mapDispatchToProps)(HelloWorld);
You can keep reading the docs but I hope that helps explain what all the parts are. Let me know if anything isn't clear.
[1]: http://redux.js.org/docs/api/createStore.html

Resources