I have a component parent and a component child with some props connected to the parent state.
In the parent I call setState but the componentWillReceiveProps function of the child is not fired.
More precisaly, its fired in a certain point of the parent, its not fired in another point.
This is the parent:
... imports
class HomeScreen extends Component {
constructor(props) {
super(props);
dispatchFbPermissionAction = this.dispatchFbPermissionAction.bind(this);
this.state = {
fbPermissions: [],
}
}
componentDidMount () {
this._loadInitialState();
}
_responsePermissionsCallback(error: ?Object, result: ?Object) {
if (error) {
log('Error fetching data: ' + error.toString());
} else {
dispatchFbPermissionAction(result.data);
}
}
dispatchFbPermissionAction = (data) => {
// **NOT FIRED**
this.setState({
fbPermissions: data
});
this.props.fbPermissionsLoaded(data);
}
async _loadInitialState() {
AccessToken.getCurrentAccessToken().then(
(data) => {
if (data) {
const infoRequest = new GraphRequest(
'/me/permissions',
null,
this._responsePermissionsCallback,
);
new GraphRequestManager().addRequest(infoRequest).start();
// **FIRED**
this.setState({
...
});
this.props.loggedIn();
}
}
);
}
render () {
const { navigation } = this.props;
return (
<Container>
<ScrollableTabView
<View tabLabel="ios-film" style={styles.tabView}>
<Text style={styles.tabTitle}>{_.toUpper(strings("main.theatres"))}</Text>
<ListTheatre navigation={this.props.navigation} filterText={this.state.filterText} isLoggedIn={this.state.isLoggedIn} fbPermissions={this.state.fbPermissions}></ListTheatre>
</View>
</ScrollableTabView>
</Container>
)
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
listTheatre: state.listTheatre,
listMusic: state.listMusic
};
};
// wraps dispatch to create nicer functions to call within our component
const mapDispatchToProps = (dispatch) => ({
startup: () => dispatch(StartupActions.startup()),
loggedIn: () => dispatch({
type: LOGGED_IN
}),
fbPermissionsLoaded: (data) => dispatch({
type: FB_PERMISSIONS_LOADED,
fbPermissions: data
})
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
And this is the child:
... imports
class ListTheatre extends Component {
constructor(props) {
super(props);
this.state = {
...
}
}
componentWillReceiveProps(nextProps) {
log(this.props)
}
shouldComponentUpdate(nextProps, nextState) {
return !nextState.fetching;
}
render() {
const { navigate } = this.props.navigation;
return (
<SectionList
...
/>
)
}
}
ListTheatre.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn
};
};
const mapDispatchToProps = (dispatch) => ({
startup: () => dispatch(StartupActions.startup())
});
export default connect(mapStateToProps, mapDispatchToProps)(ListTheatre);
I do not why the setState after the GraphRequestManager().addRequest call works like a charm (the componentWillReceiveProps function of the child is fired), while the setState in the dispatchFbPermissionAction function does not fire the componentWillReceiveProps function of the child.
This is due to connect/Connect(ListTheatre) that wraps your ListTheatre component implemented sCU(shouldComponentUpdate) internally for you, turn it off by setting pure option of connect to false like
export default connect(mapStateToProps, mapDispatchToProps, null, {pure: false})(ListTheatre)
[pure] (Boolean): If true, connect() will avoid re-renders and calls to mapStateToProps, mapDispatchToProps, and mergeProps if the relevant state/props objects remain equal based on their respective equality checks. Assumes that the wrapped component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Default value: true
Related
I developed my first React component that was Function based and I'm now trying to refactor it to be Class based. However, I can't seem to get it to work when trying to convert it over. I'm pretty sure that the issue is with the RenderItem method, and when I try to bind it, I get this error: TypeError: Cannot read property 'bind' of undefined. How can I bind a method that's a child of a parent method? Is this possible, and if not what would be a better solution?
Error when compiling:
Line 35:10: 'state' is assigned a value but never used no-unused-vars
Line 51:9: 'renderItem' is assigned a value but never used no-unused-vars
import React, { useEffect, useReducer } from 'react';
import API from '#aws-amplify/api';
import { List } from 'antd';
import { listQuestions as ListQuestions } from '../../graphql/queries';
export default class QuestionLoader extends React.Component {
state = {
questions: [],
loading: true,
error: false,
form: { asked: '', owner: '' },
};
constructor() {
super();
this.GetQuestion = this.GetQuestion.bind(this);
this.reducer = this.reducer.bind(this);
// this.renderItem = this.renderItem.bind(this);
console.log('constructor', this);
}
reducer(state, action) {
switch (action.type) {
case 'SET_QUESTIONS':
return { ...state, questions: action.questions, loading: false };
case 'ERROR':
return { ...state, loading: false, error: true };
default:
return state;
}
}
GetQuestion() {
const [state, dispatch] = useReducer(this.reducer, this.state);
useEffect(() => {
fetchQuestions();
}, []);
async function fetchQuestions() {
try {
const questionData = await API.graphql({ query: ListQuestions });
dispatch({ type: 'SET_QUESTIONS', questions: questionData.data.listQuestions.items });
} catch (err) {
console.log('error: ', err);
dispatch({ type: 'ERROR' });
}
}
const renderItem = (item) => {
console.log(this);
return <List.Item.Meta title={item.asked} description={item.owner} />;
};
}
render() {
return (
<div>
<List loading={this.state.loading} dataSource={this.state.questions} renderItem={this.renderItem} />
</div>
);
}
}
// export default QuestionLoader;
you cannot mix functional component and class component thing.
useEffect, useReducer are wrong one to use with class component.
Don't use bind, use arrow function to create the method. Remove constructor.
import React from 'react';
export default class QuestionLoader extends React.Component {
state = {
data: "name"
};
handleClick = () => {
this.setState({
name:"test"
})
}
render() {
return (<div> {this.state.name}
<button onClick={this.handleClick}></button>
</div>
}
}
I'm working in a react native app with react redux integration. When I call dispatch from a service my store is getting updated but somehow my component is not re-rendering.
Is it wrong to call dispatch from a service file and not from mapDispatchToProps function.
store.js
import { memesReducer } from './memesReducer'
export default combineReducers({
memesReducer
});
export default configureStore = () => {
const store = createStore(rootReducer);
return store;
}
memesReducer.js
const initialState = { memeList: [] }
export const memesReducer = (state = initialState, action) => {
switch (action.type) {
case LOAD_MEMES: {
return { ...state,
memeList: action.data
}
}
default:
return state;
}
}
memeService.js
import configureStore from '../redux/store';
import { loadMemes } from '../redux/actions';
const store = configureStore();
export const getMemesList = () => {
axios('https://jsonplaceholder.typicode.com/albums')
.then(response => {=
store.dispatch(loadMemes(response.data))
})
.catch(error => { console.error('getMemesList : ', error); })
}
memeActions.js
export const loadMemes = memesListData => ({
type: LOAD_MEMES,
data: memesListData
});
MemeList.js
class MemeList extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
getMemesList()
}
render() {
const memeListData = this.props.memeList.map((meme) => <MemeCard meme={meme} />)
return (
<Container>
<Content>
<List>
{ memeListData }
</List>
</Content>
</Container>
)
}
}
const mapStateToProps = state => {
return {
memeList: state.memesReducer.memeList,
}
}
export default connect(mapStateToProps)(MemeList);
memeActions.js
export const getMemesList = () => dispatch => {
axios("https://jsonplaceholder.typicode.com/albums")
.then(response => dispatch(loadMemes(response.data)))
.catch(error => {
console.error("getMemesList : ", error);
});
};
const loadMemes = memesListData => ({
type: "LOAD_MEMES",
data: memesListData
});
memeReducer.js
case "LOAD_MEMES": {
return { ...state, memeList: action.data };
}
index.js
export default combineReducers({
memesReducer: memeReducer
});
memeList.js
class memeList extends Component {
componentDidMount() {
this.props.getMemesList();
}
render() {
console.log(this.props.memeList);
return <div>MemeList</div>;
}
}
const mapStateToProps = state => ({
memeList: state.memesReducer.memeList
});
export default connect(
mapStateToProps,
{ getMemesList }
)(memeList);
Yeah bro it wouldn't work. You should call dispatch in a Redux connected component.
What you can do is await or attach a .then to the Service Call and THEN call the dispatch after the await or inside the .then.
call your actions like this then only i will work.
componentDidMount() {
this.props.getMemesList()
}
for your more clarification check this official documentation react redux
Is it possible to publish and subscribe to events(like in ionic) for Component communication. The two components I have there are no related (there are not parent and child).
One component is a header that has a button Publish , and the other component is a form. What i want is to send an event from the clicked button to the form for validation purpose that says for example the field body cant be empty something like that.
EDIT:
I am using router flux. The component i have the form is NewPost and the one with the Button publish is ButtonsNewPost. Are this components parent and child? They can comunicate somehow?
<Scene
key="newPost"
component={NewPost}
hideNavBar={false}
renderRightButton={<ButtonsNewPost/>}
navBarButtonColor='#fff'
>
SOLUTION:
newPost.js
componentWillReceiveProps(newProps) {
let validationMessage;
if(newProps.validationBody) {
validationMessage = 'El campo descripción es requerido';
this.showToastValidation(validationMessage);
//without the next line the validation toast only appear once
this.props.validation_body(false);
}
}
const mapStateToProps = state => {
return {
validationBody: state.validationBody
}
}
const mapDispatchToProps = dispatch => {
return {
validation_body: (validationBody) =>
dispatch(validation_body(validationBody))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewPost)
reducers/validationBody.js
export default (state = false, action) => {
switch(action.type) {
case 'validation_body':
return action.payload
default:
return state
}
}
reducers/index.js
import validationBody from './validationBody';
export default combineReducers({
validationBody: validationBody
})
actions/index.js
export const validation_body = (validationBody) => {
return {
type: 'validation_body',
payload: validationBody
}
}
buttonsNewPost.js
if (!window.description) {
this.props.validation_body(true);
return;
}
const mapDispatchToProps = dispatch => {
return {
validation_body: (validationBody) =>
dispatch(validation_body(validationBody)),
}
}
export default connect(null, mapDispatchToProps)(ButtonsNewPost)
You can use the react-native-event-listeners library:
https://github.com/meinto/react-native-event-listeners
Usage is similar to Ionic events:
import { EventRegister } from 'react-native-event-listeners'
/*
* RECEIVER COMPONENT
*/
class Receiver extends PureComponent {
constructor(props) {
super(props)
this.state = {
data: 'no data',
}
}
componentWillMount() {
this.listener = EventRegister.addEventListener('myCustomEvent', (data) => {
this.setState({
data,
})
})
}
componentWillUnmount() {
EventRegister.removeEventListener(this.listener)
}
render() {
return <Text>{this.state.data}</Text>
}
}
/*
* SENDER COMPONENT
*/
const Sender = (props) => (
<TouchableHighlight
onPress={() => {
EventRegister.emit('myCustomEvent', 'it works!!!')
})
><Text>Send Event</Text></TouchableHighlight>
)
I'm trying to test my smart component using Jest and Enzyme but it has no data to render because it supposed to be fetched trought actions. Error is: getTasks is not a function
Enzyme.configure({ adapter: new Adapter() });
describe('Main', () => {
describe('when loading is true', () => {
it('should render loading div', () => {
const wrapper = mount(<Main.WrappedComponent loading={true} />);
expect(wrapper.html()).toEqual('<div>Loading</div>');
wrapper.unmount();
});
});
});
And this is component I'm trying to test, it fetching data trought actions and then doing some stuff with them, but if there is no data(loading === true) it just renders the <div> with "Loading" text. getTasks() just import the data:
class Main extends React.Component {
constructor(props) {
super();
this.handleData = this.handleData.bind(this);
this.handleHigh = this.handleHigh.bind(this);
}
componentDidMount() {
const { getTasks } = this.props;
getTasks();
}
render() {
const { data, loading } = this.props;
if (!loading) {
this.handleData(data);
return (
{data.map(task => {
if (task.obj_status === 'active')
return (
// Doing some stuff with data here
);
} else {
return <div>Loading</div>;
}
}
}
const mapStateToProps = state => ({
data: state.main.data,
loading: state.main.loading
});
const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
getTasks: loadTasks,
dispatch
},
dispatch
)
});
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(Main)
);
You need to pass in getTasks as a function into your props:
const wrapper = mount(<Main.WrappedComponent loading={true} getTasks={() => {}} />);
As when Enzyme mounts it will invoke componentDidMount and will call the undefined prop and blow up
Better solution is mock function:
const getTasksMock = jest.fn();
const wrapper = mount(<Main.WrappedComponent loading={true} getTasks={getTasksMock}/>);
Then you are able check invoking function by for example: toHaveBeenCalled()
https://jestjs.io/docs/en/expect.html#tohavebeencalled
I have the following action:
export function loginUserRequest() {
console.log('ACTION CALLED');
return {
type: LOGIN_USER_REQUEST,
};
}
and this is the reducer:
export default function loginReducer(state = initialState, action) {
switch (action.type) {
case LOGIN_USER_REQUEST:
console.log('REDUCER CALLED');
return Object.assign({}, state, {
isAuthenticated: true,
isAuthenticating: true,
statusText: null,
});
default:
return initialState;
}
}
Then, my component:
class Login extends React.Component {
goHome = () => {
browserHistory.push('/');
}
handleSubmit = (values) => {
console.log(this.props.isAuthenticating);
this.props.actions.loginUserRequest();
console.log(this.props.isAuthenticating);
}
render() {
return (
<LoginForm onSubmit={this.handleSubmit} />
);
}
}
Login.propTypes = {
actions: PropTypes.objectOf(PropTypes.func).isRequired,
isAuthenticating: PropTypes.bool.isRequired,
};
const mapStateToProps = state => ({
token: state.login.token,
isAuthenticated: state.login.isAuthenticated,
isAuthenticating: state.login.isAuthenticating,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actionCreators, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
LoginForm is a redux-form component.
So, the expeted ouput from the handleSubmit function is:
false
ACTION CALLED
REDUCER CALLED
true
but it is giving me:
false
ACTION CALLED
REDUCER CALLED
false
But in the redux dev tool I can see the diff in LOGIN_USER_REQUEST:
Why I don't see it inside the handleSubmit function? Is it something related to redux-form library?
Extra info:
Added shouldComponentUpdate and logger
shouldComponentUpdate = (nextProps, nextState) => {
console.log('Should component update called');
if (this.props.isAuthenticating !== nextProps.isAuthenticating) {
console.log('distntict');
return true;
}
console.log('false');
return false;
}
You are getting such a result because of async nature of Javascript. So in your code
handleSubmit = (values) => {
console.log(this.props.isAuthenticating);
this.props.actions.loginUserRequest();
console.log(this.props.isAuthenticating);
}
First, you are printing the value of prop, and then the action gets called but before the action returns a response with the updated state, your third statement gets called to log the value and since the state is not yet updated you see the same result.
One approach will be have callbacks but that doesn't seem to be a requirement for your case. If your want to log the state then you can do so in componentWillReceiveProps function
like
componentWillReceiveProps(nextProps) {
if(this.props.isAuthenicating != nextProps.isAuthenticating) {
console.log(nextProps.isAuthenticating);
}
}
I hope it helps