I have the following code in my react/redux app:
import { createSelector } from 'reselect';
const selectMembers = state => state.membersData;
const makeSelectLessonSets = createSelector(
selectMembers,
substate => substate.get('myData').toJS()
);
The problem is that sometimes myData is not yet defined, so substate.get('myData') will not get anything. And since I try to call toJS() on it, it shows the error: undefined is not an object.
But I don't know how to check if substate.get('myData') has returned a valid object inside createSelector before I call toJS() on it.
Can you please help with it.
I added ? before the dot this will not throw an error
import { createSelector } from 'reselect';
const selectMembers = state => state.membersData;
const makeSelectLessonSets = createSelector(
selectMembers,
substate => substate?.get('myData')?.toJS()
);
Related
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.
My goal is to avoid invoking a method because of React.useEffect on each re-render. This happens because React thinks the stompClient variable is undefined on each re-render.
This is the current solution I use but this will cause the stompClient to reconnect on each re-render.
App.tsx
import React from "react";
import {stompClient, setStompClient} from "./services/StompClient";
const AuthenticatedContainer = () => {
...
React.useEffect(() => {
if (stompClient?.connected === undefined || stompClient.connected === false) {
// this always happens on each re-render, causing unnecessary re-connection.
setStompClient( the config );
}
});
....
}
./services/StompClient/index.tsx
import {Client, IFrame, StompConfig} from '#stomp/stompjs';
import {environment} from '../../environments/environment';
export let stompClient: Client | null = null;
export const setStompClient = (
onConnect: () => void,
onStompError: (receipt: IFrame) => void,
) => {
const stompConfig: StompConfig = {
brokerURL: `${environment.wsBaseURL}/chat`,
forceBinaryWSFrames: true,
appendMissingNULLonIncoming: true,
onConnect,
onStompError,
};
stompClient = new Client(stompConfig);
};
What I've tried:
I tried to store the StompClient object with redux. It turns out, redux does not like to store non-serializable object. Reference
I tried to create a custom hook, this would create multiple StompClient instead of one. Reference
What I've not tried:
Use React.Context, I am assuming React.Context and Redux is the same, it does not like storing non serializable object. Please feel free to let me know if I am wrong.
My issue is the following:
redux state updates won't be mapped to the props immediately. This does not refer to useState of react. I'm referring to react-redux, where the state updates should be synchronous, afaik. I know they could be batched, but not sure if that is relevant here. I assume the update is happening immediately, but the mapping is not. I don't know how to get around this. I tried to replicate the error in a "clean" environment. Here is the Component:
import React from 'react';
function MyComponent({ title, setTitle }) {
const handleclick = e => {
console.log(title);
setTitle("I don't get it.");
console.log(title);
};
return <p onClick={handleclick}>{title}</p>;
}
export default MyComponent;
Here is the ComponentContainer:
import React from 'react';
import { connect } from 'react-redux';
import { setTitle } from '../../reducers/customReducer/actions.js';
import MyComponent from './MyComponent.jsx';
export const MyComponentContainer = props => {
return <MyComponent {...props} />;
};
const mapStateToProps = state => {
return {
title: state.customData.title
};
};
const mapDispatchToProps = dispatch => {
return {
setTitle: newTitle => dispatch(setTitle(newTitle))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponentContainer);
I expect the second console.log to output the value I don't get it. But it doesn't. Though, the render update happens properly. Meaning the innerHTML of the p element will be updated and rendered accordingly "a bit later".
How do I make it, that I can access the updated value instantly? This is causing an issue in my real world application. I'm not trying to simply print some value there, but rather, change a value and then call another function, that based on the updated value performs some action. Since the value isn't changed/mapped instantly, it won't work.
Am I doing something that shouldn't be done?
I'm just starting to use react hooks and I'm having some issues when using custom hooks. It's probably lack of understanding but here's what I'm attempting
My Custom hook:
import React, { useState } from "react"
export const useValidateContent = initState => {
const[valid, setValid] = useState(initState)
const[errorMsg, setErrorMsg] = useState(null)
const validate = () => {
// Update component state to test
setValid(false)
setErrorMsg('Some error found')
}
return [valid, validate, errorMsg]
}
My parent container which uses the custom hook:
import React, { useState, useEffect } from 'react'
import { useValidateContent } from './hooks/useValidateContent'
export default function ParentComp () {
const [contentIsValid, validate, contentError] = useValidateContent(true)
const initValidate = () => {
// values before running validate
console.log('valid', contentIsValid)
console.log('error', contentError)
validate()
// values after running validate
console.log('valid', contentIsValid)
console.log('error', contentError)
}
return (
<div>
<button onclick={initValidate} />
</div>
)
}
What I expected to be consoled here was:
valid true error nullvalid falseerror Some error found
Instead what I see is:
valid true error nullvalid true error null
It seems like the hook is not updating the local state. Why is this? Even when I try to console those values inside the hook component I get the same thing. I cannot figure out why this is. Am I using custom hooks wrong?
Updating state with hooks is asynchronous just like setState in a class component is, and since the state is not mutated contentIsValid and contentError will still refer to the stale old state and not the new state.
If you render your state variables you will see that your code works as expected.
const { useState } = React;
const useValidateContent = initState => {
const [valid, setValid] = useState(initState);
const [errorMsg, setErrorMsg] = useState("");
const validate = () => {
setValid(false);
setErrorMsg("Some error found");
};
return [valid, validate, errorMsg];
};
function ParentComp() {
const [contentIsValid, validate, contentError] = useValidateContent(true);
const initValidate = () => {
// values before running validate
console.log("valid", contentIsValid);
console.log("error", contentError);
validate();
// values after running validate
console.log("valid", contentIsValid);
console.log("error", contentError);
};
return (
<div>
<button onClick={initValidate}>initValidate</button>
contentIsValid: {contentIsValid.toString()}, contentError: {contentError}
</div>
);
}
ReactDOM.render(<ParentComp />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
valid state is set when you called validate() function
and since the custom hook return valid state value to the component you use it at, you can directly use valid state.
The problem is, when you called validate() and "valid" got its state changed, but our component needs to tell when valid gets a value assign render our component. So in react functional compoennts we can simply put "valid" as a dependency for useEffect. then whenever valid gets state it will call a re render for our component.
I have plenty of template components, they resemble each other in way they are used.
Before being rendered to page, template components get wrapped in graphql and connected to redux.
I want to create a HOC to wrap my templates, so that I do not create a new container each time to connect template to data.
Like so:
Here is my page component, where I try to wrap the AppointmentsListTemplate template with gqlList HOC:
import React from 'react'
import { AdminTemplate, AppointmentsListTemplate } from 'components'
import { gqlList } from 'containers'
import {qyListAppointments} from 'services/gqlQueries/Appointments'
const AppointmentsListTemplateWrapped = gqlList(AppointmentsListTemplate, qyListAppointments)
const AdminAppointmentsPage = (props) => {
return (
<AdminTemplate>
<AppointmentsListTemplateWrapped />
</AdminTemplate>
)
}
export default AdminAppointmentsPage
And here is my gqlList HOC:
import React, {Component} from 'react'
import { graphql } from 'react-apollo'
import { connect } from 'react-redux'
import { saveQueryVars } from 'store/helper/actions'
const gqlList = (WrappedComponent, gqlQuery) => {
const GQL = graphql(gqlQuery)(WrappedComponent)
return connect(null, {
saveQueryVars,
})(GQL)
}
export default gqlList
But graphql connector part throws me this error:
Uncaught TypeError: Cannot read property 'displayName' of undefined
at getDisplayName (react-apollo.browser.umd.js:250)
at wrapWithApolloComponent (react-apollo.browser.umd.js:266)
at new eval (gqlList.js:22)
at eval (createClassProxy.js:95)
at instantiate (createClassProxy.js:103)
at Unknown (eval at proxyClass (createClassProxy.js:NaN), :4:17)
at eval (ReactCompositeComponent.js:303)
at measureLifeCyclePerf (ReactCompositeComponent.js:73)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner
What am I doing wrong?
There doesn't seem to be anything wrong with your code, as far as I can tell. I would also rule out redux as the source of the error. But here's what I suggest:
Check your GraphQL query (gqlQuery) so that it gets what it needs and it can return what you need. I suspect it requires some parameters but doesn't get the right type of a parameter - resulting in a Type error.
Here's an example (without redux) of how to pass parameters:
const Data = graphql(fetchData, { options: {variables: {id: this.getId()} }})(Thing);
Here, fetchData requires the id of a Thing and returns data about that thing. Then you can render <Data/>.
You might want to improve your question by adding the query and variables (saveQueryVars) to it. Also, mention the version of react-apollo because that's the module throwing the error. As a side note, the error message coming from the Apollo client is not very helpful.
You can chain them together:
import { changeFoo } from './myReduxActions';
const QUERY = gql`{ FindSomething { Id, Value }}`;
const myComponent = connect(
store => ({
foo: store.fooReducer.foo
}),
dispatch => ({
changeFoo: (val) => dispatch(changeFoo(val))
})
)(graphql(QUERY)(
{props.data.loading && return <div>Loading...</div>}
let myNewFoo = 'abc';
return props.data.FindSomething ?
<div>{`Id is ${props.data.FindSomething.Id}, redux store value foo is ${props.foo}`}
<div onClick={() => props.ChangeFoo(myNewFoo)}></div></div> :
props.data.error ? <div>Error {props.data.error}</div> : null;
));
So you could do connect(graphql(pureComponent))) or written as connect => graphql => component. You can change the order of graphql and connect.