Redux: Component is not updating after state is updated [duplicate] - javascript

This question already has answers here:
How do I access store state in React Redux?
(8 answers)
Closed 1 year ago.
I've created the reducer and using it to change the state of my store. but as you can see in App.js whenever I click on button and update the state. it updates. I can see it in console. but component does not update. as you can see I have list of tracks there it is not updating. and if I make any changes to code because of that the component re-render I can see the new state after that. why is it not rendering automatically whenever the state updates.
Action
import * as actions from './actionTypes'
export const trackAdded = (title, artist, audioSrc, img) => {
return {
type: actions.TRACK_ADDED,
payload: {
title,
artist,
audioSrc,
img
}
}
}
Reducer
import * as actions from './actionTypes'
export default function reducer(state = [], action) {
switch (action.type) {
case actions.TRACK_ADDED:
return [
...state,
{
title: action.payload.title,
artist: action.payload.artist,
audioSrc: action.payload.audioSrc,
img: action.payload.img
}
]
default:
return state
}
}
App.js
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App() {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<div className="App">
{store.getState().map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
);
}
export default App;

The component will not update because the store.getState() inside of <div className="App"> will only run once when the component is called. There is no logic that exists that tells the component to rerun the store.getState(). If you want the component to receive updates when the store's state changes, you need to connect it to the store using react-redux's connect function or a useSelector hook.
As an example, if using the connect function, you can map the redux state to a react component's props. So if the component's props change, then the component will "react" to it's props changing. The mapping of redux's state to the components props happens in the mapStateToProps function, returning a prop tracks that is mapped to the component. Otherwise there is no reason for the component to update. Also note: in the example below, the store is connected to React through a Provider component, providing the store to child components that wish to connect to it.
import { Provider, connect } from 'react-redux'
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App(props) {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<Provider store={store}>
<div className="App">
{props.tracks.map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
</Provider>
);
}
const mapStateToProps = (state) => {
const tracks = state
return { tracks }
}
export default connect(mapStateToProps)(App);
Having said that, if you go the connect route, you might want to add a mapDispatchToProps function to the connect so you dont pass around the store everywhere. You can find that info in the redux docs, but that would be an answer to a different question.

This is happening because your component does not know that store has been updated, you can use something like this
useEffect(() => {
this.artistList = store.getState();
}, [store.getState()]); // kind of watcher of store.getState()
but this is definitely not recommended. You would want to use useSelector() from react-redux, which is much concise and recommended way to doing it.

Related

Props alternative to pass the component state to all the components of application in next.js

I want to pass the setState method of the component (SnackBar) to all the child components of the _app.js. If I pass the setState method of SnackBar to all the child components of _app.js then it will be a very tedious task. Because, there are approx 4 levels of hierarchy from _app.js to the single component node. It includes,
_app.js -> pages -> layouts -> sections -> components
The snippet of _app.js is here.
function MyApp({ Component, pageProps }) {
const [ toastOpen, setToastOpen ] = React.useState({
msg: '',
open: false
});
React.useEffect(() => {
pageProps = { ...pageProps, setToastOpen };
}, []);
return (
<React.Fragment>
<ToastMessage
message={ toastOpen.msg }
setOpenState={ setToastOpen }
openState={ toastOpen.open }
/>
<Component {...pageProps} />
</React.Fragment>
)
}
Is there any way that I can directly import the setToastOpen method in the child component and use it whenever I need it?
React have a special Feature called Context Api , using that you can skip the props chain passed into your components..
I recomend you to checkout below resources to learn about context Api -
https://reactjs.org/docs/context.html
https://www.freecodecamp.org/news/react-context-in-5-minutes
Example of ContextAPI
Create a seperate file for Context Toast-context.js , You can use any name you want.
import React, { useState } from "react"
const ToastContext = React.createContext({
message: "",
toastOpen: false,
toggleToast: () => { },
changeMessage: () => { }
})
export const ToastContextProvider = ({children}) => {
/*you need to use
this component to wrap App.js so that the App.js and all of its children
and their children components and so on will get the access to the
context*/
const [toastOpen, setToastOpen] = useState(false);
const [message, setMessage] = useState("");
const toggleToast = () => {
setToastOpen(true)
}
const changeMessage = (message) => {
setMessage(message);
}
return (
<ToastContext.Provider value={
toastOpen,
message,
toggleToast,
changeMessage
}>
{children}
</ToastContext.Provider>
)
}
now in the App.js file you need to wrap your components with ToastContextProvider component
import React, { useContext } from "react";
import { ToastContextProvider } from "./Toast-context";
import { ToastContext } from "./Toast-context";
function MyApp({ Component, pageProps }) {
const { message, toastOpen, toggleToast, changeMessage } =
useContext(ToastContext);
return (
<ToastContextProvider>
{toastOpen && <div className="toast">{message}</div>}
</ToastContextProvider>
);
}
just import the context using useContext Hook in any component you want. you don't need to wrap with <ToastContextProvider> in every component.
just use useContext hook and then you can see the state and as well as call the functions methods to change the state.
Also make sure to refer the above links to learn more about Context Api. Thank You

Using Context API with useState in React.js, any downsides?

I create a context and a provider as below. As you can see, I use useState() within my provider (for state) along with functions (all passed within an object as the value prop, allows for easy destructuring whatever I need in child components).
import React, { useState, createContext } from "react";
const CountContext = createContext(null);
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<CountContext.Provider value={{ count, incrementCount, decrementCount }}>
{children}
</CountContext.Provider>
);
};
export default CountContext;
I wrap my app within such a provider(s) at a higher location such as at index.js.
And consume the state using useContext() as below.
import React, { useContext } from "react";
import CountContext from "../contexts/CountContext";
import Incrementer from "./Incrementer";
import Decrementer from "./Decrementer";
const Counter = () => {
const { count } = useContext(CountContext);
return (
<div className="counter">
<div className="count">{count}</div>
<div className="controls">
<Decrementer />
<Incrementer />
</div>
</div>
);
};
export default Counter;
Everything is working just fine, and I find it easier to maintain things this way as compared to some of the other methods of (shared) state management.
CodeSandbox: https://codesandbox.io/s/react-usecontext-simplified-consumption-hhfz6
I am wondering if there is a fault or flaw here that I haven't noticed yet?
One of the key differences with other state management tools like Redux is performance.
Any child that uses a Context needs to be nested inside the ContextProvider component. Every time the ContextProvider state changes it will render, and all its (non-memoized) children will render too.
In contrast, when using Redux we connect each Component to the store, so each component will render only if the part of the state it is connect to changes.

searching/filtering a list with react/redux

I'm currently learning react and redux and stumbled into a problem i can't really get my head around. Trying to implement the same functionality
as in this article: https://medium.com/#yaoxiao1222/implementing-search-filter-a-list-on-redux-react-bb5de8d0a3ad but even though the data request from the rest api i'm working with is successfull i can't assign the local state in my component to my redux-state, in order to be able to filter my results. Heres my component:
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as fetchActions from '../../actions/fetchActions'
import Stafflist from './Stafflist'
class AboutPage extends React.Component {
constructor(props) {
super(props)
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
this.updateSearch = this.updateSearch.bind(this)
}
updateSearch(event) {
let newlyDisplayed = this.state.currentlyDisplayed.filter(
(post) => { 
return (
post.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
|| post.role.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
)}
)
console.log(newlyDisplayed)
this.setState({
search: event.target.value.substr(0, 20),
currentlyDisplayed: newlyDisplayed
})
}
render() {
return (
<div className="about-page">
<h1>About</h1>
<input type="text"
value={this.state.search}
onChange={this.updateSearch}
/>
//component for rendering my list of posts.
<Stafflist posts={this.state.currentlyDisplayed} />
</div>
)
}
}
// this is here i assign my api data to this.props.store.posts
function mapStateToProps(state, ownProps) {
return {
store: state
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(fetchActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutPage)
Comparing how i assign my stores state to my local component with how it works in the article, it seems to be done in the same way. Mine:
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
article:
this.state = {
searchTerm: '',
currentlyDisplayed: this.props.people
}
within react devtools i can see my data in as it should be in the store, but it won't work to assign it to my local state within the component in order to perform the filtering, and i don't really know how to debug this. My state in the local component just says
State
currentlyDisplayed: Array[0]
Empty array
also if i change
<Stafflist posts={this.state.currentlyDisplayed} />
to
<Stafflist posts={this.props.store.posts} />
the list renders as it should :)
Reducer:
import * as types from '../actions/actionTypes'
import initialState from './initialState'
export default function postReducer(state = initialState.posts, action) {
switch(action.type) {
case types.FETCH_POSTS_SUCCESS:
return action.posts.data.map(post => {
return {
id: post.id,
name: post.acf.name,
role: post.acf.role
}
})
default:
return state
}
}
Any ideas?
The problem with your code is that you do not handle how to get newly received props to your state. This means that when you receive the data from your api call only the props are updated while component state is not.
If you look carefully at the referenced article, in the onInputChange method they recalculate the state from the props whereas your updateState method only filters from the local state.
Setting the state in the constructor only ensures that the props are copied on the initial mount of the component. At that point in time the store only contains the initial state (initialState.posts in your reducer code). This is the price of keeping both component state and store; you must think of how to keep the two in sync after the initial load.
One solution is to update the state in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
const nextFiltered = nextProps.store.posts.filter(your filtering code here);
this.setState({currentlyDisplayed: nextFiltered});
}
This will update your state whenever the component receives new props. Note react has phased out componentWillReceiveProps and you should use getDerivedStateToProps as of react 16.3. Refer here for more details.

How can I edit or manipulate the data I get in my state before rendering the data

I am building an app using react and redux
After navigating to a new url an API call is made and the data now exists in the containers state.
What I want to do is before using that API data in a child component (FixtureList). I want to refactor the data. Where do I do that and how do I do that? Below is an example of the data:
Data Example - Please Click Here
From the Data example you can see that we have a key called matchday. I want to group all the objects by the matchday they are in and then render to the front end.
React Container
import React, { Component } from 'react';
import { connect } from 'react-redux';
import FixtureList from '../components/league-fixture-list';
class LeagueFixtures extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div className="row">
<div className="col-xs-12">
<h2>Fixtures</h2>
</div>
</div>
<div className="row">
{this.props.leagueFixtures.fixtures.map((fixture, index) => {
return <FixtureList fixture={fixture} matchday={fixture.matchday} key={index}/>
})}
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
leagueFixtures: state.LeagueFixtures
})
const mapDispatchToProps = (dispatch) => {
return {
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(LeagueFixtures)
React Component
import React from 'react'
const FixtureList = (props) => {
return (
<div className="col-sm-4">
<li className="list-group-item">
{props.matchday}
{props.fixture.homeTeamName} vs {props.fixture.awayTeamName}
</li>
<p></p>
</div>
);
}
export default FixtureList;
At the moment I loop through all of the data in the container and print it out in my component. It works as expected but I would like to mutate the data so I can achieve a different result.
Thank you
you can use reselect. Basically what you are trying to get is derived data, which can be computed from the original data returned from the server.
Reselect makes it really easy plus it caches the result, so as long as the original data does not change, the derived data is not recomputed (currently it does so in every render).
A possible implementation would be something like this: (using lodash for simplicity)
import { createSelector } from 'reselect';
import { keyBy } from 'lodash';
const fixtures = state => state.LeagueFixtures
const fixturesByMatchDaySelector = createSelector(
fixtures,
(fixtures) => keyBy(fixtures, ({matchday}) => matchday),
)
const mapStateToProps = (state) => ({
leagueFixtures: state.LeagueFixtures,
fixturesByMatchDay: fixturesByMatchDaySelector(state)
})
Hope that helps [=

React/Redux simple access to Store from Component

I'm trying to figure out how to user the reducers with and inside my React-Component.
My goal is pretty easy - at least i thought so: I want to toggle a Drawer-Menu. I know I can solve this with React-Only, but I want to learn Redux.
So, I've got a Component…
import React, { Component } from 'react';
class Example extends Component {
// ???
render() {
return (
<button className="burgerbutton" onClick={this.toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default Example;
also a Reducer
const initialState = {
buttonstate: false
};
const example = (state = initialState, action) => {
switch (action.type) {
case 'TOGGLE_BTN':
return Object.assign({}, state, {
buttonstate: !state.buttonstate
})
default:
return state
}
}
export default example
and an Action (although I don't know where to put that since it's so simple)
export const toggleDrawer = () => {
return {
type: 'TOGGLE_DRAWER'
}
}
I read a lot of tutorials and most of them want me to seperate between "Presentational Components" and "Container Components". I can't really see how these concepts apply here.
So what do I have to do to do to make this work? Am I looking at this problem from the right angle or do I need 12 "Container Components" to solve this?
I really hope this question makes sense at all and/or is not a duplicate!
In redux you have to dispatch action to update reducer state. So normally a component is connected to the redux store and communication is done through dispatch.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from 'action file location';
class Example extends Component {
toggleDrawerHandler() {
this.props.dispatch(toggleDrawer())
}
render() {
// access button state from this.props.buttonstate
return (
<button className="burgerbutton" onClick={this.toggleDrawerHandler.bind(this)}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default connect((store) => {buttonstate: store.buttonstate})(Example);
First, I'm really enjoying using redux "ducks" which is basically a redux reducer bundle. You put your reducer, action constants, and action creators in one file (called a duck). Then you may have multiple ducks for different modules or pieces of state that you'd then combine with combineReducers.
While #duwalanise has the right idea, I'd rather see the second param of connect() be used to directly map the action to dispatch (and there's a good shortcut for it) instead of having to use this.props.dispatch
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from './duck';
class Example extends Component {
render() {
const { buttonstate, togglerDrawer } = this.props;
return (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);
}
}
const mapStateToProps = (state) => ({
buttonstate: state.buttonstate,
});
export default connect(mapStateToProps, { toggleDrawer })(Example);
One side note, if you have a handler method in your component, it's better to do .bind(this) inside the constructor instead of using an arrow function or .bind(this) inside the event, ie don't do this onClick={() => /* do something */ } or this onClick={this.myHandler.bind(this)} This is an interesting (and long) read on it.
To touch on the Container vs Presentational Component piece: The idea would be to put all of your logic, handlers, redux actions etc into your containers, and pass that through props to your simple (hopefully stateless/pure function) presentational components. Technically, your component the way it's written could be turned into a stateless component:
const Example = ({ buttonstate, togglerDrawer }) => (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);

Categories