How can I get createSlice() to update my page? - javascript

I'm creating a chatroom app, and I've been learning Redux Toolkit as I do it. I currently have text input that will go to the messages data object when 'Send' is clicked; however, I can't seem to get the page to update and render new messages. Anyone know why?
Reducer:
import { createSlice, current } from '#reduxjs/toolkit'
import chatData from '../../components/chatData'
export const chatSlice = createSlice({
name: 'chat',
initialState: chatData[0],
reducers: {
addChat: (state, action) => {
if (action.payload !== '') {
state.messages.push({
avatar: "#",
username: "",
message: action.payload
})
console.log('after', current(state))
}
console.log(action.payload);
},
},
});
export const { addChat } = chatSlice.actions
export const selectChat = state => state.messages
export default chatSlice.reducer
JSX:
<div>
{props.chatData.messages.map((val, i) => {
return <MessageBox username={val.username} avatar={val.avatar} message={val.message} />
}
</div>
The state does update according to Redux DevTools and some console logging, but again, the page will not update accordingly.

If the state did update in the store then I believe the issue is caused by your component:
You did not map the state to your component correctly, maybe typos?
The component’s shouldComponentUpdate method was modified to prevent rerendering of the component.

Related

Why my storage variable doesn't load when my functional component mounts?

I want to save my store(array of todos) in localStorage when my functional component unmounts. And when my component mounts (for example when App refreshes), it should read that variable from localStorage.
But why when I refresh the app, previous data doesn't load?
This is a part of my code:
function App() {
const storeContents = useSelector(state => state.todo);
const dispatch = useDispatch();
useEffect(() => {
const todos = localStorage.getItem("dd");
if(todos){
dispatch({type: "todos/loadTodos", payload: JSON.parse(todos)})
}
return () => {
localStorage.setItem("dd", JSON.stringify(storeContents));
}
// eslint-disable-next-line
},[])
.
.
My slice:
const todoSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
loadTodos(state, action){
return action.payload;
},
.
.
I checked this question and used JSON.stringify and JSON.parse but it doesn't work again: React Redux Toolkit: How can I can store my initialState to localStorage using createSlice?
Link to complete code: https://github.com/arman-ebrahimi/TodosApp
Link to output: https://dapper-basbousa-c1c0e6.netlify.app/
Issue
The only place you update the local storage is in App in that useEffect's clean-up function. The problem is that App will never be unmounted as it's your main component, and it's not rendered conditionally. When someone closes its tab or refreshes the page, you are not in React's component lifecycle anymore.
Solution
What you can do that would simplify your code is to change initialState in todoSlice to:
initialState: localStorage.getItem("dd") ? JSON.parse(localStorage.getItem("dd")) : []
Change your useEffect in App.js as below to update the localStorage after each change:
useEffect(() => {
localStorage.setItem("dd", JSON.stringify(storeContents));
}, [storeContents]);

Infinite re-render using Zustand

I'm coming from redux + redux-saga and class component, everything went well when using componentDidMount in class component. Dispatching action to fetch api works well without duplicate request.
I've been learning functional component for a while, and decided to use Zustand to replace my redux-saga to handle my state management process. I've able to set state A from state B in reducer just by calling the action creators and the state get updated.
First of all, here's my react functional component code so far:
HomeContainer
import { useEffect } from "react";
import { appStore } from "../App/store";
export default function HomeContainer(props: any): any {
const getCarouselData = appStore((state: any) => state.getCarousels);
const carousels = appStore((state: any) => state.carousels);
useEffect(() => {
if (carousels.length === 0) {
getCarouselData();
}
}, [carousels, getCarouselData]);
console.log("carousels", carousels);
return <p>Home Container</p>;
}
Loading Slice
const loadingSlice = (set: any, get: any) => ({
loading: false,
setLoading: (isLoading: boolean) => {
set((state: any) => ({ ...state, loading: isLoading }));
},
});
export default loadingSlice;
App Store
import create from "zustand";
import homeSlice from "../Home/store";
import loadingSlice from "../Layout/state";
export const appStore = create((set: any, get: any) => ({
...loadingSlice(set, get),
...homeSlice(set, get),
}));
Coming to Zustand, it seems like the behaviour is different than Redux. I'm trying to update the boolean value of loading indicator with this code below:
import create, { useStore } from "zustand";
import axios from "axios";
import { appStore } from "../App/store";
const homeSlice = (set: any, get: any) => ({
carousels: [],
getCarousels: () => {
appStore.getState().setLoading(true);
axios
.get("api-endpoint")
.then((res) => {
set((state: any) => ({
...state,
carousels: res.data,
}));
})
.catch((err) => {
console.log(err);
});
appStore.getState().setLoading(true);
},
});
export default homeSlice;
The state is changing, the dialog is showing, but the component keeps re-render until maximum update depth exceeded. I have no idea why is this happening. How can I update state from method inside a state without re-rendering the component?
Any help will be much appreciated.
Thank you.
Update
The new instance of getCarousels is not created because of the dispatch since the create callback is called only once to set the initial state, then the updates are made on this state.
Original answer
Your global reducer is calling homeSlice(set, get) on each dispatch (through set). This call creates a new instance of getCarousels which is passed as a dependency in your useEffect array which causes an infinite re-rendering.
Your HomeContainer will call the initial getCarousels, which calls setLoading which will trigger the state update (through set) with a new getCarousels. The state being updated will cause the appStore hook to re-render the HomeContainer component with a new instance of getCarousels triggering the effect again, in an infinite loop.
This can be solved by removing the getCarouselsData from the useEffect dependency array or using a ref to store it (like in the example from the Zustand readme) this way :
const carousels = appStore((state: any) => state.carousels);
const getCarouselsRef = useRef(appStore.getState().getCarousels)
useEffect(() => appStore.subscribe(
state => (getCarouselsRef.current = state.getCarousels)
), [])
useEffect(() => {
if (carousels.length === 0) {
getCarouselsRef.current();
}
}, [carousels]); // adding getCarouselsRef here has no effect

How to access redux-toolkit reducer/action from a class component using react-redux connect()?

I have a redux-toolkit store at store.js.
import { configureStore } from '#reduxjs/toolkit';
import productCurrency from './stateSlices/productCurrency';
const Store = configureStore({
reducer: {
productCurrency: productCurrency,
},
})
export default Store;
The createslice() function itself is in a different file and looks like this below.
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
value: '$',
}
export const productCurrency = createSlice({
name: 'productCurrency',
initialState,
reducers: {
setproductCurrency(state, newState) {
state.value = newState
},
},
})
export const { setproductCurrency } = productCurrency.actions;
export default productCurrency.reducer;
My issue is that I have a class component NavSection that needs to access the initial state and the reducer action setproductCurrency() to change the state. I am trying to use the react-redux connect() function to accomplish that.
const mapStateToProps = (state) => {
const { productCurrency } = state
return { productCurrency: productCurrency.value }
}
const mapDispatchToProps = (dispatch) => {
return {
setproductCurrency: () => dispatch(setproductCurrency()),
dispatch,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NavSection);
Now, I am able to access the state by ussing this.props.productCurrency. Yet, if I try to access the setproductCurrency() by ussing this.props.setproductCurrency()... Chrome console gives me an error that "this.props.setproductCurrency() is not a function".
Is there a way of fixing this, or am I trying to do something impossible?
UPDATE #1
I think I just moved the ball in the right direction. I changed the onClick function to be an arrow function as shown below.
onClick={() => this.props.setproductCurrency('A$')}
Now, setproductCurrency() is considered a function, but it returns a different error when I click the button...
Objects are not valid as a React child (found: object with keys {type, payload}). If you meant to render a collection of children, use an array instead.
Why would this function now return an object? It is supposed to change the state and trigger a re-render of the page so that the class component can access the newly changed state.
To be clear, RTK has nothing to do with React-Redux, connect, or mapDispatch :)
The current error of "Objects are not valid as a React child" is because your reducer is wrong. A reducer's signature is not (state, newState). It's (state, action). So, your line state.value = newStateis reallystate.value = action`, and that's assigning the entire Redux action object as a value into the state. That's definitely not correct conceptually.
Instead, you need state.value = action.payload.

React-Redux component does not reflect changes in Redux Store

I am new to Redux and i've been having a hard time rendering changes made to the store. I've been using Redux-DevTools to explore state changes and here is my problem.
I have a sidebar which has expanded state as true/false. Initial state is below.
{expanded: false}
During the toggle action i trigger store.dispatch to change the state to true.
In React-DevTools i could see that my state is being changed, the console also logs the change when executed from within my sidenav component.
I have a home page which is able to fetch the initial state of the store, however the actions from sidenav which updates the store doesn't re-render(props val dint change) the home page.
I strongly feel the issue is related to this SO post but not able to get around the wrapped component concept. Any advice.
React Redux - changes aren't reflected in component
Below is code.
Redux Store
const rootReducer = combineReducers({
sideBarReducerMain: sidebarReducer })
export const configurestore = () => {
const store = createStore(rootReducer, composeWithDevTools(
));
return store; }
Action generators
export const onExpand = {
type: 'EXPAND',
payload: {
expanded: true
}
}
export const onCollapse = {
type: 'COLLAPSE',
payload: {
expanded: false
}
}
Reducer
export const sidebarReducer = (state = {expanded:false},action) => {
switch (action.type) {
case 'EXPAND':
return Object.assign({}, state, {expanded: true})
case 'COLLAPSE':
return Object.assign({}, state, {expanded: false})
default:
return state;
}
}
SideBar Toggle (The console logs the new state on every toggle)
onToggle = (expanded) => {
expanded ? store.dispatch(onExpand):store.dispatch(onCollapse)
this.setState({ expanded: expanded });
//outputs the state change looks fine.
console.log("State of store :: " + store.getState());
};
Home page
import React from 'react';
import styled from 'styled-components'
import Main from './MainAlign'
import { connect } from 'react-redux'
class HomePage extends React.Component {
getExpansionState() {
console.log("From render" + this.props.expandedState)
return this.props.expandedState
}
componentWillReceiveProps(nextProps) {
console.log("Looks like this never gets called :( " + this.props.expandedState)
}
render(props) {
return (
<div>
<Main expanded={this.getExpansionState()}>
<h1>Welcome</h1>
<p>This is my site. Take a look around!</p>
</Main>
</div>
)
}
}
const mapStateToProps = state => {
console.log("New state " + state.expanded)
return {
expandedState: state.expanded
}
}
export default connect(mapStateToProps)(HomePage);
REDUX DevTools
You need to declare action creators (functions) which return actions (objects):
export const onExpand = () => ({
type: "EXPAND",
payload: {
expanded: true
}
});
Then instead of calling store.dispatch, you should connect the action creator:
withRouter(connect(null, { onExpand, onCollapse })(SideBar))
And invoke it in your component:
if (expanded) {
this.props.onExpand();
} else {
this.props.onCollapse();
}
Working code:
Misc
this.setState({ expanded: expanded });
console.log("FROM SIDENAV" + this.state.expanded);
setState is asynchronous so you need use the callback to access the updated value:
this.setState({ expanded }, () => console.log("FROM SIDENAV" + this.state.expanded));
Also, there's no need to replicate the redux state in local component (single source of truth).
You can also combine onExpand and onCollapse into a single onToggle function and perform the toggle in reducer: expanded: !state.expanded, or pass the expanded flag as payload.
Change your mapStateToProps function like that:
const mapStateToProps = state => {
console.log("New state " + state.expanded)
return {
expandedState: state.sideBarReducerMain.expanded
}
}
You have a sideBarReducerMain state in your root,global state. This sideBarRecuerMain state has an expanded value not the global one.
Also, you don't need a payload for your action creators since you don't use them. Just a type is enough for your logic.

View doesn't want to re-render after store dispatch is called in Redux (React)

I need to change text input value after the state in redux has been changed (re-render the view). That means everything from text input will be stored in redux store (after each character) and re-rendered back to the input. If I use setState without redux, the view is changed successfully but also if I use this.forceUpdate() method for force re-render in handleChange function.
I think that's problem of the fact, that state in reducer isn't changed properly. I googled many way to do it but nothing doesn't worked for me.
Reduced code here:
'use strict';
import * as React from "react";
import { createStore } from 'redux';
interface State
{
}
interface Props
{
}
function cardReducer(state = {redirect : false, cardId : ""}, action) {
console.log("reducer action: " + JSON.stringify(action));
switch (action.type) {
case 'TYPING':
return Object.assign({}, state, {
redirect : false,
cardId : action.cardId
});
default:
return state
}
}
const store = createStore(cardReducer);
console.log("STORE state: " + JSON.stringify(store.getState()));
export class Home extends React.Component<Props, State> {
constructor(props : any)
{
super(props);
}
handleChange = (e) => {
store.dispatch({ type : 'TYPING', cardId : store.getState().cardId + e.target.value});
};
render()
{
return (
<div className={"container cardwrapper offset-md-2 col-md-8"}>
<form>
<input value={store.getState().cardId} onChange={this.handleChange} id={"card_id"} className={"command"} type="text" autoFocus autoComplete={"off"}/>
</form>
);
}
}
}
You're missing connect and mapStateToProps (Google these two). Doing store.getState() in the component is not how React listens for updates.
After getting the data from store.getState(), you need to set it on your state as well for your component to be re rendered.
You can do it as follows:
Put cardId on your state inside the constructor as follows:
this.state {
cardId: ""
}
Then on getting update from store, set the state as:
this.setState({
cardId: store.getState().cardId;
});

Categories

Resources