I have one page with a list of "tenants". When I select one tenant if shows the data for this specific tenant. It is working. However, when I navigate back to the tenant list and select another tenant, it does not update the this.props with the new tenant data.
My Tenant Details Page
constructor(props) {
super(props);
this.state = {
tenantData: {}
};
}
componentDidMount() {
this.getTenantID();
}
componentDidUpdate(prevProps) {
// needs to be a unique value
if (prevProps.tenantData.Email !== this.props.tenantData.Email) {
this.getTenantID();
}
}
getTenantID = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: this.props.tenantData
});
};
My action:
export const getTenantByID = (tID) => {
return (dispatch) => {
axios.get('http://myirent.com/rent/components/iRentApp.cfc', {
params: {
method: 'getTenantByTenant',
tenantID: tID
}
}).then((response) => {
const tenant = response.data.DATA[0];
console.log(tenant);
const getTenant = {
FirstName: tenant[1],
LastName: tenant[2],
Email: tenant[5],
Phone: tenant[6],
Unit: tenant[11],
MiddleName: tenant[3],
RentalAmount: tenant[4],
MoveInDate: getFormattedDate(tenant[7]),
MoveOutDate: getFormattedDate(tenant[8]),
LeaseStartDate: getFormattedDate(tenant[9]),
LeaseEndDate: getFormattedDate(tenant[10])
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
});
};
};
The tenantID is being updated and the action response data too. It looks like that the page is loading before updating the this.props.tenantData
The componentDidUpdate() is called immediately after the update. This method is not called in the first rendering.
componentDidUpdate(prevProps) {
// typical use cases (don't forget the props comparison)
if (prevProps.navigation !== this.props.navigation) {
const data = this.props.navigation.getParam('tenantID', '0')
this.getTenantID(data);
}
}
getTenantID = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
const tenantdata = await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: tenantdata,
updateid : tenantID
});
};
Related
I use FlatList with useState.
const [state, setState] = useState(route);
<FlatList
keyboardDismissMode={true}
showsVerticalScrollIndicator={false}
data={state}
keyExtractor={(comment) => "" + comment.id}
renderItem={renderComment}
/>
When I change the datㅁ which is contained in state, I want to re-run Flatlist with new data.
So after I mutate my data, I try to rerun useQuery first in order to change state. I put refetch module here.
1)
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
If I put button, this onValid function will executed.
<ConfirmButton onPress={handleSubmit(onValid)}>
onValid function changes data and after all finished, as you can see I put refetch().
=> all this process is for that if I add comment and press confirm button, UI (flatlist) should be changed.
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};
But when I console.log data after all, it doesnt' contain added data..
what is the problem here?
If you need more explanation, I can answer in real time.
please help me.
add full code
export default function Comments({ route }) {
const { data: userData } = useMe();
const { register, handleSubmit, setValue, getValues } = useForm();
const [state, setState] = useState(route);
const [update, setUpdate] = useState(false);
const navigation = useNavigation();
useEffect(() => {
setState(route?.params?.comments);
}, [state, route]);
const renderComment = ({ item: comments }) => {
return <CommentRow comments={comments} photoId={route?.params?.photoId} />;
};
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
const createCommentUpdate = (cache, result) => {
const { comments } = getValues();
const {
data: {
createComment: { ok, id, error },
},
} = result;
if (ok) {
const newComment = {
__typename: "Comment",
createdAt: Date.now() + "",
id,
isMine: true,
payload: comments,
user: {
__typename: "User",
avatar: userData?.me?.avatar,
username: userData?.me?.username,
},
};
const newCacheComment = cache.writeFragment({
data: newComment,
fragment: gql`
fragment BSName on Comment {
id
createdAt
isMine
payload
user {
username
avatar
}
}
`,
});
cache.modify({
id: `Photo:${route?.params?.photoId}`,
fields: {
comments(prev) {
return [...prev, newCacheComment];
},
commentNumber(prev) {
return prev + 1;
},
},
});
}
};
const [createCommentMutation] = useMutation(CREATE_COMMENT_MUTATION, {
update: createCommentUpdate,
});
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};
I'm trying to load points from Firebase in order to display it on the screen
I'm using Redux, because the points number can be updated but I can not put this.props.Points.updatePoint inside Firebase request
How can I update it?
Home.js :
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount = async () => {
const pointsRef=firebase.database().ref("Users").child(firebase.auth().currentUser.uid).orderByChild("Points").once('value',function(snapshot){
const Points=snapshot.val().Points
});
this.props.Points.updatePoints(Points)
render(){
return(
<Text>{this.props.Points}</Text>
)}
}
const mapStateToProps = (state) => {
return {
Points:state.Points};
};
const mapDispatchToProps = (dispatch) => {
return {
updatePoints:(Points)=>dispatch({ type: "UPDATE_POINTS", payload: Points }),
};
};
PointReducer.js :
const initialState = {
Points: 0,
};
const Points = (state = initialState, action) => {
switch (action.type) {
case "UPDATE_POINTS":
return {
...state,
Points: action.payload,
};
default:
return state;
}
};
export default Points;
Your method is correct. The problem is actually with the way you're trying to access to updatePoints function in mapDispatchToProps & the place you're run the statement.
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount = async () => {
const pointsRef = firebase
.database()
.ref("Users")
.child(firebase.auth().currentUser.uid)
.orderByChild("Points")
.once("value", (snapshot) => {
const Points = snapshot.val().Points;
this.props.updatePoints(Points); // You can directly access to `updatePoints` using prop object.
}); // convert this function to an arrow function. It will fix `this` related issues.
};
render() {
return <Text>{this.props.Points}</Text>;
}
}
const mapStateToProps = (state) => {
return {
Points: state.Points,
};
};
const mapDispatchToProps = (dispatch) => {
return {
updatePoints: (Points) =>
dispatch({ type: "UPDATE_POINTS", payload: Points }),
};
};
Let me know if you need further support.
My redux is not updating the props.
My component:
...
import { connect } from 'react-redux';
import { getTenantByID, updateTenant } from '../actions';
...
constructor(props) {
super(props);
this.state = {
tenantData: {}
};
}
componentDidMount() {
this.getTenant();
}
onChangeText = (text, input) => {
const obj = { ...this.state.tenantData };
obj[input] = text;
this.setState({
tenantData: obj
});
};
onChangeNumberFormat = (text, input) => {
const obj = { ...this.state.tenantData };
let value = parseFloat(text);
if (isNaN(value)) {
value = 0;
}
value = parseFloat(value).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
obj[input] = value;
this.setState({
tenantData: obj
});
};
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: this.props.tenantData
});
};
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData, saving } = tenants;
return { error, tenantData, saving };
};
export default connect(mapStateToProps, {
getTenantByID, updateTenant
})(TenantDetails);
In my action, I export the method:
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '', RentalAmount: '1000.50', MoveInDate: toDate('2019-01-01'),
MoveOutDate: toDate('2019-12-01'), LeaseStartDate: toDate('2019-01-01'), LeaseEndDate: toDate('2019-12-01'),
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
And I use the reducer to return the data.
...
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
saving: false,
};
...
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
If I do a console.log in the GET_TENANT_DATA in my reducer, I can see that the action.payload has data. But if I do console.log(this.state.tenantData) in my render() method, it is empty. Why is it happening?
Thanks
I include logs in the componentDidMount and render. It display in the following order
call render
this.props.tenantData is empty
Call componentDidMount
this.props.tenantData is empty
call render
this.props.tenantData has value
call render
this.props.tenantData has value
It is never setting state.tenantData. Why is it calling render() after componentDidMount()?
The problem is here, in getTenant function.
getTenant should not be async function becuase you are not returning a promise
componentDidUpdate(prevProps){
if (prevProps.tenantData.Email !== this.props.tenantData.Email) {// you need a unique value to check for changes in props
this.setTenantData();
}
}
setTenantData = () => this.setState({ tenantData: this.props.tenantData });
getTenant = () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
const tenantData = this.props.getTenantByID(tenantID);
};
And this should be your action.
export const getTenantByID = ({ tenantID }) => {
const tenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '', RentalAmount: '1000.50', MoveInDate: toDate('2019-01-01'),
MoveOutDate: toDate('2019-12-01'), LeaseStartDate: toDate('2019-01-01'), LeaseEndDate: toDate('2019-12-01'),
};
return {
type: GET_TENANT_DATA,
payload: tenant
};
};
So you can see tenantData under the console.log in componentDidUpdate.
And the reason for setState not working under getTenant is because the component takes time to update after the redux action
I have an action data that is being sending to the reducer, but not ti my page constructor.
Action Method:
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Then, in my reducer
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
If I do a console.log(action) after the case GET_TENANT_DATA, I can see that data for the payload, so it is working in the reducer.
My page:
constructor(props) {
super(props);
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props); // this show tenantData as a empty object
this.state = {
tenantData: this.props.tenantData
};
}
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Seems like you are using thunk and it's asynchronous, so you need to await your action so that you can get the updated state after you fire the action. Otherwise, you can remove thunk if it's not necessary. You may want to fire the action in componentDidMount instead of constructor too
componentDidMount() {
this.getTenant();
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Or you can capture the update via componentDidUpdate
componentDidMount() {
this.getTenant();
}
componentDidUpdate(previousProps) {
if (this.props.tenantData !== previousProps.tenantData) {
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
I am using React Native with Expo, and I am able to create users + rooms and send messages to them with the following code:
const hooks = {
onMessage: message => {
console.log("message", message);
this.setState({
messages: [...this.state.messages, message]
});
},
onUserStartedTyping: user => {
this.setState({
usersWhoAreTyping: [...this.state.usersWhoAreTyping, user.name]
});
},
onUserStoppedTyping: user => {
this.setState({
usersWhoAreTyping: this.state.usersWhoAreTyping.filter(
username => username !== user.name
)
});
},
onPresenceChange: () => this.forceUpdate()
};
class SetupChatKit extends React.Component {
constructor(props) {
super(props);
this.state = {
chatManager: null,
currentUser: {},
currentRoom: {},
messages: [],
usersWhoAreTyping: []
};
}
componentDidMount() {
const { userId, name } = this.props;
this.instantiateChatManager(userId);
this.createChatKitUser({ userId, name });
}
joinOrCreateChatKitRoom = (mode, chatKitRoomId, title) => {
const { chatManager } = this.state;
return chatManager
.connect()
.then(currentUser => {
this.setState({ currentUser });
if (mode === "join") {
return currentUser.joinRoom({ roomId: chatKitRoomId, hooks });
}
return currentUser.createRoom({
name: title,
private: false,
hooks
});
})
.then(currentRoom => {
this.setState({ currentRoom });
return currentRoom.id;
})
.catch(error => console.error("error", error));
};
instantiateChatManager = userId => {
const chatManager = new Chatkit.ChatManager({
instanceLocator: "v1:us1:9c8d8a28-7103-40cf-bbe4-727eb1a2b598",
userId,
tokenProvider: new Chatkit.TokenProvider({
url: `http://${baseUrl}:3000/api/authenticate`
})
});
this.setState({ chatManager });
};
My problem is that console.log("message", message); never gets called, even when I manually add messages to the room via the online control panel.
I've tried logging from chatManager, and that looks like the following:
As you can see from the documentation, the onMessage hook needs to be attached on subscribeRoom, not when joining a room.
https://docs.pusher.com/chatkit/reference/javascript#connection-hooks
So probably add subscribeToRoom() after the first success promise in your joinOrCreateChatKitRoom() method.
I refactored the code with async/await and used .subscribetoRoom() like so:
joinOrCreateChatKitRoom = async (mode, chatKitRoomId, title) => {
const { chatManager } = this.state;
try {
const currentUser = await chatManager.connect();
this.setState({ currentUser });
let currentRoom;
if (mode === "join") {
currentRoom = await currentUser.joinRoom({
roomId: chatKitRoomId
});
} else {
currentRoom = await currentUser.createRoom({
name: title,
private: false
});
}
this.setState({ currentRoom });
await currentUser.subscribeToRoom({
roomId: currentRoom.id,
hooks: {
onMessage: message => {
this.setState({
messages: [...this.state.messages, message]
});
},
onUserStartedTyping: user => {
this.setState({
usersWhoAreTyping: [...this.state.usersWhoAreTyping, user.name]
});
},
onUserStoppedTyping: user => {
this.setState({
usersWhoAreTyping: this.state.usersWhoAreTyping.filter(
username => username !== user.name
)
});
},
onPresenceChange: () => this.forceUpdate()
}
});
return currentRoom.id;
} catch (error) {
console.error("error creating chatManager", error);
}
};