Rerender component child after after state change in parent component - reactjs

Hello am trying to refresh the graph after changing the value of select option but it shows the first graph and when I change the select option the state is changed but the graph didn't change I think the problem is in lifecycle component when the state changes didn't change only rendred for one time how can I fix it and thank you
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Select from "react-select";
import Graph from "../graph/Graph";
class Home extends Component {
state = {
selectedOption: null
};
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
};
render() {
const { user } = this.props.auth;
const { organization } = user;
console.log(organization);
//const organization = user.organization;
console.log(user);
//let organization = user.organization[0];
const options = organization.map(org => ({
value: org.conceptPrefix,
label: org.name
}));
const { selectedOption } = this.state;
let graphObject;
if (selectedOption == null) {
graphObject = <h4>Choose Organization</h4>;
} else {
graphObject = (
<div>
<Graph org={this.state.selectedOption.value} />
</div>
);
}
return (
<div>
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
{graphObject}
</div>
);
}
}
Home.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph
});
export default connect(
mapStateToProps,
{}
)(Home);
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { graphGet } from "../../actions/graphActions";
import GraphImp from "./GraphImp";
class Graph extends Component {
constructor(props) {
super(props);
this.state = {
org: props.org
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
componentDidMount() {
this.props.graphGet(this.props.org);
}
render() {
// {this.props.graph.graph && this.state.formSubmitted
// ? this.createList()
// : "wait Graph"}
const { graph, loading } = this.props.graph;
let graphContent;
if (graph == null || loading) {
graphContent = <h4>Loading ...</h4>;
} else {
graphContent = <GraphImp grapheData={graph} />;
}
return <div>{graphContent}</div>;
}
}
Graph.prototypes = {
graphGet: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
graph: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph,
errors: state.errors
});
export default connect(
mapStateToProps,
{ graphGet }
)(Graph);

There are 2 ways to achieve your goal.
First option: Implement componentDidUpdate in Graph
componentDidUpdate(prevProps) {
if(prevProps.org !== this.props.org) {
this.setState({ org: this.props.org });
this.props.graphGet(this.props.org);
}
}
Second option: Force react to fully remount&render your graph whenever you change the option by changing the key (Make sure the key is not an object/array)
<Graph key={this.state.selectedOption.value} org={this.state.selectedOption.value} />

Related

Why can't I access the context's methods or properties

I am trying to use React's context api to manage a global state. When I try to invoke contextual methods or access contextual proprties, I get errors saying "this.context.setUser function does not exist" or "undefined".
I have however been able to hard code values into the state of the context and retreive the hardcoded value.
Feed Context
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
state = {
feed: [],
error: null,
user: ''
};
setUser = user => {
this.setState({ user })
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
render() {
const value = {
feed: this.state.feed,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setFeed: this.setFeed,
setUser: this.setUser
}
return (
<FeedContext.Provider value={value}>
{this.props.children}
</FeedContext.Provider>
)
}
}
AccountPanel.js
import React from 'react';
import FeedContext from "../../contexts/FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
renderUserInfo(){
const { user = [] } = this.context;
//this returns "undefined"
console.log(user.user)
//this returns "user.setUser() is not a function"
user.setUser('newUser')
//this returns ' '
this.context.setUser('y')
console.log(user)
}
render(){
return (
<section>
{ this.renderUserInfo() }
AccountPanel
</section>
)
}
}
export default AccountPanel;
I would like to be able to update the contextual state/user via this.context.setUser('newUser), then consume that value in my navbar component
File App.js
import React, { Component } from 'react';
import AccountPanel from "./components/AccountPanel";
import { FeedProvider } from './components/FeedContext';
class App extends Component {
render() {
return (
<div className="App">
<FeedProvider>
<AccountPanel />
</FeedProvider>
</div>
);
}
}
export default App;
File : FeedContext.js
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
constructor(props){
super(props);
this.state = {
feed: [],
error: null,
user: "11"
};
}
setUser = user => {
console.log(`setting usr fns called for username: ${user}`);
this.setState({ user });
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
componentDidMount(){
console.log('FeedProvider:componentDidMount');
}
render() {
let value1 = {
Feed:this.state.feed,
user:this.state.user,
error:this.state.error,
setError:this.setError,
clearError:this.clearError,
setFeed:this.setFeed,
setUser:this.setUser
}
return (
<FeedContext.Provider value={value1}>
{this.props.children}
</FeedContext.Provider>
)
}
}
File : AccountPanel.js
import React from 'react';
import FeedContext from "./FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
// return BlogPost component html/(JSX)
componentDidMount(){
console.log('AccountPanel:componentDidMount');
console.log(this.context);
const value = this.context;
//this returns "undefined"
console.log(value.user)
//this returns "user.setUser() is not a function"
console.log(value.setUser);
value.setUser('newUser');
}
render(){
const value = this.context;
console.log(`Value of new User is : ${value.user}`);
return (
<section>
AccountPanel
</section>
)
}
}
export default AccountPanel;
Hope This helps :)

Multiple React Context: inner one depending on outer one doesn't work

I want to use 2 React Context for my app.
The 1st context is UserContext, that loads user info.
The 2nd context is ItemContext, that loads item info based on user id.
For simplicity, I use synchronous functions to load user and item info. See CodeSandbox for a full working sample.
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { ItemProvider, ItemConsumer } from "./ItemContext";
import { UserProvider, UserConsumer } from "./UserContext";
class App extends React.Component {
render() {
return (
<UserProvider>
<UserConsumer>
{({ user }) => (
<ItemProvider user={user}>
<ItemConsumer>
{({ item }) => <div>{JSON.stringify(item, null, 2)}</div>}
</ItemConsumer>
</ItemProvider>
)}
</UserConsumer>
</UserProvider>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// UserContext.js
import React from "react";
const { Provider, Consumer } = React.createContext();
class UserProvider extends React.Component {
state = {
isLoading: false,
value: { user: {} }
};
async componentDidMount() {
this.setState({ isLoading: true });
const user = { id: 1, name: "evan" };
this.setState({ value: { user }, isLoading: false });
}
render() {
const { isLoading, value } = this.state;
if (isLoading) {
return <div>Loading data...</div>;
}
return <Provider value={value}>{this.props.children}</Provider>;
}
}
export { UserProvider, Consumer as UserConsumer };
// ItemContext.js
import React from "react";
const { Provider, Consumer } = React.createContext();
const items = { 1: { name: "a red hat" }, 2: { name: "a blue shirt" } };
const getItemByUserId = userId => items[userId];
class ItemProvider extends React.Component {
state = {
isLoading: false,
value: { item: {} }
};
async componentDidMount() {
this.setState({ isLoading: true });
const item = getItemByUserId(1);
// console.log(this.props.user); // Object {}
// const item = getItemByUserId(this.props.user.id);
this.setState({ value: { item }, isLoading: false });
}
render() {
const { isLoading, value } = this.state;
if (isLoading) {
return <div>Loading data...</div>;
}
return <Provider value={value}>{this.props.children}</Provider>;
}
}
export { ItemProvider, Consumer as ItemConsumer };
I am not able to do const item = getItemByUserId(this.props.user.id); in ItemContext because this.props.user is an empty object.
What is the solution?
User value is rendered from within UserContext asynchronously and won't be available until the children have rendered and hence you need to implement componentDidUpdate in ItemProvider to get the context value based on user prop
import React from "react";
const { Provider, Consumer } = React.createContext();
const items = { 1: { name: "a red hat" }, 2: { name: "a blue shirt" } };
const getItemByUserId = userId => items[userId];
class ItemProvider extends React.Component {
state = {
isLoading: false,
value: { item: {} }
};
async componentDidMount() {
if (this.props.user && this.props.user.id) {
this.setState({ isLoading: true });
const item = getItemByUserId(this.props.user.id);
this.setState({ value: { item }, isLoading: true });
}
}
componentDidUpdate(prevProps) {
console.log(this.props.user, prevProps.user);
if (prevProps.user !== this.props.user) {
const item = getItemByUserId(this.props.user.id);
this.setState({
value: { item }
});
}
}
render() {
const { isLoading, value } = this.state;
if (isLoading) {
return <div>Loading data...</div>;
}
console.log(value);
return <Provider value={value}>{this.props.children}</Provider>;
}
}
export { ItemProvider, Consumer as ItemConsumer };
Working demo

action does not modify state

I am trying to add user metadata to my store when mounting a screen. However, when I send the action to the reducer, the store is not modified.
I would expect props after sending the action to be as follows:
{addUserMetaData: ƒ addUserMetaData(user_object),
user: {firestore_doc: {name: "Joe"}}
}
What am i missing here?
To reproduce, react-native-init mwe then add the following code. I've added an image of the app logs below.
App.js
import React, { Component} from 'react';
import { View } from 'react-native';
import Screen from './src/screen';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const userReducer = function userReducer(state = {}, action) {
console.log('action', action);
switch (action.type) {
case "ADD_USER_METADATA":
return { ...state, firestore_doc: action.payload };
default:
return { ...state };
}
};
const store = createStore(userReducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<View>
<Screen />
</View>
</Provider>
);
}
};
src/screen.js
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
const addUserMetaData = (user) => ({
type: "ADD_USER_METADATA",
payload: user
})
class Screen extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const user = { name: "Joe" };
console.log('props', this.props);
this.props.dispatch(addUserMetaData(user));
console.log('props after action', this.props);
}
render() {
return (
<View>
<Text>Welcome to react native</Text>
</View>
)
}
}
const mapStateToProps = state => {
return { user: state };
};
export default connect(mapStateToProps)(Screen);
Fixed https://snack.expo.io/#janithar/c3RhY2
Lines I changed
return { ...state, firestore_doc: action.payload };
Please added state.firestore_doc instead of state because in reducer action.payload assign the data in firestore_doc state so you are not getting data from state.user
const mapStateToProps = state => {
return { user: state.firestore_doc };
};

Component not rerender after action being dispatched and reduced

i want to make a counter Component when i learning in react and redux now.
so i want to change the eval input and then i will click the resolve input,and the end show the resolve to component2.but when i clicked,the resolve was changed in reducer and actions (i was consoled the resolve) , but had not change in component2,why?my English is not good,thanks...
this is my full code:
actions.js
export function DO_COUNT(resolve) {
return {
type: 'DO_COUNT',
payload: resolve
}
}
reducer.js
import actions from '../actions'
export default (state = { resolve: 0 }, actions) => {
switch (actions.type) {
case 'DO_COUNT':
console.log({
...state,
resolve: actions.payload
})
return {
...state,
resolve: actions.payload
}
break
default:
return state
}
}
store.js
import { createStore } from 'redux'
import reducers from '../reducers'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducers, composeWithDevTools())
export default store
my component1:Counter.js
import React, { Component } from 'react'
import styleObj from './style.less'
import store from '../../store'
import { DO_COUNT, CHANGE_EVAL } from '../../actions'
export default class Counter extends Component {
constructor(props) {
super(props)
this.state = {
num1: 0,
num2: 0,
myEval: '+'
}
}
changeEval = e => {
this.setState({
myEval: e.target.value
})
}
changeNum1 = e => {
// let _target = e.target.dataset.target
let value = e.target.value
this.setState(
{
num1: value
},
() => {
console.log(this.state)
}
)
}
changeNum2 = e => {
// let _target = e.target.dataset.target
let value = e.target.value
this.setState(
{
num2: value
},
() => {
console.log(this.state)
}
)
}
doCount = () => {
let resolve = eval(
[this.state.num1, this.state.num2].join(this.state.myEval)
)
store.dispatch(DO_COUNT(resolve))
}
render() {
return (
<div className={styleObj.counterBox}>
<input type="number" onInput={this.changeNum1} data-target="num1" />
<select onChange={this.changeEval}>
<option defaultValue="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select>
<input type="number" onInput={this.changeNum2} data-target="num2" />
<input type="button" value="=" onClick={this.doCount} />
</div>
)
}
}
my component2:Container.js
import React, { Component } from 'react'
import styleObj from './style.less'
import store from '../../store'
export default class Container extends Component {
constructor(props) {
super(props)
}
render() {
return <h1 className={styleObj.content}>{store.getState().resolve}</h1>
}
}
and image:
You should be using react-redux.
The problem is your Container component is not being notified when the store's state changes. You can do this manually by hooking into lifecycle methods and setting state, but this is what react-redux already does (in a more optimized way).

Unable to call props functions in componentDidMount

From Party.Container where is connected Party with mapStateToProps and mapDispatchToProps, are sent two functions to Party (fetchData and fetchFooter)
They worked until I implemented in project eslint:"airbnb", and now it's constantly getting this error "Must use destructuring props assignment react/destructuring-assignment".
const mapActionsToProps = {
fetchData,
fetchDataFooter,};
--- these are functions
componentDidMount() {
this.props.fetchData();
this.props.fetchDataFooter(); }
This is the component
import { connect } from 'react-redux';
import { fetchData, fetchDataFooter } from './actions';
import Party from './Party';
const mapStateToProps = state => ({
wishlist: state.wishlist,
cart: state.cart,
});
const mapActionsToProps = {
fetchData,
fetchDataFooter,
};
export default connect(mapStateToProps, mapActionsToProps)(Party);
This is COntainer
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Header from '../../components/Header/Header';
import Content from './Content/Content.Container';
import styles from './Party.module.scss';
import Footer from '../../components/Footer/Footer';
const propTypes = {
wishlist: PropTypes.shape.isRequired,
cart: PropTypes.shape.isRequired,
// fetchData: PropTypes.func.isRequired,
// fetchDataFooter: PropTypes.func.isRequired,
};
class Party extends Component {
componentDidMount() {
// this.props.fetchData();
// this.props.fetchDataFooter();
}
render() {
const { wishlist, cart } = this.props;
let name;
let profilePicture;
let personWishlist;
let purchases;
let id;
if (wishlist.isFulfilled === true) {
const listId = wishlist.payloadData.data.filter(x => x.id === 1);
({ name } = listId[0].name);
({ profilePicture } = listId[0].picture);
({ personWishlist } = listId[0].wishList);
({ purchases } = listId[0].purchases);
({ id } = listId[0].id);
}
console.log(wishlist, cart);
return (
<div className={styles.Party}>
<Header />
<Content
name={name}
id={id}
profilePicture={profilePicture}
personWishlist={personWishlist}
purchases={purchases}
/>
<Footer
cart={cart}
/>
</div>
);
}
}
Party.propTypes = propTypes;
export default Party;
Can you try the one in below in your componentDidMount method as the error suggests:
componentDidMount() {
const { fetchData, fetchDataFooter } = this.props;
fetchData();
fetchDataFooter();
}
Actually, it means that your expressions should be destructured before usage.
E.g.: you're using:
...
this.props.fetchData();
this.props.fetchDataFooter();
...
You have to change it to:
const { fetchData, fetchDataFooter } = this.props;
fetchData();
fetchDataFooter();
Another solution is to disable this if you want to in your rules file.
"react/destructuring-assignment": [<enabled>, 'always'] - can be always or never.
See here for more information.

Resources