have problem with react useEffect function - reactjs

I have this update form for a place and I fetch its data from the backend to add initial inputs in useEffect but I got this error
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I know the problem is related to unmounted the component before update the state but I try many solutions but not working. Anyone have an idea how to fix that
const UpdatePlace = () => {
const placeId = useParams().pId;
const [loadedPlace, setLoadedPlace] = useState();
// const [isLoading, setIsLoading] = useState(true);
const { error, sendRequest, clearError } = useHttpClient();
const [isLoading, formState, inputHandler, setFormData] = useForm(
{
title: {
value: "",
isValid: false,
},
description: {
value: "",
isValid: false,
},
},
true
);
useEffect(() => {
const fetchPlace = async () => {
try {
const res = await sendRequest(`/api/places/${placeId}`);
await setLoadedPlace(res.data.place);
setFormData(
{
title: {
value: res.data.place.title,
isValid: true,
},
description: {
value: res.data.place.description,
isValid: true,
},
},
true
);
} catch (err) {}
};
fetchPlace();
}, [sendRequest, placeId, setFormData]);
if (!loadedPlace && !error) {
return (
<div className="center" style={{ maxWidth: "400px", margin: "0 auto" }}>
<Card>
<h2>No place found!</h2>
</Card>
</div>
);
}
const placeUpdateSubmitHandler = (e) => {
e.preventDefault();
console.log(formState.inputs, formState.isFormValid);
};
return (
<>
{isLoading ? (
<LoadingSpinner asOverlay />
) : error ? (
<ErrorModal error={error} onClear={clearError} />
) : (
<>
<Title label="Update place" />
<form className="place-form" onSubmit={placeUpdateSubmitHandler}>
<Input
element="input"
type="text"
id="title"
label="Update title"
validators={[VALIDATOR_REQUIRE()]}
errorText="please enter valid title"
onInput={inputHandler}
initialValue={loadedPlace.title}
initialValid={true}
/>
<Input
element="textarea"
id="description"
label="Update description"
validators={[VALIDATOR_REQUIRE(), VALIDATOR_MINLENGTH(5)]}
errorText="please enter valid description (min 5 chars) "
onInput={inputHandler}
initialValue={loadedPlace.description}
initialValid={true}
/>
<Button type="submit" disabled={!formState.isFormValid}>
Update place
</Button>
</form>
</>
)}
</>
);
};

You can use useEffect with [] with cleanup function, as it will execute last one like this:
useEffect(() => {
return () => {
console.log('cleaned up');
}
},[])

This error means that your request completes after you have navigated away from that page and it tries to update a component that is already unmounted. You should use an AbortController to abort your request. Something like this should work:
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchPlace = async () => {
try {
const res = await fetch(`/api/places/${placeId}`, { signal }).then(response => {
return response;
}).catch(e => {
console.warn(`Fetch 1 error: ${e.message}`);
});
await setLoadedPlace(res.data.place);
setFormData(
{
title: {
value: res.data.place.title,
isValid: true,
},
description: {
value: res.data.place.description,
isValid: true,
},
},
true
);
} catch (err) {}
};
fetchPlace();
return () => {
controller.abort();
};
}, [sendRequest, placeId, setFormData]);
Edit: Fix undefined obj key/value on render
The above warning will not stop your component from rendering. What would give you an undefined error and prevent your component from rendering is how you initiate the constant loadedPlace. You initiate it as null but you use it as an object inside your Input initialValue={loadedPlace.title}. When your component tries to do the first render it reads the state for that value but fails to locate the key and breaks.
Try this to fix it:
const placeObj = {
title: {
value: '',
isValid: true,
},
description: {
value: '',
isValid: true,
};
const [loadedPlace, setLoadedPlace] = useState(placeObj);
Always make sure that when you use an object you don't use undefined keys upon render.

Related

Cypress does not save and load state properly in React component, why?

I have following state:
const [startPaymentIn, setStartPaymentIn] = useLocalStorage<StartPaymentIn>(
'startPaymentIn', {})
It read content from localstorage. And it is rendered in return. But no value is rendered, though I see values are set.
useEffect(() => {
if (user?.partnerData) {
setStartPaymentIn({
...startPaymentIn,
['partnerData']: user!.partnerData!,
})
setStartPaymentIn({
...startPaymentIn,
['deliveryData']: user!.deliveryData!,
})
} else {
setStartPaymentIn((prevState) => ({
...prevState,
partnerData: { country: 'Hungary' },
}))
}
}, [user])
<div className={'inputContainer'}>
<p>Név</p>
<input
value={startPaymentIn?.partnerData?.name ?? ''}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setData('partnerData', 'name', event.target.value)
}}
/>
</div>

Interaction with Apollo GraphQL Store not Working

I'm Trying to Learn GraphQL by Developing a Simple To-do List App Using React for the FrontEnd with Material-UI. I Need to Now Update the Information on the Web App in Real-time After the Query Gets Executed. I've Written the Code to Update the Store, But for Some Reason it Doesn't Work. This is the Code for App.js.
const TodosQuery = gql`{
todos {
id
text
complete
}
}`;
const UpdateMutation = gql`mutation($id: ID!, $complete: Boolean!) {
updateTodo(id: $id, complete: $complete)
}`;
const RemoveMutation = gql`mutation($id: ID!) {
removeTodo(id: $id)
}`;
const CreateMutation = gql`mutation($text: String!) {
createTodo(text: $text) {
id
text
complete
}
}`;
class App extends Component {
updateTodo = async todo => {
await this.props.updateTodo({
variables: {
id: todo.id,
complete: !todo.complete,
},
update: (store) => {
const data = store.readQuery({ query: TodosQuery });
data.todos = data.todos.map(existingTodo => existingTodo.id === todo.id ? {
...todo,
complete: !todo.complete,
} : existingTodo);
store.writeQuery({ query: TodosQuery, data })
}
});
};
removeTodo = async todo => {
await this.props.removeTodo({
variables: {
id: todo.id,
},
update: (store) => {
const data = store.readQuery({ query: TodosQuery });
data.todos = data.todos.filter(existingTodo => existingTodo.id !== todo.id);
store.writeQuery({ query: TodosQuery, data })
}
});
};
createTodo = async (text) => {
await this.props.createTodo({
variables: {
text,
},
update: (store, { data: { createTodo } }) => {
const data = store.readQuery({ query: TodosQuery });
data.todos.unshift(createTodo);
store.writeQuery({ query: TodosQuery, data })
},
});
}
render() {
const { data: { loading, error, todos } } = this.props;
if(loading) return <p>Loading...</p>;
if(error) return <p>Error...</p>;
return(
<div style={{ display: 'flex' }}>
<div style={{ margin: 'auto', width: 400 }}>
<Paper elevation={3}>
<Form submit={this.createTodo} />
<List>
{todos.map(todo =>
<ListItem key={todo.id} role={undefined} dense button onClick={() => this.updateTodo(todo)}>
<ListItemIcon>
<Checkbox checked={todo.complete} tabIndex={-1} disableRipple />
</ListItemIcon>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<IconButton onClick={() => this.removeTodo(todo)}>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)}
</List>
</Paper>
</div>
</div>
);
}
}
export default compose(
graphql(CreateMutation, { name: 'createTodo' }),
graphql(UpdateMutation, { name: 'updateTodo' }),
graphql(RemoveMutation, { name: 'removeTodo' }),
graphql(TodosQuery)
)(App);
Also, i Want to Create Some List Items but that Doesn't Work Either. I'm Trying to get the Text Entered in the Input Field in Real-time Using a Handler Function handleOnKeyDown() in onKeyDown of the Input Field. I Pass in a event e as a Parameter to handleOnKeyDown(e) and when i console.log(e) it, instead of logging the Text Entered, it Returns a Weird Object that i Do Not Need. This is the Code that Handles Form Actions:
export default class Form extends React.Component{
state = {
text: '',
}
handleChange = (e) => {
const newText = e.target.value;
this.setState({
text: newText,
});
};
handleKeyDown = (e) => {
console.log(e);
if(e.key === 'enter') {
this.props.submit(this.state.text);
this.setState({ text: '' });
}
};
render() {
const { text } = this.state;
return (<TextField onChange={this.handleChange} onKeyDown={this.handleKeyDown} label="To-Do" margin='normal' value={text} fullWidth />);
}
}
This above Code File Gets Included in my App.js.
I Cannot Figure out the Issues. Please Help.
I was stuck with a similar problem. What resolved it for me was replacing the update with refetchQueries as:
updateTodo = async todo => {
await this.props.updateTodo({
variables: {
id: todo.id,
complete: !todo.complete
},
refetchQueries: [{
query: TodosQuery,
variables: {
id: todo.id,
complete: !todo.complete
}
}]
});
};
For your second problem, try capitalizing the 'e' in 'enter' as 'Enter'.
Hope this helps!

Maximum update depth exceeded. After Login Using Context And Hooks

I'm using hooks and Context when after login my URL refreshing sometimes and get this Error Maximum update depth exceeded and my page not loading when refresh page everything is Ok!
this code my Login Page :
function LoginView(props) {
const classes = useStyles()
const [Username, setUsername] = useState('');
const [Password, setPassword] = useState('');
const { getUserLogin, isLogin } = useContext(UserContext)
const handelSubmit = (e) => {
console.log(Username, Password)
if (Username.length < 1) {
alert("لطفا نام کاربری را وارد نمایید")
if (Password.length < 1) {
alert("لطفا رمز عبور را واردنمایید")
}
}
let uuid = uuidv1()
console.log(uuid)
localStorage.setItem('myUUID', uuid)
let xml = 'exampel xlm (srver is SOAP)';
console.log(xml)
getUserLogin(xml)
}
useEffect(() => {
if (isLogin) {
props.history.push("/MainPage")
}
})
return (
<div style={{ direction: 'rtl', }}>
<MyLogo />
<div className={classes.root} >
<div className='textfiled'>
<TextField
className={classes.txt}
name='username'
inputProps={{ style: { textAlign: 'center' } }}
onChange={(e) => setUsername(e.target.value)}
placeholder='نام کاربری' ></TextField>
</div>
<div >
<TextField
className={classes.txt}
inputProps={{ style: { textAlign: 'center' } }}
name='password'
type='password'
onChange={(e) => setPassword(e.target.value)}
placeholder='رمز عبور' ></TextField>
</div>
<div>
<Button color={'inherit'} className={classes.btn} onClick={() => handelSubmit()} > ورود</Button>
</div>
</div>
</div>
)
}
export default withRouter(LoginView);
after submitting my cod get some error in console google I read this post
Maximum update depth exceeded
and change my onclick function but steel error Maximum update depth exceeded
and this is my Context :
export const UserContext = createContext()
export const UserContextDispacher = createContext();
const UserProvider = (props) => {
const [user, setUser] = useState({ username: '', password: '', })
const [isLogin, setisLogin] = useState(false)
const getUserLogin = (value) => {
axios.post('https://exampel.com/myexampel.asmx', value, { headers: { 'Content-Type': 'text/xml;charset=UTF-8' } }).then(function (response) {
// console.log(response)
var options = {
attributeNamePrefix: "#_",
attrNodeName: "attr", //default is 'false'
textNodeName: "#text",
ignoreAttributes: true,
ignoreNameSpace: false,
allowBooleanAttributes: false,
parseNodeValue: true,
parseAttributeValue: false,
trimValues: true,
cdataTagName: "__cdata", //default is 'false'
cdataPositionChar: "\\c",
localeRange: "", //To support non english character in tag/attribute values.
parseTrueNumberOnly: false,
attrValueProcessor: a => he.decode(a, { isAttributeValue: true }),//default is a=>a
tagValueProcessor: a => he.decode(a) //default is a=>a
};
// Intermediate obj
var tObj = parser.getTraversalObj(response.data, options);
var jsonObj = parser.convertToJson(tObj, options);
//set Token
var token = jsonObj["soap:Envelope"]["soap:Body"].AuthenticateUserResponse.Token
var authResult = jsonObj["soap:Envelope"]["soap:Body"].AuthenticateUserResponse.AuthenticateUserResult
if (authResult != false) {
localStorage.setItem('mytoken', token)
localStorage.setItem('myisLogin', authResult)
setisLogin(true)
} else {
localStorage.setItem('myisLogin', authResult)
setisLogin(false)
}
return authResult
}).catch(function (error) {
// console.log("erorr in send to login : " + error)
})
}
return (
<UserContext.Provider value={{ user, getUserLogin, isLogin }}>
<UserContextDispacher.Provider>
{props.children}
</UserContextDispacher.Provider>
</UserContext.Provider>
)
}
export default withRouter(UserProvider);
how to fix it this error?
thank for helping me
UPDATE
if (authResult != false) {
localStorage.setItem('mytoken', token)
localStorage.setItem('myisLogin', authResult)
setisLogin(true)
props.history.push("/MainPage");
}
and delete useEffect from loginview.js
useEffect runs every time a change occurs so useEffect is getting triggered infinitely many times so the error.
Solution : Use isLogin as dependency for useEffect.
useEffect(() => {
if (isLogin) {
props.history.push("/MainPage")
}
},[isLogin])

Multiple useEffect and setState causing callback to be called twice

I'm test driving a pattern I found online known as meiosis as an alternative to Redux using event streams. The concept is simple, the state is produced as a stream of update functions using the scan method to evaluate the function against the current state and return the new state. It works great in all of my test cases but when I use it with react every action is called twice. You can see the entire app and reproduce the issue at CodeSandbox.
import state$, { actions } from "./meiosis";
const App = () => {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState({
title: "",
status: "PENDING"
});
useEffect(() => {
state$
.pipe(
map(state => {
return state.get("todos")
}),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setTodos(state));
}, []);
useEffect(() => {
state$
.pipe(
map(state => state.get("todo")),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setNewTodo(state));
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{genList(todos)}
<div className="formGroup">
<input
type="text"
value={newTodo.title}
onChange={evt => actions.typeNewTodoTitle(evt.target.value)}
/>
<button
onClick = {() => {
actions.addTodo()
}}
>
Add TODO
</button>
<button
onClick={() => {
actions.undo();
}}
>UNDO</button>
</div>
</header>
</div>
);
};
Meisos
import { List, Record } from "immutable";
import { Subject } from "rxjs";
const model = {
initial: {
todo: Record({
title: "",
status: "PENDING"
})(),
todos: List([Record({ title: "Learn Meiosis", status: "PENDING" })()])
},
actions(update) {
return {
addTodo: (title, status = "PENDING") => {
update.next(state => {
console.log(title);
if (!title) {
title = state.get("todo").get("title");
}
const todo = Record({ title, status })();
return state.set("todos", state.get("todos").push(todo));
});
},
typeNewTodoTitle: (title, status = "PENDING") => {
update.next(state => {
return state.set("todo", Record({ title, status })())
});
},
resetTodo: () => {
update.next(state =>
state.set("todo", Record({ title: "", status: "PENDING" })())
);
},
removeTodo: i => {
update.next(state => state.set("todos", state.get("todos").remove(i)));
}
};
}
}
const update$ = new BehaviorSubject(state => state) // identity function to produce initial state
export const actions = model.actions(update$);
export default update$;
Solve my problem. It stemmed from a misunderstanding of how RXJS was working. An issue on the RxJS github page gave me the answer. Each subscriptions causes the observable pipeline to be re-evaluated. By adding the share operator to the pipeline it resolves this behavior.
export default update$.pipe(
scan(
(state, updater) =>
updater(state),
Record(initial)()
),
share()
);

How to use axios in Effect Hook?

In class based Component:
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
this.setState({
posts: res.data.slice(0, 10)
});
console.log(posts);
})
}
I tried this:
const [posts, setPosts] = useState([]);
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
setPosts(res.data.slice(0, 10));
console.log(posts);
})
});
It creates an infinite loop. If I pass a []/{} as the second argument[1][2], then it blocks further call. But it also prevents the array from updating.
[1] Infinite loop in useEffect
[2] How to call loading function with React useEffect only once
Giving an empty array as second argument to useEffect to indicate that you only want the effect to run once after the initial render is the way to go. The reason why console.log(posts); is showing you an empty array is because the posts variable is still referring to the initial array, and setPosts is also asynchronous, but it will still work as you want if used in the rendering.
Example
const { useState, useEffect } = React;
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
setTimeout(() => {
setPosts([{ id: 0, content: "foo" }, { id: 1, content: "bar" }]);
console.log(posts);
}, 1000);
}, []);
return (
<div>{posts.map(post => <div key={post.id}>{post.content}</div>)}</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can check how axios-hooks is implemented.
It's super simple and uses the config object (or url) you provide to decide when to make a request, and when not to, as explained in Tholle's answer.
In addition to allowing you to use the awesome axios in your stateless functional components, it also supports server side rendering, which - it turns out - hooks make very straightforward to implement.
Disclaimer: I'm the author of that package.
I've written a Custom Hooks for Axios.js.
Here's an example:
import React, { useState } from 'react';
import useAxios from '#use-hooks/axios';
export default function App() {
const [gender, setGender] = useState('');
const {
response,
loading,
error,
query,
} = useAxios({
url: `https://randomuser.me/api/${gender === 'unknow' ? 'unknow' : ''}`,
method: 'GET',
options: {
params: { gender },
},
trigger: gender,
filter: () => !!gender,
});
const { data } = response || {};
const options = [
{ gender: 'female', title: 'Female' },
{ gender: 'male', title: 'Male' },
{ gender: 'unknow', title: 'Unknow' },
];
if (loading) return 'loading...';
return (
<div>
<h2>DEMO of <span style={{ color: '#F44336' }}>#use-hooks/axios</span></h2>
{options.map(item => (
<div key={item.gender}>
<input
type="radio"
id={item.gender}
value={item.gender}
checked={gender === item.gender}
onChange={e => setGender(e.target.value)}
/>
{item.title}
</div>
))}
<button type="button" onClick={query}>Refresh</button>
<div>
{error ? error.message || 'error' : (
<textarea cols="100" rows="30" defaultValue={JSON.stringify(data || {}, '', 2)} />
)}
</div>
</div>
);
}
You can see the result online.

Resources