Issue with updating state in react-redux - reactjs

There is a React component which contains list of users and form to invite a new user.
"inviteNewUser" is a *POST* request in backend
"getUsers" is a *GET* request to get all users
The problem is that after clicking on button "Invite User" I would like to see the invited user in the list of users ("currentUsers" in code below) without refreshing the page. But right now it happens only after I refresh the whole page.
when I'm trying to make a GET request to get all users right after inviteNewUser(data) (POST request) I'm getting the "old" user list without user which I just invited. So the "currentUsers" list is not immediately updated
Could someone help me to fix this issue ?
export function MyForm({
getUsers,
inviteNewUser,
userId,
currentUsers
}) {
useEffect(() => {
getUsers(userId);
}, [userId]);
function handleSendInvite(data) {
inviteNewUser(data);
getUsers(data.userId);
}
return (
<>
{currentUsers.map((user) => (
<UserItem
key={user.userId}
user={user}
/>
))}
<Button
text="Invite User"
onClick={() => {
handleSendInvite({userId});
}}
/>
</>);
}
MyForm.propTypes = {
getUsers: PropTypes.func.isRequired,
inviteNewUser: PropTypes.func.isRequired,
userId: PropTypes.number.isRequired,
currentUsers: PropTypes.arrayOf(UserInfo),
};
const mapStateToProps = (state) => {
const { id } = routerParamsSelector(state);
const currentUsers = selectCurrentUsers(state);
return {
userId: parseInt(id, 10),
currentUsers,
};
};
const mapDispatchToProps = {
getUsers,
inviteNewUser
};
export default connect(mapStateToProps, mapDispatchToProps)(MyForm);

Try async and await, it works.
const handleSendInvite = async (data) {
await inviteNewUser(data);
getUsers(data.userId);
}

It should be user instead of userId as you access the property of the object in the function.
<Button text="Invite User" onClick={() => { handleSendInvite(user); }} />
------------------------------------------------------------------------
const handleSendInvite = async (data) {
await inviteNewUser(data);
getUsers(data.userId);
}

Related

how to Pass props from react-redux to container component?

here is my code in redux ,everythig is fine the code are working
export const loginUser = (values, history, setFieldError, setSubmitting) => {
i take **email**, split it until # and take it as a username ,
const username = values.email.split("#")[0]
return () => {
//then i pass it to axios params as a query name
axios.get(url, {
params: {
name: username
}
}).then((response) => {
//if res ok
console.log("username", username)
history.push("/user")
}).catch(error => console.error(error))
setSubmitting(false);
}
}
now i should pass that usernam as a props to my Dashboard witch is a component
const Dashboard = ({logoutUser,user}) => {
const history = useHistory();
return (
<StyledFromArea bg={colors.dark2}>
here i need to show a *username*
should be like **Hello , YourName**
<StyledTitle size={65}>Hello, {user.user}
//but its undefided
{console.log("user",user.name)}
</StyledTitle>
*same here*
<ExtraText color={colors.light1}>{user.email}</ExtraText>
{console.log("email",user.email)}
<Userinfo/>
<ButtonGroup>
<StyledButton to="#" onClick={()=> logoutUser(history)}> Logout
</StyledButton>
</ButtonGroup>
</StyledFromArea>
)
}
//i use **mapStateToProps**but maybe it's not working ,i think the //problem comes from here
const mapStateToProps =({session})=>({
user:session.user
})
export default connect(mapStateToProps,{logoutUser})(Dashboard) ;
my code
https://codesandbox.io/s/login-page-forked-6gcvq?file=/src/pages/Dashboard.js
First you must use connect with class componets but you use functional style. Seсond in session absent your user data, you must create another reducer for user. Demo

Why does my state affect the emitted events from a click handler?

I have (what seems to be) a very peculiar situation where I seem to be getting extra events emitted based on my Redux state.
I have narrowed the behavior down to whether or not I make a successful request to my /users endpoint and retrieve a list of users which is then stored in Redux.
If the commented code is not active (as it is currently shown), I am able to successfully render the modal(s) reliably and step between states.
If the commented code is active, the (which is what is behind the as well) emits an onDismiss call immediately. This has the result of closing the modal immediately.
If the commented code is active, but the response from the thunk is a 401 and the user data is not loaded (i.e., the state of the user key in redux is a failure, not success, then the modal works -- though of course, there are no users to select.
I have confirmed this behavior is consistent no matter where I seem to make this fetch request (initially it was in the App.tsx to be called immediately. I also tried it in an intermediate component).
Question(s):
Can you explain why I might be getting different behavior in my click handlers based on what is in my state?
Is there something I'm missing and I'm conflating my Redux state with the actual behavior?
I know I can solve this by adding a event.stopPropagation() call in strategic places (e.g., on the first button that opens the <ConfirmationBox> and then again on the button in the <ConfirmationBox> that transitions to the SelectUser modal), but are there other solutions?
//pinFlow.tsx
type States =
| { state: 'Confirm' }
| { state: 'SelectUser' }
| { state: 'SubmitPin'; user: User };
export function pinFlow<T extends ConfirmationBoxProps>(
ConfirmationBox: React.FC<T>,
authorization: Authorization,
) {
const [state, setState] = React.useState<States>({ state: 'Confirm' });
// const dispatch=useDispatch();
// initialize users
// const users = useSelector((state: InitialState) => state.pinAuth.users);
// const fetchUsers = useCallback(() => {
// dispatch(fetchUsersThunk());
// }, [dispatch]);
// useEffect(() => {
// if (users.state === RemoteDataState.NotStarted) {
// fetchUsers();
// }
// }, [fetchUsers, users.state]);
return (props: T) => {
const users = useSelector((state: InitialState) =>
mapRemoteData(state.pinAuth.users, users =>
users.filter(user => user.authorizations.includes(authorization)),
),
);
switch (state.state) {
case 'Confirm': {
return (
<ConfirmationBox
{...props}
onSubmit={(_event: React.MouseEvent) => {
setState({ state: 'SelectUser' });
}}
/>
);
}
case 'SelectUser': {
return (
<Modal
title={'PIN Required'}
canClickOutsideToDismiss={true}
onDismiss={() => {
setState({ state: 'Confirm' });
}}
>
<p className={style.selectProfileText}>Select your profile:</p>
<pre>
<code>{JSON.stringify(users, null, 4)}</code>
</pre>
{/*
<UserList users={users.data} /> */}
</Modal>
);
}
default: {
return <Modal title="others">all others</Modal>;
}
}
};
}
The code is used in another component like so:
function Comp(){
const [selected, setSelected] = useState();
const [mode, setMode] = useState();
const ConfirmationModal =
protected
? pinFlow(MenuItemModal, permission)
: MenuItemModal;
return(
<ConfirmationModal
item={selected}
mode={mode}
disabled={availability.state === RemoteDataState.Loading}
errorMessage={tryGetError(availability)}
onCancel={() => {
setMode(undefined);
dispatch(resetAvailability());
}}
onSubmit={(accessToken: string) => {
dispatch(findAction(selected, mode, accessToken));
}}
/>
)
}

Im getting an object is undefined error in react native app

I am using drawer navigation with react navigation v5, and i have a screen called profile, that takes in a route prop, that i pass user id to. Problem is, when I visit a profile, and then logout, and log back in, i get an error saying that route.params.id is not an object, undefined. In my profile.tsx I checked where i use the route params, and its as in the shown code below:
useEffect(() => {
if (!route) {
return navigation.navigate("Søg Brugere");
}
getUser();
}, [route]);
and getUser function should not be executed, however I include it for clarlity.
const getUser = async () => {
if (!route) return;
try {
console.log(route.params.id);
const id = route.params.id;
setRefreshing(true);
const { data } = await (await HttpClient()).get(
config.SERVER_URL + "/api/user/get-user-by-id/" + id
);
setRefreshing(false);
setProfile(data.user);
setMatch(data.match);
setInitiated(true);
if (socket && user) {
const notificationData = {
url: `/profile/${user._id}`,
type: "new-visit",
text: `Nyt besøg fra ${user.displayName}`,
user: data.user,
resourceId: user._id,
};
socket.emit("notification", notificationData);
}
} catch (e) {
navigation.navigate("Søg Brugere");
}
};
and also a snippet of my logout function, used in drawer navigator:
<View style={{ flex: 1, justifyContent: "flex-end" }}>
<Button
onPress={async () => {
props.navigation.replace("Søg Brugere");
props.navigation.closeDrawer();
await AsyncStorage.removeItem("token");
setUser(null);
}}
title="Log Ud"
color="#F44336"
/>
</View>
i solved this by adding at the top of my rfc:
const id = route?.params?.id

React-admin's onSave method not passing form values

I am working on a React application and have been using the react-admin framework.
I need to pre-process the data coming from a form given that I need separate tables for a new employee and their address but don't want to split it into two screens.
I found the Using onSave To Alter the Form Submission Behavior section in the react-admin's Create/Edit View documentation and I applied it to my code (sample below) in hopes that it would allow me to process the data before getting into the dataProvider. Unfortunately, I can't seem to get the data out of the form and into the callback for the CreateEntity button module.
Create View
const CreateActions = props => (
<TopToolbar {...props}>
<CreateEntityButton props={props} variant="contained" label={"Create"}/>
</TopToolbar>
);
const EmployeeCreate = props => (
<Create {...props} >
<TabbedForm toolbar={<CreateActions record={props.record} redirect="show" />}>
<FormTab label="Identity">
<span >
<PersonInput />
</span>
</FormTab>
<FormTab label="Address">
<span >
<AddressInput />
</span>
</FormTab>
</TabbedForm>
</Create>
)
export default TequitiEmployeeCreate;
When I step through the logic in the browser, the callback function in the handleSave method (below) passes down undefined for both the values and the redirect parameters.
I expected the values object to contain all the input values from the TabbedForm so that it could be parsed and then passed over to my dataProvider module.
CreateEntityButton logic:
const CreateEntityButton = ({ ...props}) => {
const [create] = useCreate(props.props.resource);
const redirectTo = useRedirect();
const notify = useNotify();
const { basePath } = props;
const handleSave = useCallback(
(values, redirect) => { // <-------- undefined all the time
console.log(values);
console.log(redirect);
create(
{
payload: { data: { ...values } },
},
{
onSuccess: ({ data: newRecord }) => {
notify('ra.notification.created', 'info', {
smart_count: 1,
});
redirectTo(redirect, basePath, newRecord.id, newRecord);
},
}
);
},
[create, notify, redirectTo, basePath]
);
return <SaveButton
label={props.label}
variant={props.variant}
handleSubmitWithRedirect={handleSave}
/>;
};
I thought that perhaps having separate modules for PersonInput and AddressInput was to blame for this, but even consolidating all those components into a single one, didn't help.
Any help/thoughts would be helpful.
Turns out, I was mixing the example and was using handleSubmiutWithRedirect instead of the onSave action in the SaveButton.
const CreateEntityButton = ({ ...props}) => {
const resource = props.props.resource;
const redirectTo = useRedirect();
const notify = useNotify();
const { basePath } = props.props;
const dataProvider = useDataProvider();
const handleSave = useCallback(
(values) => {
const createPerson = new PersonAddressCreate(dataProvider);
createPerson.create(values, resource)
.then((data)=>{
notify('ra.notification.created', 'info', { smart_count: 1 });
redirectTo("show", basePath, data.id, data)
})
.catch((error)=>{
notify(error, 'error', { smart_count: 1 });
})
},
[notify, redirectTo, basePath]
);
return <SaveButton
{...props.props}
label={props.label}
variant={props.variant}
onSave={handleSave}
/>;
};

React native, cannot update during an existing state transition

I have a react native component. I got the error:
Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
Code:
import....
class Register extends Component {
static navigationOptions = {
header: null,
};
async handleSubmit(values, customerCreate) {
const { email, password, firstName, lastName, phone } = values;
const input = { email, password, firstName, lastName, phone };
const customerCreateRes = await customerCreate({ variables: { input } });
const isCustomerCreated = !!customerCreateRes.data.customerCreate.customer.id;
if (isCustomerCreated) {
const isStoredCrediential = await storeCredential(email, password);
if (isStoredCrediential === true) {
// Store in redux
// Go to another screen
console.log('test');
}
}
}
render() {
return (
<Mutation mutation={CREATE_CUSTOMER_ACCOUNT}>
{
(customerCreate, { error, data }) => {
return (
<MainLayout
title="Create Account"
backButton
currentTab="profile"
navigation={this.props.navigation}
>
{ showError }
{ showSuccess }
<RegistrationForm
onSubmit={async (values) => this.handleSubmit(values, customerCreate)}
initialValues={this.props.initialValues}
/>
</MainLayout>
);
}
}
</Mutation>
);
}
}
const mapStateToProps = (state) => {
return {
....
};
};
export default connect(mapStateToProps)(Register);
CREATE_CUSTOMER_ACCOUNT is graphql:
import gql from 'graphql-tag';
export const CREATE_CUSTOMER_ACCOUNT = gql`
mutation customerCreate($input: CustomerCreateInput!) {
customerCreate(input: $input) {
userErrors {
field
message
}
customer {
id
}
}
}
`;
More detail here
Who is using the handleSubmit?
There is a button in the form call the handleSubmit, when press.
is this syntax correct onPress={handleSubmit} ?
const PrimaryButton = ({ label, handleSubmit, disabled }) => {
let buttonStyle = styles.button;
if (!disabled) {
buttonStyle = { ...buttonStyle, ...styles.primaryButton };
}
return (
<Button block primary={!disabled} disabled={disabled} onPress={handleSubmit} style={buttonStyle}>
<Text style={styles.buttonText}>{label}</Text>
</Button>
);
};
export default PrimaryButton;
Update 1:
If I remove customerCreate (coming from graphql), the error disappears. It means the async await is actually correct, but I need the customerCreate
Did you check with following code ?
onSubmit={(values) => this.handleSubmit(values, customerCreate)}
If you are trying to add arguments to a handler in recompose, make sure that you're defining your arguments correctly in the handler.
Also can be you're accidentally calling the onSubmit method in your render method, you probably want to double check how your onSubmit in RegistrationForm component.
Also you might want to try one more thing, moving async handleSubmit(values, customerCreate) { to handleSubmit = async(values, customerCreate) =>;
If this doesn't work, please add up your RegistrationForm component as well.
Bottom line, unless your aren't setting state in render, this will not happen.
It turns out the async await syntax is correct. The full original code (not posted here) contains Toast component react-base. The other developer is able to tell me to remove it and the error is gone. Sometimes it is hard to debug.

Resources